Added button delegate checks for labelWithInternalButton. WIP with multi-action label.
This commit is contained in:
parent
9c6ddc2470
commit
d1153d4bf8
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user