Edit
See my answer for a full working solution:
I managed to solve this myself by using a UITextView
instead of a UILabel
. I wrote a class that makes the UITextView
behave like a UILabel
but with fully accurate link detection.
I have managed to style the links without problem using NSMutableAttributedString
but I am unable to accurately detect which character has been clicked. I have tried all the solutions in this question (that I could convert to Swift 4 code) but with no luck.
The following code works but fails to accurately detect which character has been clicked and gets the wrong location of the link:
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: label.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 = label.bounds.size
textContainer.size = labelSize
// Find the tapped character location and compare it to the specified range
let locationOfTouchInLabel = self.location(in: label)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = 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(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
print(indexOfCharacter)
return NSLocationInRange(indexOfCharacter, targetRange)
}
If you don't mind rewriting you code, you should use
UITextView
instead ofUILabel
.You can easily detect the link by setting
UITextView
'sdataDetectorTypes
and implement the delegate function to retrieve your clicked urls.https://developer.apple.com/documentation/uikit/uitextviewdelegate/1649337-textview
I wanted to avoid posting an answer since it's more a comment on Dan Bray's own answer (can't comment due to lack of rep). However, I still think it's worth sharing.
I made some small (what I think are) improvements to Dan Bray's answer for convenience:
textLink
dict which stores the link strings and their respective targets. The implementing viewController only needs to set this to initialize the textView.delegate
tolinkDelegate
since UITextViews do have a delegate already.The TextView:
The delegate:
The implementing viewController:
And last but not least a big thank you to Dan Bray, who's solution this is after all!
If you need a subclass of
Label
, solution may be something like one prepared in a playground (of cause some points should be optimized because this is just a draft):demo:
You can use MLLabel library. MLLabel is a subclass of UIlabel. The library has a class MLLinkLabel that is subclass of MLLabel. That means you can use it in place of UIlabel (even in interface builder just drag a UILabel and change it's class to MLLinkLabel)
MLLinkLabel can do the trick for you and it is very easy. Here is an example:
you can check the library in GitHub https://github.com/molon/MLLabel
Here is a screenshot from one of my apps that I used MLLabel in it.
I managed to solve this by using a
UITextView
instead of aUILabel
. I originally, didn't want to use aUITextView
because I need the element to behave like aUILabel
and aUITextView
can cause issues with scrolling and it's intended use, is to be editable text. The following class I wrote makes aUITextView
behave like aUILabel
but with fully accurate click detection and no scrolling issues:The function
hitTest
get's called multiple times but that never causes a problem, asclickedLink()
will only ever get called once per click. I tried disablingisUserInteractionEnabled
for different views but didn't that didn't help and was unnecessary.To use the class, simply add it to your
UITextView
. If you're usingautoLayout
in the Xcode editor, then disableScrolling Enabled
for theUITextView
in the editor to avoid layout warnings.In the
Swift
file that contains the code to go with yourxib
file (in my case a class for aUITableViewCell
, you need to set the following variables for your clickable textView:ranges
- the start and end index of every clickable link with theUITextView
page
- aString
to identify the page or view that contains the theUITextView
paragraph
- If you have multiple clickableUITextView
, assign each one with an numberdelegate
- to delegate the click events to where ever you are able to process them.You then need to create a protocol for your
delegate
:The variables passed into
clickedLink
give you all the information you need to know which link has been clicked.