Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add long press gesture recognizer & textInserts #78

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Example/Nantes/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
}

35 changes: 35 additions & 0 deletions Source/Classes/ActionHandling/Actions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
2 changes: 2 additions & 0 deletions Source/Classes/ActionHandling/Link.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down
14 changes: 14 additions & 0 deletions Source/Classes/Delegate/LabelDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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]) { }
}
8 changes: 5 additions & 3 deletions Source/Classes/Drawing/Drawing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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)
Expand Down
39 changes: 33 additions & 6 deletions Source/Classes/NantesLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] = [:]
Expand Down Expand Up @@ -248,19 +253,24 @@ 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 {
return action == #selector(copy(_:))
}

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 {
Expand All @@ -270,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
}
Expand Down Expand Up @@ -311,4 +321,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
}
}
5 changes: 3 additions & 2 deletions Source/Classes/Sizing/Sizing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down