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:
Pfeil, Scott Robert 2019-09-04 13:52:40 -04:00
commit 8f4abb5864

View File

@ -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)
}
}