Created
March 20, 2024 01:27
-
-
Save kylehowells/fd1ef0d547722820ed0483d1a928e095 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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