From 31b9704163e64fd444759d51630254df0c1271ab Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 12:20:39 -0500 Subject: [PATCH] first attempt to get link clicks working again Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 85 ++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 15ed4b45..c622b694 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -411,20 +411,85 @@ open class Label: UILabel, ViewProtocol, UserInfoable { private func didTapActionInLabel(_ location: CGPoint, inRange targetRange: NSRange) -> Bool { - guard let attributedText else { return false } +// 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 intrinsicWidth = intrinsicContentSize.width + + // Assert that tapped occured within acceptable bounds based on alignment. + switch textAlignment { + case .right: + if tapLocation.x < bounds.width - intrinsicWidth { + return false + } + case .center: + let halfBounds = 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) + } + + /** + 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)? { + + // Must configure the attributed string to translate what would appear on screen to accurately analyze. + guard let attributedText = attributedText else { return nil } + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + + let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + + let textStorage = NSTextStorage(attributedString: stagedAttributedString) let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) + let textContainer = NSTextContainer(size: .zero) + 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 + + textContainer.lineFragmentPadding = 0.0 + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines + textContainer.size = bounds.size + + return (textContainer, layoutManager, textStorage) } - - + private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { guard let text = text, let attributedText else { return nil }