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
@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 actionText: String?
var frontText: String?
var clauses = [ActionableClause]()
// This is here for LabelWithInternalButton
var makeWholeViewClickable = false
@ -41,14 +40,23 @@ public typealias ActionBlock = () -> Void
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 length: Int?
var actionText: String?
var actionBlock: ActionBlock?
var words: [String]? {
return actionText?.components(separatedBy: " ")
}
var actionBlock: ActionBlock?
var range: NSRange {
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 {
@ -153,7 +161,46 @@ public typealias ActionBlock = () -> Void
// 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 }
@ -175,7 +222,7 @@ public typealias ActionBlock = () -> Void
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 {
label.textColor = UIColor.mfGet(forHex: textColorHex)
@ -234,25 +281,30 @@ public typealias ActionBlock = () -> Void
}
case "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 {
guard let locationx = action["location"] as? Int,
let lengthx = action["length"] as? Int
guard let actionLocation = action["location"] as? Int,
let actionLength = action["length"] as? Int,
let subStringRange = Range(NSRange(location: actionLocation, length: actionLength), in: text)
else { continue }
let subStringRange = Range(NSRange(location: locationx, length: lengthx), in: text!)
let actionText = text![subStringRange!]
label.clauses.append(ActionableClause(location: locationx, length: lengthx,
actionText: String(actionText),
actionBlock: { MVMCoreActionHandler.shared()?.handleAction(with: attribute, additionalData: additionalData, delegateObject: delegate) }))
if !label.isUserInteractionEnabled {
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: label, action: #selector(textLinkTapped(_:)))
tapGesture.numberOfTapsRequired = 1
label.addGestureRecognizer(tapGesture)
}
label.clauses.append(ActionableClause(labelView: label,
location: actionLocation,
length: actionLength,
actionText: String(text[subStringRange]),
actionBlock: { [weak delegate] in
var willPerform = true
if let buttonDelegate = (delegate as? MVMCoreUIDelegateObject)?.buttonDelegate,
buttonDelegate.responds(to: #selector(ButtonObjectDelegate.button(_:shouldPerformActionWithMap:additionalData:))) {
willPerform = buttonDelegate.button?(label, shouldPerformActionWithMap: json, additionalData: additionalData) ?? false
}
if willPerform {
MVMCoreActionHandler.shared()?.handleAction(with: attribute, additionalData: additionalData, delegateObject: delegate)
} }))
Label.setGestureInteraction(for: label)
}
default:
continue
@ -341,10 +393,10 @@ public typealias ActionBlock = () -> Void
standardFontSize = 0
}
}
//------------------------------------------------------
// MARK: - Atomization
//------------------------------------------------------
}
// MARK: - Atomization
extension Label {
@objc public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable: Any]?) {
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 {
@objc func textLinkTapped(_ gesture: UITapGestureRecognizer) {
@ -368,6 +421,7 @@ extension Label {
}
}
// For LabelWithInternalButton
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if areTouches(inActionString: touches), let action = actionBlock {
@ -375,6 +429,7 @@ extension Label {
}
}
// For LabelWithInternalButton
private func areTouches(inActionString touches: Set<UITouch>?) -> Bool {
if UIAccessibility.isVoiceOverRunning || makeWholeViewClickable {
@ -401,7 +456,7 @@ extension Label {
return false
}
// Works for LabelWithInternalButton
// For LabelWithInternalButton
private func getRangeArrayOfWords(in string: String?, withInitalIndex index: Int) -> [Any]? {
var index = index
@ -420,7 +475,7 @@ extension Label {
return rangeArray
}
// Works for LabelWithInternalButton
// For LabelWithInternalButton
private func getRectArray(from rangeArray: [Any]?) -> [Any]? {
var rectArray = [AnyHashable]()
@ -434,13 +489,16 @@ extension Label {
}
}
// MARK: - UITapGestureRecognizer Override
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool {
guard let attributedText = label.attributedText else { return false }
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: .zero)
let textStorage = NSTextStorage(attributedString: label.attributedText!)
let textStorage = NSTextStorage(attributedString: attributedText)
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)

View File

@ -146,11 +146,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
}
public convenience init(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
self.init(frontText: actionMap?.optionalStringForKey(KeyTitlePrefix),
actionText: actionMap?.optionalStringForKey(KeyTitle),
backText: actionMap?.optionalStringForKey(KeyTitlePostfix),
actionMap: actionMap, additionalData: additionalData,
delegateObject: delegateObject)
self.init(frontText: actionMap?.optionalStringForKey(KeyTitlePrefix), 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?) {
@ -173,8 +169,17 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
setText(fullText, startTag: startTag, endTag: endTag)
actionBlock = {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
actionBlock = { [weak self, weak delegateObject] in
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)
backText = actionMap?.optionalStringForKey(KeyTitlePostfix)
actionBlock = {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
actionBlock = { [weak self, weak delegateObject] in
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()
@ -501,7 +515,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt
@available(*, deprecated)
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
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)
backText = actionMap?.optionalStringForKey(KeyTitlePostfix)
actionBlock = {
actionBlock = { [weak delegate] in
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegate: delegate as? CoreObjectActionLoadPresentDelegate)
}