Merge branch 'feature/label_export_box' into 'develop'
Multiple Icon Attachments for Label. See merge request BPHV_MIPS/mvm_core_ui!116
This commit is contained in:
commit
8f4abb5864
@ -9,14 +9,14 @@
|
||||
|
||||
import MVMCore
|
||||
|
||||
public typealias ActionBlock = () -> Void
|
||||
public typealias ActionBlock = () -> ()
|
||||
|
||||
|
||||
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol {
|
||||
//------------------------------------------------------
|
||||
// MARK: - General Properties
|
||||
//------------------------------------------------------
|
||||
|
||||
|
||||
public var makeWholeViewClickable = false
|
||||
|
||||
/// Set this property if you want updateView to update the font based on this standard and the size passed in.
|
||||
@ -39,7 +39,17 @@ public typealias ActionBlock = () -> Void
|
||||
//------------------------------------------------------
|
||||
|
||||
public var clauses: [ActionableClause] = [] {
|
||||
didSet { isUserInteractionEnabled = !clauses.isEmpty }
|
||||
didSet {
|
||||
isUserInteractionEnabled = !clauses.isEmpty
|
||||
if clauses.count > 1 {
|
||||
clauses.sort { first, second in
|
||||
guard let firstLocation = first.range?.location,
|
||||
let secondLocation = second.range?.location
|
||||
else { return false }
|
||||
return firstLocation < secondLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for tappable links in the text.
|
||||
@ -113,6 +123,7 @@ public typealias ActionBlock = () -> Void
|
||||
return label
|
||||
}
|
||||
|
||||
/// H32 -> Head
|
||||
@objc public static func commonLabelH32(_ scale: Bool) -> Label {
|
||||
let label = Label.label()
|
||||
label.styleH32(scale)
|
||||
@ -140,12 +151,14 @@ public typealias ActionBlock = () -> Void
|
||||
return label
|
||||
}
|
||||
|
||||
/// B20 -> Body
|
||||
@objc public static func commonLabelB20(_ scale: Bool) -> Label {
|
||||
let label = Label.label()
|
||||
label.styleB20(scale)
|
||||
return label
|
||||
}
|
||||
|
||||
/// Default
|
||||
@objc open class func label() -> Label {
|
||||
return Label(frame: .zero)
|
||||
}
|
||||
@ -189,6 +202,10 @@ public typealias ActionBlock = () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
if let wholeViewIsClickable = json?.boolForKey("makeWholeViewClickable") {
|
||||
(label as? Label)?.makeWholeViewClickable = wholeViewIsClickable
|
||||
}
|
||||
|
||||
if let backgroundColorHex = json?.optionalStringForKey(KeyBackgroundColor), !backgroundColorHex.isEmpty {
|
||||
label.backgroundColor = UIColor.mfGet(forHex: backgroundColorHex)
|
||||
}
|
||||
@ -229,11 +246,28 @@ public typealias ActionBlock = () -> Void
|
||||
case "strikethrough":
|
||||
attributedString.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.thick.rawValue, range: range)
|
||||
attributedString.addAttribute(.baselineOffset, value: 0, range: range)
|
||||
|
||||
case "color":
|
||||
if let colorHex = attribute.optionalStringForKey(KeyTextColor), !colorHex.isEmpty {
|
||||
attributedString.removeAttribute(.foregroundColor, range: range)
|
||||
attributedString.addAttribute(.foregroundColor, value: UIColor.mfGet(forHex: colorHex), range: range)
|
||||
}
|
||||
case "image":
|
||||
let fontSize = attribute["size"] as? CGFloat ?? label.font.pointSize
|
||||
let imageName = attribute["name"] as? String ?? "externalLink"
|
||||
let imageURL = attribute["URL"] as? String
|
||||
let imageAttachment: NSTextAttachment
|
||||
|
||||
if let url = imageURL, let label = label as? Label {
|
||||
imageAttachment = Label.getTextAttachmentFrom(url: url, dimension: fontSize, label: label)
|
||||
} else {
|
||||
imageAttachment = Label.getTextAttachmentImage(name: imageName, dimension: fontSize)
|
||||
}
|
||||
|
||||
let mutableString = NSMutableAttributedString()
|
||||
mutableString.append(NSAttributedString(attachment: imageAttachment))
|
||||
attributedString.insert(mutableString, at: location)
|
||||
|
||||
case "font":
|
||||
if let fontStyle = attribute.optionalStringForKey("style") {
|
||||
let styles = MFStyler.styleGetAttributedString("0", withStyle: fontStyle)
|
||||
@ -243,13 +277,13 @@ public typealias ActionBlock = () -> Void
|
||||
} else {
|
||||
let fontSize = attribute["size"] as? CGFloat
|
||||
var font: UIFont?
|
||||
|
||||
|
||||
if let fontName = attribute.optionalStringForKey("name") {
|
||||
font = MFFonts.mfFont(withName: fontName, size: fontSize ?? label.font.pointSize)
|
||||
} else if let fontSize = fontSize {
|
||||
font = label.font.withSize(fontSize)
|
||||
}
|
||||
|
||||
|
||||
if let font = font {
|
||||
attributedString.removeAttribute(.font, range: range)
|
||||
attributedString.addAttribute(.font, value: font, range: range)
|
||||
@ -260,9 +294,9 @@ public typealias ActionBlock = () -> Void
|
||||
|
||||
actionLabel.addActionAttributes(range: range, string: attributedString)
|
||||
actionLabel.clauses.append(ActionableClause(range: range,
|
||||
actionBlock: actionLabel.createActionBlockFrom(actionMap: json,
|
||||
additionalData: additionalData,
|
||||
delegateObject: delegate)))
|
||||
actionBlock: actionLabel.createActionBlockFrom(actionMap: json,
|
||||
additionalData: additionalData,
|
||||
delegateObject: delegate)))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
@ -271,8 +305,6 @@ public typealias ActionBlock = () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//------------------------------------------------------
|
||||
@ -323,15 +355,28 @@ public typealias ActionBlock = () -> Void
|
||||
if let originalAttributedString = originalAttributedString {
|
||||
let attributedString = NSMutableAttributedString(attributedString: originalAttributedString)
|
||||
attributedString.removeAttribute(.font, range: NSRange(location: 0, length: attributedString.length))
|
||||
originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: [], using: { value, range, stop in
|
||||
// Loop the original attributed string, resize the fonts.
|
||||
|
||||
// Loop the original attributed string, resize the fonts.
|
||||
originalAttributedString.enumerateAttribute(.font, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
|
||||
|
||||
if let fontObj = value as? UIFont, let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: fontObj.pointSize)?.getValueBased(onSize: size) {
|
||||
attributedString.addAttribute(.font, value: fontObj.withSize(stylerSize) as Any, range: range)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Loop the original attributed string, resize the image attachments.
|
||||
originalAttributedString.enumerateAttribute(.attachment, in: NSRange(location: 0, length: originalAttributedString.length), options: []) { value, range, stop in
|
||||
if let attachment = value as? NSTextAttachment,
|
||||
let stylerSize = MFStyler.sizeObjectGeneric(forCurrentDevice: attachment.bounds.width)?.getValueBased(onSize: size) {
|
||||
|
||||
let dimension = round(stylerSize)
|
||||
attachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
|
||||
}
|
||||
}
|
||||
|
||||
attributedText = attributedString
|
||||
} else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject: MFSizeObject = self.sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) {
|
||||
self.font = self.font.withSize(sizeObject.getValueBased(onSize: size))
|
||||
} else if !MVMCoreGetterUtility.fequal(a: Float(standardFontSize), b: 0.0), let sizeObject = sizeObject ?? MFStyler.sizeObjectGeneric(forCurrentDevice: standardFontSize) {
|
||||
font = font.withSize(sizeObject.getValueBased(onSize: size))
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,23 +398,92 @@ public typealias ActionBlock = () -> Void
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Appends an external link image to the end of the attributed string.
|
||||
Will provide one whitespace to the left of the icon
|
||||
*/
|
||||
@objc public func appendExternalLinkIcon() {
|
||||
|
||||
guard let attributedText = attributedText else { return }
|
||||
|
||||
let mutableString = NSMutableAttributedString(attributedString: attributedText)
|
||||
|
||||
mutableString.append(NSAttributedString(string: " "))
|
||||
mutableString.append(NSAttributedString(attachment: Label.getTextAttachmentImage(dimension: font.pointSize)))
|
||||
self.attributedText = mutableString
|
||||
}
|
||||
|
||||
///Appends an external link image to the end of the attributed string.
|
||||
public func addExternalLinkIcon() {
|
||||
/**
|
||||
Insert external link icon anywhere within text of Label.
|
||||
|
||||
- Note: Each icon insertion adds 1 additional characters to the overall text length.
|
||||
Therefore, you MUST insert icons and links in the order they would appear.
|
||||
- parameter index: Location within the associated text to insert an external Link Icon
|
||||
*/
|
||||
public func insertExternalLinkIcon(at index: Int) {
|
||||
|
||||
let size = round(font.pointSize * 0.8)
|
||||
guard let attributedText = attributedText, index <= attributedText.string.count && index >= 0 else { return }
|
||||
|
||||
guard let attributedText = self.attributedText else { return }
|
||||
let mutableString = NSMutableAttributedString(attributedString: attributedText)
|
||||
mutableString.insert(NSAttributedString(attachment: Label.getTextAttachmentImage(dimension: font.pointSize)), at: index)
|
||||
|
||||
let fullString = NSMutableAttributedString(attributedString: attributedText)
|
||||
self.attributedText = mutableString
|
||||
}
|
||||
|
||||
/*
|
||||
Retrieves an NSTextAttachment for NSAttributedString that is prepped to be inserted with the text.
|
||||
|
||||
- parameter name: The Asset name of the image. DEFAULT: "externalLink"
|
||||
- parameter dimension: length of the height and width of the image. Will be 80% the passed magnitude.
|
||||
*/
|
||||
static func getTextAttachmentImage(name: String = "externalLink", dimension: CGFloat) -> NSTextAttachment {
|
||||
|
||||
let dimension = round(dimension * 0.8)
|
||||
|
||||
let imageAttachment = NSTextAttachment()
|
||||
imageAttachment.image = MVMCoreUIUtility.imageNamed("externalLink")
|
||||
imageAttachment.bounds = CGRect(x: 0, y: 0, width: size, height: size)
|
||||
imageAttachment.image = MVMCoreUIUtility.imageNamed(name)
|
||||
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
|
||||
|
||||
fullString.append(NSAttributedString(string: " "))
|
||||
fullString.append(NSAttributedString(attachment: imageAttachment))
|
||||
self.attributedText = fullString
|
||||
return imageAttachment
|
||||
}
|
||||
|
||||
static func getTextAttachmentFrom(url: String, dimension: CGFloat, label: Label) -> NSTextAttachment {
|
||||
|
||||
let dimension = round(dimension * 0.8)
|
||||
|
||||
let imageAttachment = NSTextAttachment()
|
||||
imageAttachment.bounds = CGRect(x: 0, y: 0, width: dimension, height: dimension)
|
||||
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
|
||||
guard let url = URL(string: url),
|
||||
let data = try? Data(contentsOf: url)
|
||||
else { return }
|
||||
|
||||
DispatchQueue.main.sync {
|
||||
imageAttachment.image = UIImage(data: data)
|
||||
label.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
return imageAttachment
|
||||
}
|
||||
|
||||
/// Call to detect in the attributedText contains an NSTextAttachment.
|
||||
func textContainsTextAttachment() -> Bool {
|
||||
|
||||
guard let attributedText = attributedText else { return false }
|
||||
|
||||
var containsAttachment = false
|
||||
|
||||
attributedText.enumerateAttribute(.attachment, in: NSRange(location: 0, length: attributedText.length), options: []) { value, range, stop in
|
||||
if value is NSTextAttachment {
|
||||
containsAttachment = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return containsAttachment
|
||||
}
|
||||
}
|
||||
|
||||
@ -452,9 +566,8 @@ extension Label {
|
||||
Provides an actionable range of text.
|
||||
|
||||
- Attention: This method expects text to be set first. Otherwise, it will do nothing.
|
||||
- Parameters:
|
||||
- range: The range of text to be tapped.
|
||||
- actionBlock: The code triggered when tapping the range of text.
|
||||
- parameter range: The range of text to be tapped.
|
||||
- parameter actionBlock: The code triggered when tapping the range of text.
|
||||
*/
|
||||
@objc public func addTappableLinkAttribute(range: NSRange, actionBlock: @escaping ActionBlock) {
|
||||
|
||||
@ -466,11 +579,10 @@ extension Label {
|
||||
Provides an actionable range of text.
|
||||
|
||||
- Attention: This method expects text to be set first. Otherwise, it will do nothing.
|
||||
- Parameters:
|
||||
- range: The range of text to be tapped.
|
||||
- actionMap:
|
||||
- delegate:
|
||||
- additionalData:
|
||||
- parameter range: The range of text to be tapped.
|
||||
- parameter actionMap:
|
||||
- parameter delegate:
|
||||
- parameter additionalData:
|
||||
*/
|
||||
@objc public func addTappableLinkAttribute(range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
|
||||
|
||||
@ -484,6 +596,8 @@ extension Label {
|
||||
@objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) {
|
||||
|
||||
for clause in clauses {
|
||||
|
||||
// This determines if we tapped on the desired range of text.
|
||||
if let range = clause.range, gesture.didTapAttributedTextInLabel(self, inRange: range) {
|
||||
clause.performAction()
|
||||
return
|
||||
@ -497,15 +611,23 @@ extension UITapGestureRecognizer {
|
||||
|
||||
func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
|
||||
|
||||
// There would only ever be one clause to act on.
|
||||
if label.makeWholeViewClickable {
|
||||
return true
|
||||
}
|
||||
|
||||
// Must configure the attributed string to translate what would appear on screen to accurately analyze.
|
||||
guard let attributedText = label.attributedText else { return false }
|
||||
|
||||
let paragraph = NSMutableParagraphStyle()
|
||||
paragraph.alignment = label.textAlignment
|
||||
|
||||
let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText)
|
||||
stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count))
|
||||
|
||||
let textStorage = NSTextStorage(attributedString: stagedAttributedString)
|
||||
let layoutManager = NSLayoutManager()
|
||||
let textContainer = NSTextContainer(size: .zero)
|
||||
let textStorage = NSTextStorage(attributedString: attributedText)
|
||||
|
||||
layoutManager.addTextContainer(textContainer)
|
||||
textStorage.addLayoutManager(layoutManager)
|
||||
@ -515,8 +637,8 @@ extension UITapGestureRecognizer {
|
||||
textContainer.maximumNumberOfLines = label.numberOfLines
|
||||
textContainer.size = label.bounds.size
|
||||
|
||||
let indexOfCharacter = layoutManager.characterIndex(for: location(in: label), in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
|
||||
|
||||
return NSLocationInRange(indexOfCharacter, targetRange)
|
||||
let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer)
|
||||
|
||||
return NSLocationInRange(indexOfGlyph, targetRange)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user