diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index c622b694..78d34574 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -389,82 +389,59 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } } + //-------------------------------------------------- + // MARK: - Touch Events + //-------------------------------------------------- @objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) { - for actionable in actions { - // This determines if we tapped on the desired range of text. - let location = gesture.location(in: self) - if didTapActionInLabel(location, inRange: actionable.range) { - actionable.performAction() - return - } + let location = gesture.location(in: self) + if let action = actions.first(where: { isAction(for: location, inRange: $0.range) }) { + action.performAction() } } public func isAction(for location: CGPoint) -> Bool { - for actionable in actions { - if didTapActionInLabel(location, inRange: actionable.range) { - return true - } - } - return false + actions.contains(where: {isAction(for: location, inRange: $0.range)}) } - private func didTapActionInLabel(_ location: CGPoint, inRange targetRange: NSRange) -> Bool { + public func isAction(for location: CGPoint, inRange targetRange: NSRange) -> Bool { + guard let attributedText = attributedText, let abstractContainer = abstractTextContainer() else { return false } + let textContainer = abstractContainer.textContainer + let layoutManager = abstractContainer.layoutManager -// guard let attributedText else { return false } -// let layoutManager = NSLayoutManager() -// let textContainer = NSTextContainer(size: bounds.size) -// let textStorage = NSTextStorage(attributedString: attributedText) - // layoutManager.addTextContainer(textContainer) - // textStorage.addLayoutManager(layoutManager) - // - // let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - // - // guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, - // characterIndex < attributedText.length else { - // return false - // } - // return true - - // There would only ever be one clause to act on. - guard let abstractContainer = abstractTextContainer() else { return false } - let textContainer = abstractContainer.0 - let layoutManager = abstractContainer.1 - - let tapLocation = location - let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer) + let indexOfGlyph = layoutManager.glyphIndex(for: location, in: textContainer) let intrinsicWidth = intrinsicContentSize.width // Assert that tapped occured within acceptable bounds based on alignment. switch textAlignment { case .right: - if tapLocation.x < bounds.width - intrinsicWidth { + if location.x < bounds.width - intrinsicWidth { return false } case .center: let halfBounds = bounds.width / 2 let halfIntrinsicWidth = intrinsicWidth / 2 - if tapLocation.x > halfBounds + halfIntrinsicWidth { + if location.x > halfBounds + halfIntrinsicWidth { return false - } else if tapLocation.x < halfBounds - halfIntrinsicWidth { + } else if location.x < halfBounds - halfIntrinsicWidth { return false } default: // Left align - if tapLocation.x > intrinsicWidth { + if location.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) + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(location) + && NSLocationInRange(indexOfGlyph, targetRange) } /** Provides a text container and layout manager of how the text would appear on screen. They are used in tandem to derive low-level TextKit results of the label. */ - public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? { + public func abstractTextContainer() -> (textContainer: NSTextContainer, layoutManager: NSLayoutManager, textStorage: NSTextStorage)? { // Must configure the attributed string to translate what would appear on screen to accurately analyze. guard let attributedText = attributedText else { return nil } @@ -489,25 +466,23 @@ open class Label: UILabel, ViewProtocol, UserInfoable { return (textContainer, layoutManager, textStorage) } - + + //-------------------------------------------------- + // MARK: - Accessibility + //-------------------------------------------------- private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { - guard let text = text, let attributedText else { return nil } + guard let text = text, let attributedText, let abstractContainer = abstractTextContainer() else { return nil } + let textContainer = abstractContainer.textContainer + let layoutManager = abstractContainer.layoutManager + let actionText = accessibleText ?? (text.isValid(range: range) ? NSString(string:text).substring(with: range) : text) - // Calculate the frame of the substring - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - var glyphRange = NSRange() // Convert the range for the substring into a range of glyphs layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange) - let substringBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) // Create custom accessibility element @@ -520,11 +495,8 @@ open class Label: UILabel, ViewProtocol, UserInfoable { accessibilityElements?.append(element) return element } + - - //-------------------------------------------------- - // MARK: - Accessibility - //-------------------------------------------------- open var accessibilityAction: ((Label) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/Extensions/UITapGestureRecognizer.swift b/VDS/Extensions/UITapGestureRecognizer.swift index 612a11ca..4461ae06 100644 --- a/VDS/Extensions/UITapGestureRecognizer.swift +++ b/VDS/Extensions/UITapGestureRecognizer.swift @@ -12,23 +12,11 @@ extension UITapGestureRecognizer { /// Determines if the touch event has a action attribute within the range given /// - Parameters: - /// - label: UILabel in question + /// - label: Label in question /// - targetRange: Range to look within /// - Returns: Wether the range in the label has an action - public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool { - - guard let attributedText = label.attributedText else { return false } - - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: label.bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - - let location = location(in: label) - let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - - guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false } - return true + public func didTapActionInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool { + let tapLocation = location(in: label) + return label.isAction(for: tapLocation, inRange: targetRange) } }