From 4770aa6e6bd4c78fd557e42754d766fd488f97b4 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 23 Sep 2019 12:35:51 -0400 Subject: [PATCH] Label Improvements. Conveniences added. Greater accuracy to tap detection provided. --- MVMCoreUI/Atoms/Views/Label.swift | 67 +++++++++++-------- .../Atoms/Views/LabelWithInternalButton.swift | 4 +- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index e1c4cc5e..c62bdf52 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -115,25 +115,11 @@ public typealias ActionBlock = () -> () standardFontSize = size } - /// Convenience to init Label as a TextButton. All text will behave as a link. - @objc convenience public init(text: String, actionBlock: @escaping ActionBlock) { - self.init() - self.text = text - setTextLinkState(range: getRange, actionBlock: actionBlock) - } - - /// Convenience to init Label where the provided text will be marked as tappable by the given range. - @objc convenience public init(text: String, range: NSRange, actionBlock: @escaping ActionBlock) { - self.init() - self.text = text - setTextLinkState(range: range, actionBlock: actionBlock) - } - /// Convenience to init Label with the link comprised of range, actionMap and delegateObject @objc convenience public init(text: String, range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { self.init() self.text = text - if let actionBlock = Label.createActionBlockFor(label: self, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { setTextLinkState(range: range, actionBlock: actionBlock) } } @@ -333,7 +319,7 @@ public typealias ActionBlock = () -> () guard let actionLabel = label as? Label else { continue } actionLabel.addActionAttributes(range: range, string: attributedString) - if let actionBlock = Label.createActionBlockFor(label: actionLabel, actionMap: json, additionalData: additionalData, delegateObject: delegate) { + if let actionBlock = actionLabel.createActionBlockFor(actionMap: json, additionalData: additionalData, delegateObject: delegate) { actionLabel.appendActionableClause(range: range, actionBlock: actionBlock) } @@ -510,7 +496,7 @@ public typealias ActionBlock = () -> () } /// Call to detect in the attributedText contains an NSTextAttachment. - func textContainsTextAttachment() -> Bool { + func containsTextAttachment() -> Bool { guard let attributedText = attributedText else { return false } @@ -588,10 +574,9 @@ extension Label { clauses = [] } - public static func createActionBlockFor(label: Label?, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? { - guard let label = label else { return nil } - return { - if (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(label, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { + public func createActionBlockFor(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) -> ActionBlock? { + return { [weak self] in + if let wSelf = self, (delegateObject as? MVMCoreUIDelegateObject)?.buttonDelegate?.button?(wSelf, shouldPerformActionWithMap: actionMap, additionalData: additionalData) ?? true { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } @@ -635,19 +620,19 @@ extension Label { */ @objc public func addTappableLinkAttribute(range: NSRange, actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - if let actionBlock = Label.createActionBlockFor(label: self, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { setTextLinkState(range: range, actionBlock: actionBlock) } } - @objc public func makeAllTextLinkable(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { + @objc public func makeTextButton(actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - if let actionBlock = Label.createActionBlockFor(label: self, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { + if let actionBlock = createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) { setTextLinkState(range: getRange, actionBlock: actionBlock) } } - @objc public func makeAllTextLinkable(actionBlock: @escaping ActionBlock) { + @objc public func makeTextButton(actionBlock: @escaping ActionBlock) { setTextLinkState(range: getRange, actionBlock: actionBlock) } @@ -689,7 +674,7 @@ extension UITapGestureRecognizer { paragraph.alignment = label.textAlignment let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) - stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) let textStorage = NSTextStorage(attributedString: stagedAttributedString) let layoutManager = NSLayoutManager() @@ -703,9 +688,33 @@ extension UITapGestureRecognizer { textContainer.maximumNumberOfLines = label.numberOfLines textContainer.size = label.bounds.size - let indexOfGlyph = layoutManager.glyphIndex(for: location(in: label), in: textContainer) - - return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(location(in: label)) && NSLocationInRange(indexOfGlyph, targetRange) + let tapLocation = location(in: label) + let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer) + let intrinsicWidth = label.intrinsicContentSize.width + + // Assert that tapped occured within acceptable bounds based on alignment. + switch label.textAlignment { + case .right: + if tapLocation.x < label.bounds.width - intrinsicWidth { + return false + } + case .center: + let halfBounds = label.bounds.width / 2 + let halfintrinsicWidth = intrinsicWidth / 2 + + if tapLocation.x > halfBounds + halfintrinsicWidth{ + return false + } else if tapLocation.x < halfBounds - halfintrinsicWidth { + return false + } + default: // Left align + if tapLocation.x > intrinsicWidth { + return false + } + } + + // Affirms that the tap occured in the desired rect of provided by the target range. + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange) } } diff --git a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift index 44f254f4..070cc0f7 100644 --- a/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift +++ b/MVMCoreUI/Atoms/Views/LabelWithInternalButton.swift @@ -207,7 +207,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt @objc public func setActionMap(_ actionMap: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - actionBlock = Label.createActionBlockFor(label: label, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + actionBlock = label?.createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) } //------------------------------------------------------ @@ -377,7 +377,7 @@ public typealias CoreObjectActionLoadPresentDelegate = MVMCoreActionDelegateProt actionText = actionMap?.optionalStringForKey(KeyTitle) backText = actionMap?.optionalStringForKey(KeyTitlePostfix) text = getTextFromStringComponents() - actionBlock = Label.createActionBlockFor(label: label, actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) + actionBlock = label?.createActionBlockFor(actionMap: actionMap, additionalData: additionalData, delegateObject: delegateObject) setLabelAttributes() }