Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kylehowells/fd1ef0d547722820ed0483d1a928e095 to your computer and use it in GitHub Desktop.
Save kylehowells/fd1ef0d547722820ed0483d1a928e095 to your computer and use it in GitHub Desktop.
//
// TappableUILabelDemoViewController.swift
// Label Link Test
//
// Created by Kyle Howells on 2024-03-20.
//
import UIKit
// MARK: - TappableUILabelDemoViewController
class TappableUILabelDemoViewController: UIViewController {
// MARK: - Setup View
override func loadView() {
self.view = MainView()
}
var _view: MainView {
return self.view as! MainView
}
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(self.tappedOnLabel(tapGesture: )))
self._view.label.addGestureRecognizer(tapGesture)
}
// MARK: - Tap Label
@objc func tappedOnLabel(tapGesture: UITapGestureRecognizer) {
guard tapGesture.state == .recognized else { return }
print("tapGesture.state: \(tapGesture.state)")
let locationOfTouchInLabel: CGPoint = tapGesture.location(in: self._view.label)
print("self.tapLabel( locationOfTouchInLabel: \(locationOfTouchInLabel) )")
let label: UILabel = self._view.label
let attributedText: NSAttributedString = label.attributedText!
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager: NSLayoutManager = NSLayoutManager()
let textContainer: NSTextContainer = NSTextContainer(size: CGSize.zero)
let textStorage: NSTextStorage = NSTextStorage(attributedString: attributedText)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize: CGSize = label.bounds.size
textContainer.size = labelSize
let textBoundingBox: CGRect = layoutManager.usedRect(for: textContainer)
// - NSTextStorage
let textContainerOffset: CGPoint = CGPoint(
x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y
)
let locationOfTouchInTextContainer: CGPoint = CGPoint(
x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y
)
let indexOfCharacter: Int = layoutManager.characterIndex(
for: locationOfTouchInTextContainer,
in: textContainer,
fractionOfDistanceBetweenInsertionPoints: nil
)
let attachment = attributedText.attribute(.attachment, at: indexOfCharacter, effectiveRange: nil)
print("attachment: \(attachment) - \( type(of: attachment) )")
if let textLink: String = attachment as? String,
let url: URL = URL(string: textLink)
{
UIApplication.shared.open(url)
}
}
}
// MARK: - View
class MainView: UIView {
// MARK: - Views
let label: UILabel = {
let label = UILabel()
label.isUserInteractionEnabled = true
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = 0
label.textColor = UIColor(white: 0, alpha: 1)
label.tintColor = UIColor.red
label.textAlignment = .center
let fontSize: CGFloat = 20
label.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
// - Attributes
let attributes: [NSAttributedString.Key : Any] = [
.font : UIFont.systemFont(ofSize: fontSize, weight: .regular),
.foregroundColor: UIColor(white: 0, alpha: 0.6)
]
let linkAttributes: [NSAttributedString.Key : Any] = [
.font : UIFont.systemFont(ofSize: fontSize, weight: .medium),
.foregroundColor: UIColor(white: 0, alpha: 1),
.attachment: "https://google.com"
]
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: "", attributes: attributes)
attributedString.append(NSAttributedString(
string: "Hello",
attributes: attributes
))
attributedString.append(NSAttributedString(
string: " World ",
attributes: linkAttributes
))
attributedString.append(NSAttributedString(
string: "Testing",
attributes: attributes
))
label.attributedText = attributedString
label.layer.borderWidth = 1
label.layer.borderColor = UIColor(white: 0, alpha: 1).cgColor
return label
}()
// MARK: - Setup
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
func commonInit() {
self.backgroundColor = UIColor(white: 1, alpha: 1)
self.addSubview(self.label)
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
let size = self.bounds.size
let safeArea = self.safeAreaInsets
let contentBounds = self.bounds.inset(by: safeArea)
self.label.frame = {
//let labelSize = self.label.sizeThatFits(size)
var frame = CGRect()
frame.size.width = size.width
frame.size.height = contentBounds.height * 0.5
frame.origin.y = contentBounds.minY
return frame
}()
}
}
// MARK: - Swift Preview
#if DEBUG
// Not meant to be touched. Updates itself because of the binding
import SwiftUI
struct TappableUILabelDemoViewController_Preview: PreviewProvider {
static var previews: some View {
return Wrapper(noOp: Binding.constant("no-op"))
.edgesIgnoringSafeArea(.all)
.previewDisplayName("TappableUILabelDemoViewController")
}
}
// Could probably use a generic, for easier reuse
fileprivate struct Wrapper: UIViewControllerRepresentable {
@Binding var noOp: String // no-op -> binding just to trigger updateUIView
func makeUIViewController(context: Context) -> UIViewController {
return TappableUILabelDemoViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
uiViewController.view.setNeedsLayout()
uiViewController.view.layoutIfNeeded()
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment