From 7072553b52a4aacbedb3dc63a781f666098f7ea0 Mon Sep 17 00:00:00 2001 From: Jayden Liu Date: Tue, 25 May 2021 11:30:44 +0800 Subject: [PATCH 1/3] Add long press gesture recognizer --- Example/Nantes/ViewController.swift | 4 +++ Source/Classes/ActionHandling/Actions.swift | 35 +++++++++++++++++++++ Source/Classes/ActionHandling/Link.swift | 2 ++ Source/Classes/Delegate/LabelDelegate.swift | 14 +++++++++ Source/Classes/NantesLabel.swift | 21 +++++++++++++ 5 files changed, 76 insertions(+) diff --git a/Example/Nantes/ViewController.swift b/Example/Nantes/ViewController.swift index 786120d..460711c 100644 --- a/Example/Nantes/ViewController.swift +++ b/Example/Nantes/ViewController.swift @@ -279,5 +279,9 @@ extension ViewController: NantesLabelDelegate { func attributedLabel(_ label: NantesLabel, didSelectTransitInfo transitInfo: [NSTextCheckingKey: String]) { print("Tapped transit info: \(transitInfo)") } + + func attributedLabel(_ label: NantesLabel, didLongPressLink link: URL) { + print("Long press link: \(link)") + } } diff --git a/Source/Classes/ActionHandling/Actions.swift b/Source/Classes/ActionHandling/Actions.swift index 3192523..c826bdf 100644 --- a/Source/Classes/ActionHandling/Actions.swift +++ b/Source/Classes/ActionHandling/Actions.swift @@ -107,4 +107,39 @@ extension NantesLabel { delegate.attributedLabel(self, didSelectTextCheckingResult: result) } } + + func handleLinkLongPress(_ link: NantesLabel.Link) { + if let linkLongPressBlock = link.linkLongPressBlock { + linkLongPressBlock(self, link) + return + } + guard let result = link.result, let delegate = delegate else { + return + } + + switch result.resultType { + case .address: + if let address = result.addressComponents { + delegate.attributedLabel(self, didLongPressAddress: address) + } + case .date: + if let date = result.date { + delegate.attributedLabel(self, didLongPressDate: date, timeZone: result.timeZone ?? TimeZone.current, duration: result.duration) + } + case .link: + if let url = result.url { + delegate.attributedLabel(self, didLongPressLink: url) + } + case .phoneNumber: + if let phoneNumber = result.phoneNumber { + delegate.attributedLabel(self, didLongPressPhoneNumber: phoneNumber) + } + case .transitInformation: + if let transitInfo = result.components { + delegate.attributedLabel(self, didLongPressTransitInfo: transitInfo) + } + default: // fallback to result if we aren't sure + delegate.attributedLabel(self, didLongPressTextCheckingResult: result) + } + } } diff --git a/Source/Classes/ActionHandling/Link.swift b/Source/Classes/ActionHandling/Link.swift index 2514db0..924554e 100644 --- a/Source/Classes/ActionHandling/Link.swift +++ b/Source/Classes/ActionHandling/Link.swift @@ -10,12 +10,14 @@ import UIKit extension NantesLabel { public typealias LinkTappedBlock = ((NantesLabel, NantesLabel.Link) -> Void) + public typealias LinkLongPressBlock = ((NantesLabel, NantesLabel.Link) -> Void) public struct Link: Equatable { public var attributes: [NSAttributedString.Key: Any] public var activeAttributes: [NSAttributedString.Key: Any] public var inactiveAttributes: [NSAttributedString.Key: Any] public var linkTappedBlock: NantesLabel.LinkTappedBlock? + public var linkLongPressBlock: NantesLabel.LinkLongPressBlock? public var result: NSTextCheckingResult? public var text: String? diff --git a/Source/Classes/Delegate/LabelDelegate.swift b/Source/Classes/Delegate/LabelDelegate.swift index 4e6ec91..64526f0 100644 --- a/Source/Classes/Delegate/LabelDelegate.swift +++ b/Source/Classes/Delegate/LabelDelegate.swift @@ -15,6 +15,13 @@ public protocol NantesLabelDelegate: class { func attributedLabel(_ label: NantesLabel, didSelectPhoneNumber phoneNumber: String) func attributedLabel(_ label: NantesLabel, didSelectTextCheckingResult result: NSTextCheckingResult) func attributedLabel(_ label: NantesLabel, didSelectTransitInfo transitInfo: [NSTextCheckingKey: String]) + + func attributedLabel(_ label: NantesLabel, didLongPressAddress addressComponents: [NSTextCheckingKey: String]) + func attributedLabel(_ label: NantesLabel, didLongPressDate date: Date, timeZone: TimeZone, duration: TimeInterval) + func attributedLabel(_ label: NantesLabel, didLongPressLink link: URL) + func attributedLabel(_ label: NantesLabel, didLongPressPhoneNumber phoneNumber: String) + func attributedLabel(_ label: NantesLabel, didLongPressTextCheckingResult result: NSTextCheckingResult) + func attributedLabel(_ label: NantesLabel, didLongPressTransitInfo transitInfo: [NSTextCheckingKey: String]) } public extension NantesLabelDelegate { @@ -24,4 +31,11 @@ public extension NantesLabelDelegate { func attributedLabel(_ label: NantesLabel, didSelectPhoneNumber phoneNumber: String) { } func attributedLabel(_ label: NantesLabel, didSelectTextCheckingResult result: NSTextCheckingResult) { } func attributedLabel(_ label: NantesLabel, didSelectTransitInfo transitInfo: [NSTextCheckingKey: String]) { } + + func attributedLabel(_ label: NantesLabel, didLongPressAddress addressComponents: [NSTextCheckingKey: String]) { } + func attributedLabel(_ label: NantesLabel, didLongPressDate date: Date, timeZone: TimeZone, duration: TimeInterval) { } + func attributedLabel(_ label: NantesLabel, didLongPressLink link: URL) { } + func attributedLabel(_ label: NantesLabel, didLongPressPhoneNumber phoneNumber: String) { } + func attributedLabel(_ label: NantesLabel, didLongPressTextCheckingResult result: NSTextCheckingResult) { } + func attributedLabel(_ label: NantesLabel, didLongPressTransitInfo transitInfo: [NSTextCheckingKey: String]) { } } diff --git a/Source/Classes/NantesLabel.swift b/Source/Classes/NantesLabel.swift index 8309287..124794c 100644 --- a/Source/Classes/NantesLabel.swift +++ b/Source/Classes/NantesLabel.swift @@ -248,6 +248,10 @@ import UIKit private func commonInit() { isUserInteractionEnabled = true enabledTextCheckingTypes = [.link, .address, .phoneNumber] + + let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureDidFire(sender:))) + longPressGestureRecognizer.delegate = self + self.addGestureRecognizer(longPressGestureRecognizer) } override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { @@ -311,4 +315,21 @@ import UIKit attributedText = mutableAttributedString } + + @objc private func longPressGestureDidFire(sender: UILongPressGestureRecognizer) { + guard sender.state == .began else { + return + } + let touchPoint = sender.location(in: self) + guard let link = link(at: touchPoint) else { + return + } + handleLinkLongPress(link) + } +} + +extension NantesLabel: UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { + return link(at: touch.location(in: self)) != nil + } } From 5bb14b60c7e5d25ee1db2c7a03b9cd9bc9c6ccd1 Mon Sep 17 00:00:00 2001 From: Jayden Liu Date: Tue, 25 May 2021 18:15:37 +0800 Subject: [PATCH 2/3] Add textInserts --- Source/Classes/Drawing/Drawing.swift | 8 +++++--- Source/Classes/NantesLabel.swift | 18 ++++++++++++------ Source/Classes/Sizing/Sizing.swift | 5 +++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Source/Classes/Drawing/Drawing.swift b/Source/Classes/Drawing/Drawing.swift index cf224e0..e8ea8d0 100644 --- a/Source/Classes/Drawing/Drawing.swift +++ b/Source/Classes/Drawing/Drawing.swift @@ -18,8 +18,10 @@ extension NantesLabel { } override open func drawText(in rect: CGRect) { + let insetRect = rect.inset(by: self.textInsets) + guard var attributedText = attributedText else { - super.drawText(in: rect) + super.drawText(in: insetRect) return } @@ -44,14 +46,14 @@ extension NantesLabel { context.saveGState() context.textMatrix = .identity - context.translateBy(x: 0.0, y: rect.size.height) + context.translateBy(x: 0.0, y: insetRect.size.height) // invert context to match iOS coordinates, otherwise we'll draw upside down context.scaleBy(x: 1.0, y: -1.0) let textRange = CFRangeMake(0, attributedText.length) let limitedRect = textRect(forBounds: rect, limitedToNumberOfLines: numberOfLines) - context.translateBy(x: rect.origin.x, y: rect.size.height - limitedRect.origin.y - limitedRect.size.height) + context.translateBy(x: insetRect.origin.x, y: insetRect.size.height - limitedRect.origin.y - limitedRect.size.height) if let shadowColor = shadowColor, !isHighlighted { context.setShadow(offset: shadowOffset, blur: shadowRadius, color: shadowColor.cgColor) diff --git a/Source/Classes/NantesLabel.swift b/Source/Classes/NantesLabel.swift index 124794c..ebb9e95 100644 --- a/Source/Classes/NantesLabel.swift +++ b/Source/Classes/NantesLabel.swift @@ -99,6 +99,11 @@ import UIKit /// defaults to .center open var verticalAlignment: NantesLabel.VerticalAlignment = .center + /// The distance, in points, from the margin to the text container. This value is `UIEdgeInsets.zero` by default. + /// sizeThatFits: will have its returned size increased by these margins. + /// drawTextInRect: will inset all drawn text by these margins. + @IBInspectable open var textInsets: UIEdgeInsets = UIEdgeInsets.zero + // MARK: - Private vars static private var dataDetectorsByType: [UInt64: NSDataDetector] = [:] @@ -259,12 +264,13 @@ import UIKit } override open func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { + let innerBounds = bounds.inset(by: self.textInsets) guard let attributedText = attributedText, let framesetter = framesetter else { - return super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) + return super.textRect(forBounds: innerBounds, limitedToNumberOfLines: numberOfLines) } - var textRect = bounds + var textRect = innerBounds var maxLineHeight: CGFloat = -1.0 attributedText.enumerateAttribute(.font, in: NSRange(location: 0, length: attributedText.length), options: [], using: { value, _, _ in guard let font = value as? UIFont else { @@ -274,18 +280,18 @@ import UIKit maxLineHeight = max(maxLineHeight, font.lineHeight) }) maxLineHeight = maxLineHeight == -1.0 ? font.lineHeight : maxLineHeight - textRect.size.height = max(maxLineHeight * CGFloat(max(2, numberOfLines)), bounds.height) + textRect.size.height = max(maxLineHeight * CGFloat(max(2, numberOfLines)), innerBounds.height) var textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRange(location: 0, length: attributedText.length), nil, textRect.size, nil) textSize = CGSize(width: ceil(textSize.width), height: ceil(textSize.height)) - if textSize.height < bounds.height { + if textSize.height < innerBounds.height { var yOffset: CGFloat = 0.0 switch verticalAlignment { case .center: - yOffset = floor((bounds.height - textSize.height) / 2.0) + yOffset = floor((innerBounds.height - textSize.height) / 2.0) case .bottom: - yOffset = bounds.height - textSize.height + yOffset = innerBounds.height - textSize.height case .top: break } diff --git a/Source/Classes/Sizing/Sizing.swift b/Source/Classes/Sizing/Sizing.swift index f63fd31..ede2cf7 100644 --- a/Source/Classes/Sizing/Sizing.swift +++ b/Source/Classes/Sizing/Sizing.swift @@ -19,8 +19,9 @@ extension NantesLabel { return super.sizeThatFits(size) } - let labelSize = NantesLabel.suggestFrameSize(for: string, framesetter: framesetter, withSize: size, numberOfLines: numberOfLines) - // add textInsets? + var labelSize = NantesLabel.suggestFrameSize(for: string, framesetter: framesetter, withSize: size, numberOfLines: numberOfLines) + labelSize.width += textInsets.left + textInsets.right + labelSize.height += textInsets.top + textInsets.bottom return labelSize } From e675664d52bab35dc9019010bae3d3cc71f5ab34 Mon Sep 17 00:00:00 2001 From: Jayden Liu Date: Wed, 26 May 2021 08:54:53 +0800 Subject: [PATCH 3/3] Fix lint warning --- Source/Classes/ActionHandling/Actions.swift | 4 ++-- Source/Classes/Delegate/LabelDelegate.swift | 4 ++-- Source/Classes/Drawing/Drawing.swift | 2 +- Source/Classes/NantesLabel.swift | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Source/Classes/ActionHandling/Actions.swift b/Source/Classes/ActionHandling/Actions.swift index c826bdf..b79c984 100644 --- a/Source/Classes/ActionHandling/Actions.swift +++ b/Source/Classes/ActionHandling/Actions.swift @@ -107,7 +107,7 @@ extension NantesLabel { delegate.attributedLabel(self, didSelectTextCheckingResult: result) } } - + func handleLinkLongPress(_ link: NantesLabel.Link) { if let linkLongPressBlock = link.linkLongPressBlock { linkLongPressBlock(self, link) @@ -116,7 +116,7 @@ extension NantesLabel { guard let result = link.result, let delegate = delegate else { return } - + switch result.resultType { case .address: if let address = result.addressComponents { diff --git a/Source/Classes/Delegate/LabelDelegate.swift b/Source/Classes/Delegate/LabelDelegate.swift index 64526f0..2a56ad4 100644 --- a/Source/Classes/Delegate/LabelDelegate.swift +++ b/Source/Classes/Delegate/LabelDelegate.swift @@ -15,7 +15,7 @@ public protocol NantesLabelDelegate: class { func attributedLabel(_ label: NantesLabel, didSelectPhoneNumber phoneNumber: String) func attributedLabel(_ label: NantesLabel, didSelectTextCheckingResult result: NSTextCheckingResult) func attributedLabel(_ label: NantesLabel, didSelectTransitInfo transitInfo: [NSTextCheckingKey: String]) - + func attributedLabel(_ label: NantesLabel, didLongPressAddress addressComponents: [NSTextCheckingKey: String]) func attributedLabel(_ label: NantesLabel, didLongPressDate date: Date, timeZone: TimeZone, duration: TimeInterval) func attributedLabel(_ label: NantesLabel, didLongPressLink link: URL) @@ -31,7 +31,7 @@ public extension NantesLabelDelegate { func attributedLabel(_ label: NantesLabel, didSelectPhoneNumber phoneNumber: String) { } func attributedLabel(_ label: NantesLabel, didSelectTextCheckingResult result: NSTextCheckingResult) { } func attributedLabel(_ label: NantesLabel, didSelectTransitInfo transitInfo: [NSTextCheckingKey: String]) { } - + func attributedLabel(_ label: NantesLabel, didLongPressAddress addressComponents: [NSTextCheckingKey: String]) { } func attributedLabel(_ label: NantesLabel, didLongPressDate date: Date, timeZone: TimeZone, duration: TimeInterval) { } func attributedLabel(_ label: NantesLabel, didLongPressLink link: URL) { } diff --git a/Source/Classes/Drawing/Drawing.swift b/Source/Classes/Drawing/Drawing.swift index e8ea8d0..1480f72 100644 --- a/Source/Classes/Drawing/Drawing.swift +++ b/Source/Classes/Drawing/Drawing.swift @@ -19,7 +19,7 @@ extension NantesLabel { override open func drawText(in rect: CGRect) { let insetRect = rect.inset(by: self.textInsets) - + guard var attributedText = attributedText else { super.drawText(in: insetRect) return diff --git a/Source/Classes/NantesLabel.swift b/Source/Classes/NantesLabel.swift index ebb9e95..5f5fc73 100644 --- a/Source/Classes/NantesLabel.swift +++ b/Source/Classes/NantesLabel.swift @@ -103,7 +103,7 @@ import UIKit /// sizeThatFits: will have its returned size increased by these margins. /// drawTextInRect: will inset all drawn text by these margins. @IBInspectable open var textInsets: UIEdgeInsets = UIEdgeInsets.zero - + // MARK: - Private vars static private var dataDetectorsByType: [UInt64: NSDataDetector] = [:] @@ -253,7 +253,7 @@ import UIKit private func commonInit() { isUserInteractionEnabled = true enabledTextCheckingTypes = [.link, .address, .phoneNumber] - + let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureDidFire(sender:))) longPressGestureRecognizer.delegate = self self.addGestureRecognizer(longPressGestureRecognizer) @@ -321,7 +321,7 @@ import UIKit attributedText = mutableAttributedString } - + @objc private func longPressGestureDidFire(sender: UILongPressGestureRecognizer) { guard sender.state == .began else { return