Added button delegate checks for labelWithInternalButton. WIP with multi-action label.

This commit is contained in:
Christiano, Kevin 2019-05-02 14:12:41 -04:00
parent 9c6ddc2470
commit d1153d4bf8
2 changed files with 115 additions and 43 deletions

View File

@ -11,17 +11,16 @@ import MVMCore
public typealias ActionBlock = () -> Void public typealias ActionBlock = () -> Void
@objcMembers open class Label: UILabel, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol { @objcMembers open class Label: UILabel, MVMCoreViewProtocol, MFButtonProtocol, MVMCoreUIMoleculeViewProtocol {
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Properties // MARK: - General Properties
//------------------------------------------------------ //------------------------------------------------------
// Specifically used in LabelWithInternalButton to interact with UIControl.
var actionBlock: ActionBlock? var actionBlock: ActionBlock?
var actionText: String? var actionText: String?
var frontText: String? var frontText: String?
var clauses = [ActionableClause]()
// This is here for LabelWithInternalButton // This is here for LabelWithInternalButton
var makeWholeViewClickable = false var makeWholeViewClickable = false
@ -41,14 +40,23 @@ public typealias ActionBlock = () -> Void
return !text.isEmpty || !attributedText.string.isEmpty return !text.isEmpty || !attributedText.string.isEmpty
} }
struct ActionableClause { //------------------------------------------------------
// MARK: - For Multi-Action Text
//------------------------------------------------------
private var clauses: [ActionableClause] = []
// Used for tappable links in the text.
private struct ActionableClause {
var labelView: Label?
var location: Int? var location: Int?
var length: Int? var length: Int?
var actionText: String? var actionText: String?
var actionBlock: ActionBlock?
var words: [String]? { var words: [String]? {
return actionText?.components(separatedBy: " ") return actionText?.components(separatedBy: " ")
} }
var actionBlock: ActionBlock?
var range: NSRange { var range: NSRange {
return NSRange(location: location ?? 0, length: length ?? 0) return NSRange(location: location ?? 0, length: length ?? 0)
@ -94,7 +102,7 @@ public typealias ActionBlock = () -> Void
} }
//------------------------------------------------------ //------------------------------------------------------
// MARK: - Functions // MARK: - Factory Functions
//------------------------------------------------------ //------------------------------------------------------
@objc public static func commonLabelH1(_ scale: Bool) -> Label { @objc public static func commonLabelH1(_ scale: Bool) -> Label {
@ -153,7 +161,46 @@ public typealias ActionBlock = () -> Void
// MARK: - Functions // MARK: - Functions
//------------------------------------------------------ //------------------------------------------------------
@objc public static func setLabel(_ label: UILabel?, withHTML html: String?) { /**
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.
*/
@objc public func addTappableLinkAttribute(range: NSRange, actionBlock: @escaping ActionBlock) {
guard let text = self.text,
let subStringRange = Range(range, in: text)
else { return }
clauses.append(ActionableClause(labelView: self,
location: range.location,
length: range.length,
actionText: String(text[subStringRange]),
actionBlock: actionBlock))
Label.setGestureInteraction(for: self)
}
/**
Makes the view interactive and applies the gesture recognizer.
- Parameters:
- label: The current label view that will have an actionable range of text.
*/
private static func setGestureInteraction(for label: Label) {
if !label.isUserInteractionEnabled {
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: label, action: #selector(textLinkTapped(_:)))
tapGesture.numberOfTapsRequired = 1
label.addGestureRecognizer(tapGesture)
}
}
@objc public static func setLabel(_ label: UILabel?, with html: String?) {
guard let data = html?.data(using: .utf8) else { return } guard let data = html?.data(using: .utf8) else { return }
@ -175,7 +222,7 @@ public typealias ActionBlock = () -> Void
label.text = json?.optionalStringForKey(KeyText) label.text = json?.optionalStringForKey(KeyText)
setLabel(label, withHTML: json?.optionalStringForKey("html")) setLabel(label, with: json?.optionalStringForKey("html"))
if let textColorHex = json?.optionalStringForKey(KeyTextColor), !textColorHex.isEmpty { if let textColorHex = json?.optionalStringForKey(KeyTextColor), !textColorHex.isEmpty {
label.textColor = UIColor.mfGet(forHex: textColorHex) label.textColor = UIColor.mfGet(forHex: textColorHex)
@ -234,25 +281,30 @@ public typealias ActionBlock = () -> Void
} }
case "actions": case "actions":
let actions = attribute.arrayForKey("actions") let actions = attribute.arrayForKey("actions")
let text = json?.optionalStringForKey(KeyText) guard let text = json?.optionalStringForKey(KeyText) else { continue }
for case let action as [String: Any] in actions { for case let action as [String: Any] in actions {
guard let locationx = action["location"] as? Int, guard let actionLocation = action["location"] as? Int,
let lengthx = action["length"] as? Int let actionLength = action["length"] as? Int,
let subStringRange = Range(NSRange(location: actionLocation, length: actionLength), in: text)
else { continue } else { continue }
let subStringRange = Range(NSRange(location: locationx, length: lengthx), in: text!) label.clauses.append(ActionableClause(labelView: label,
let actionText = text![subStringRange!] location: actionLocation,
length: actionLength,
actionText: String(text[subStringRange]),
actionBlock: { [weak delegate] in
var willPerform = true
label.clauses.append(ActionableClause(location: locationx, length: lengthx, if let buttonDelegate = (delegate as? MVMCoreUIDelegateObject)?.buttonDelegate,
actionText: String(actionText), buttonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) {
actionBlock: { MVMCoreActionHandler.shared()?.handleAction(with: attribute, additionalData: additionalData, delegateObject: delegate) })) willPerform = buttonDelegate.button?(label, shouldPerformActionWithMap: json, additionalData: additionalData) ?? false
if !label.isUserInteractionEnabled { }
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: label, action: #selector(textLinkTapped(_:))) if willPerform {
tapGesture.numberOfTapsRequired = 1 MVMCoreActionHandler.shared()?.handleAction(with: attribute, additionalData: additionalData, delegateObject: delegate)
label.addGestureRecognizer(tapGesture) } }))
} Label.setGestureInteraction(for: label)
} }
default: default:
continue continue
@ -341,10 +393,10 @@ public typealias ActionBlock = () -> Void
standardFontSize = 0 standardFontSize = 0
} }
} }
}
//------------------------------------------------------ // MARK: - Atomization
// MARK: - Atomization extension Label {
//------------------------------------------------------
@objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) { @objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) {
Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData) Label.setUILabel(self, withJSON: json, delegate: delegateObject, additionalData: additionalData)
@ -356,7 +408,8 @@ public typealias ActionBlock = () -> Void
} }
} }
// MARK: - UIControl Override
// MARK: - UIControl functionality Override
extension Label { extension Label {
@objc func textLinkTapped(_ gesture: UITapGestureRecognizer) { @objc func textLinkTapped(_ gesture: UITapGestureRecognizer) {
@ -368,6 +421,7 @@ extension Label {
} }
} }
// For LabelWithInternalButton
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if areTouches(inActionString: touches), let action = actionBlock { if areTouches(inActionString: touches), let action = actionBlock {
@ -375,6 +429,7 @@ extension Label {
} }
} }
// For LabelWithInternalButton
private func areTouches(inActionString touches: Set<UITouch>?) -> Bool { private func areTouches(inActionString touches: Set<UITouch>?) -> Bool {
if UIAccessibility.isVoiceOverRunning || makeWholeViewClickable { if UIAccessibility.isVoiceOverRunning || makeWholeViewClickable {
@ -401,7 +456,7 @@ extension Label {
return false return false
} }
// Works for LabelWithInternalButton // For LabelWithInternalButton
private func getRangeArrayOfWords(in string: String?, withInitalIndex index: Int) -> [Any]? { private func getRangeArrayOfWords(in string: String?, withInitalIndex index: Int) -> [Any]? {
var index = index var index = index
@ -420,7 +475,7 @@ extension Label {
return rangeArray return rangeArray
} }
// Works for LabelWithInternalButton // For LabelWithInternalButton
private func getRectArray(from rangeArray: [Any]?) -> [Any]? { private func getRectArray(from rangeArray: [Any]?) -> [Any]? {
var rectArray = [AnyHashable]() var rectArray = [AnyHashable]()
@ -434,13 +489,16 @@ extension Label {
} }
} }
// MARK: - UITapGestureRecognizer Override
extension UITapGestureRecognizer { extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool { func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
guard let attributedText = label.attributedText else { return false }
let layoutManager = NSLayoutManager() let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero) let textContainer = NSTextContainer(size: .zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!) let textStorage = NSTextStorage(attributedString: attributedText)
layoutManager.addTextContainer(textContainer) layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager) textStorage.addLayoutManager(layoutManager)

View File

@ -146,11 +146,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
} }
public convenience init(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { public convenience init(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
self.init(frontText: actionMap?.optionalStringForKey(KeyTitlePrefix), self.init(frontText: actionMap?.optionalStringForKey(KeyTitlePrefix), actionText: actionMap?.optionalStringForKey(KeyTitle), backText: actionMap?.optionalStringForKey(KeyTitlePostfix), actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject)
actionText: actionMap?.optionalStringForKey(KeyTitle),
backText: actionMap?.optionalStringForKey(KeyTitlePostfix),
actionMap: actionMap, additionalData: additionalData,
delegateObject: delegateObject)
} }
public convenience init(frontText: String?, backText: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { public convenience init(frontText: String?, backText: String?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
@ -173,8 +169,17 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
setText(fullText, startTag: startTag, endTag: endTag) setText(fullText, startTag: startTag, endTag: endTag)
actionBlock = { actionBlock = { [weak self, weak delegateObject] in
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) var performAction = true
if let wSelf = self, let wButtonDelegate = (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate,
wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) {
performAction = wButtonDelegate.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? false
}
if performAction {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
}
} }
} }
@ -406,8 +411,17 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
actionText = actionMap?.optionalStringForKey(KeyTitle) actionText = actionMap?.optionalStringForKey(KeyTitle)
backText = actionMap?.optionalStringForKey(KeyTitlePostfix) backText = actionMap?.optionalStringForKey(KeyTitlePostfix)
actionBlock = { actionBlock = { [weak self, weak delegateObject] in
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) var performAction = true
if let wSelf = self, let wButtonDelegate = (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate,
wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) {
performAction = wButtonDelegate.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? false
}
if performAction {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
}
} }
text = getTextFromStringComponents() text = getTextFromStringComponents()
@ -501,7 +515,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
@available(*, deprecated) @available(*, deprecated)
private func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, actionDelegate delegate: ActionObjectDelegate?, buttonDelegate: ButtonObjectDelegate?) { private func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, actionDelegate delegate: ActionObjectDelegate?, buttonDelegate: ButtonObjectDelegate?) {
actionBlock = { [weak self] in actionBlock = { [weak self, weak buttonDelegate] in
var performAction = true var performAction = true
if let wSelf = self, let wButtonDelegate = buttonDelegate, wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) { if let wSelf = self, let wButtonDelegate = buttonDelegate, wButtonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) {
@ -621,7 +635,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
actionText = actionMap?.optionalStringForKey(KeyTitle) actionText = actionMap?.optionalStringForKey(KeyTitle)
backText = actionMap?.optionalStringForKey(KeyTitlePostfix) backText = actionMap?.optionalStringForKey(KeyTitlePostfix)
actionBlock = { actionBlock = { [weak delegate] in
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate) MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate)
} }