Swift: нажмите на часть текста UILabel
У меня проблема, что "boundingRectForGlyphRange" всегда возвращает CGRect.zero "0,0, 0,0, 0,0, 0,0". "boundingRectForGlyphRange" не работает. Например, я пишу код для прикосновения к части текста функции UILabel. Мой текст имеет первую часть "любой текст", а вторая "ЧИТАТЬ БОЛЬШЕ". Я хочу, чтобы распознаватель касаний работал только тогда, когда я нажимаю "ЧИТАТЬ БОЛЬШЕ". Если я коснусь какой-либо точки на UILabel, "CGRectContainsPoint" всегда вернет true, тогда действие называется
Вот мой код:
override func viewDidLoad() {
super.viewDidLoad()
// The full string
let firstPart:NSMutableAttributedString = NSMutableAttributedString(string: "Lorem ipsum dolor set amit ", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(13)])
firstPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
range: NSRange(location: 0, length: firstPart.length))
info.appendAttributedString(firstPart)
// The "Read More" string that should be touchable
let secondPart:NSMutableAttributedString = NSMutableAttributedString(string: "READ MORE", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(14)])
secondPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
range: NSRange(location: 0, length: secondPart.length))
info.appendAttributedString(secondPart)
lblTest.attributedText = info
// Store range of chars we want to detect touches for
moreStringRange = NSMakeRange(firstPart.length, secondPart.length)
print("moreStringRange\(moreStringRange)")
tapRec.addTarget(self, action: "didTap:")
lblTest.addGestureRecognizer(tapRec)
}
func didTap(sender:AnyObject) {
// Storage class stores the string, obviously
let textStorage:NSTextStorage = NSTextStorage(attributedString: info)
// The storage class owns a layout manager
let layoutManager:NSLayoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
// Layout manager owns a container which basically
// defines the bounds the text should be contained in
let textContainer:NSTextContainer = NSTextContainer(size: lblTest.frame.size)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lblTest.lineBreakMode
// Begin computation of actual frame
// Glyph is the final display representation
var glyphRange = NSRange()
// Extract the glyph range
layoutManager.characterRangeForGlyphRange(moreStringRange!, actualGlyphRange: &glyphRange)
// Compute the rect of glyph in the text container
print("glyphRange\(glyphRange)")
print("textContainer\(textContainer)")
let glyphRect:CGRect = layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)
// Final rect relative to the textLabel.
print("\(glyphRect)")
// Now figure out if the touch point is inside our rect
let touchPoint:CGPoint = tapRec.locationOfTouch(0, inView: lblTest)
if CGRectContainsPoint(glyphRect, touchPoint) {
print("User tapped on Read More. So show something more")
}
}
Это просто демонстрация для проверки того, что я хочу сделать:
https://i.gyazo.com/a612d5f1d36089d5387083ec5bc20214.png
Любая помощь будет принята с благодарностью.
9 ответов
Swift 4.2
Пожалуйста, найдите решение здесь для получения конкретного текста action
из Label
,
1) Ярлык декларации
@IBOutlet weak var lblTerms: UILabel!
2) Установить атрибутивный текст на этикетке
let text = "Please agree for Terms & Conditions."
lblTerms.text = text
self.lblTerms.textColor = UIColor.white
let underlineAttriString = NSMutableAttributedString(string: text)
let range1 = (text as NSString).range(of: "Terms & Conditions.")
underlineAttriString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range1)
underlineAttriString.addAttribute(NSAttributedString.Key.font, value: UIFont.init(name: Theme.Font.Regular, size: Theme.Font.size.lblSize)!, range: range1)
underlineAttriString.addAttribute(NSAttributedString.Key.foregroundColor, value: Theme.color.primaryGreen, range: range1)
lblTerms.attributedText = underlineAttriString
lblTerms.isUserInteractionEnabled = true
lblTerms.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))
Это похоже на изображение выше.
3) Добавьте метод действия tapLable к контроллеру
@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
let termsRange = (text as NSString).range(of: "Terms & Conditions")
// comment for now
//let privacyRange = (text as NSString).range(of: "Privacy Policy")
if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: termsRange) {
print("Tapped terms")
} else if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: privacyRange) {
print("Tapped privacy")
} else {
print("Tapped none")
}
}
4) Добавить UITapGestureRecognizer
расширение
extension UITapGestureRecognizer {
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 = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
//(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
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 = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
// locationOfTouchInLabel.y - textContainerOffset.y);
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
Удачи!:-)
После нескольких проблем с такими вещами, использования множества разных библиотек и т. Д. Я нашел интересное решение: http://samwize.com/2016/03/04/how-to-create-multiple-tappable-links-in-a-uilabel/
Он собирается расширить UITapGestureRegonizer и определить, находится ли нажатие в диапазоне строки при запуске.
Вот обновленная версия Swift 4 этого расширения:
extension UITapGestureRecognizer {
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)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
Чтобы упростить преобразование диапазона, вам также нужно это расширение диапазона
extension Range where Bound == String.Index {
var nsRange:NSRange {
return NSRange(location: self.lowerBound.encodedOffset,
length: self.upperBound.encodedOffset -
self.lowerBound.encodedOffset)
}
}
Если у вас есть это расширение, вы можете добавить жест метки к метке:
let tap = UITapGestureRecognizer(target: self, action: #selector(tapLabel(tap:)))
self.yourLabel.addGestureRecognizer(tap)
self.yourLabel.isUserInteractionEnabled = true
Вот функция для управления краном:
@objc func tapLabel(tap: UITapGestureRecognizer) {
guard let range = self.yourLabel.text?.range(of: "Substring to detect")?.nsRange else {
return
}
if tap.didTapAttributedTextInLabel(label: self.yourLabel, inRange: range) {
// Substring tapped
}
}
Чтобы включить многострочное нажатие и не хотите создавать подкласс UILabel, тогда:
- Запись функции расширения для UITapGestureRecognizer
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
guard let attributedText = label.attributedText else { return false }
let mutableStr = NSMutableAttributedString.init(attributedString: attributedText)
mutableStr.addAttributes([NSAttributedString.Key.font : label.font!], range: NSRange.init(location: 0, length: attributedText.length))
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: mutableStr)
// 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)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
- Настройте свой UILable
label.text = "For any type of query please call us on +9186XXX-XXXXX or mail us at example@yourdomain.com"
label.isUserInteractionEnabled = true
label.lineBreakMode = .byWordWrapping
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(tappedOnLabel(_:)))
tapGesture.numberOfTouchesRequired = 1
label.addGestureRecognizer(tapGesture)
- Добавьте функцию селектора распознавателя жестов:
@objc func tappedOnLabel(_ gesture: UITapGestureRecognizer) {
guard let text = label.text else { return }
let numberRange = (text as NSString).range(of: "+9186XXX-XXXXX")
let emailRange = (text as NSString).range(of: "example@yourdomain.com")
if gesture.didTapAttributedTextInLabel(label: self.label, inRange: numberRange) {
print("number tapped")
} else if gesture.didTapAttributedTextInLabel(label: self.label, inRange: emailRange) {
print("Email tapped")
}
}
Ты можешь использовать NSMutableAttributedString
и UITextView
, UITextView имеет метод делегата func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
, Как только вы установите часть строки, которую вы хотите сделать доступной, метод делегата активирует ее.
11 шагов перечислены ниже в комментариях над каждым фрагментом кода.
// 1st **BE SURE TO INCLUDE** UITextViewDelegate to the view controller's class
class VewController: UIViewController, UITextViewDelegate {
// 2nd use a programmatic textView or use the textView from your storyboard
let yourTextView: UITextView = {
let textView = UITextView()
textView.textAlignment = .center
textView.isEditable = false
textView.showsVerticalScrollIndicator = false
return textView
}()
override func viewDidLoad() {
super.viewDidLoad()
// 3rd in viewDidLoad set the textView's delegate
yourTextView.delegate = self
// 4th create the first piece of the string you don't want to be tappable
let regularText = NSMutableAttributedString(string: "any text ", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17), NSAttributedStringKey.foregroundColor: UIColor.black])
// 5th create the second part of the string that you do want to be tappable. I used a blue color just so it can stand out.
let tappableText = NSMutableAttributedString(string: "READ MORE")
tappableText.addAttribute(NSAttributedStringKey.font, value: UIFont.systemFont(ofSize: 17), range: NSMakeRange(0, tappableText.length))
tappableText.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))
// 6th this ISN'T NECESSARY but this is how you add an underline to the tappable part. I also used a blue color so it can match the tappableText and used the value of 1 for the height. The length of the underline is based on the tappableText's length using NSMakeRange(0, tappableText.length)
tappableText.addAttribute(NSAttributedStringKey.underlineStyle, value: 1, range: NSMakeRange(0, tappableText.length))
tappableText.addAttribute(NSAttributedStringKey.underlineColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))
// 7th this is the important part that connects the tappable link to the delegate method in step 11
// use NSAttributedStringKey.link and the value "makeMeTappable" to link the NSAttributedStringKey.link to the method. FYI "makeMeTappable" is a name I choose for clarity, you can use anything like "anythingYouCanThinkOf"
tappableText.addAttribute(NSAttributedStringKey.link, value: "makeMeTappable", range: NSMakeRange(0, tappableText.length))
// 8th *** important append the tappableText to the regularText ***
regularText.append(tappableText)
// 9th set the regularText to the textView's attributedText property
yourTextView.attributedText = regularText
}
// 10th add the textView's delegate method that activates urls. Make sure to return false for the tappable part
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
// 11th use the value from the 7th step to trigger the url inside this method
if URL.absoluteString == "makeMeTappable"{
// in this situation I'm using the tappableText to present a view controller but it can be used for whatever you trying to do
let someVC = SomeController()
let navVC = UINavigationController(rootViewController: someVC)
present(navVC, animated: true, completion: nil)
return false // return false for this to work
}
return true
}
}
Для многострочных меток вы должны установить шрифт textStorage, иначе будет возвращен неверный диапазон
guard let attributedString = self.attributedText else { return }
let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))
let textStorage = NSTextStorage(attributedString: mutableAttribString)
Есть много ответов на этот вопрос. Тем не менее, многие люди жалуются на то, что касание не выполняется для многострочных меток, и это правильно для большинства ответов на этой странице. Неправильный диапазон для крана возвращается, потому что textStorage
не имеет правильный шрифт.
let textStorage = NSTextStorage(attributedString: label.attributedText!)
Вы можете быстро это исправить, добавив правильный шрифт textStorage
пример:
guard let attributedString = self.attributedText else { return -1 }
let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))
let textStorage = NSTextStorage(attributedString: mutableAttribString)
Собрав все воедино, вы получите что-то вроде этого:
protocol AtMentionsLabelTapDelegate: class {
func labelWasTappedForUsername(_ username: String)
}
class AtMentionsLabel: UILabel {
private var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer()
weak var tapDelegate: AtMentionsLabelTapDelegate?
var mentions: [String] = [] // usernames to style
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
isUserInteractionEnabled = true
lineBreakMode = .byWordWrapping
tapGesture = UITapGestureRecognizer()
tapGesture.addTarget(self, action: #selector(handleLabelTap(recognizer:)))
tapGesture.numberOfTapsRequired = 1
tapGesture.isEnabled = true
addGestureRecognizer(tapGesture)
}
@objc func handleLabelTap(recognizer: UITapGestureRecognizer) {
let tapLocation = recognizer.location(in: self)
let tapIndex = indexOfAttributedTextCharacterAtPoint(point: tapLocation)
for username in mentions {
if let ranges = self.attributedText?.rangesOf(subString: username) {
for range in ranges {
if tapIndex > range.location && tapIndex < range.location + range.length {
tapDelegate?.labelWasTappedForUsername(username)
return
}
}
}
}
}
func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
guard let attributedString = self.attributedText else { return -1 }
let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
// Add font so the correct range is returned for multi-line labels
mutableAttribString.addAttributes([NSAttributedString.Key.font: font], range: NSRange(location: 0, length: attributedString.length))
let textStorage = NSTextStorage(attributedString: mutableAttribString)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: frame.size)
textContainer.lineFragmentPadding = 0
textContainer.maximumNumberOfLines = numberOfLines
textContainer.lineBreakMode = lineBreakMode
layoutManager.addTextContainer(textContainer)
let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return index
}
}
extension NSAttributedString {
func rangesOf(subString: String) -> [NSRange] {
var nsRanges: [NSRange] = []
let ranges = string.ranges(of: subString, options: .caseInsensitive, locale: nil)
for range in ranges {
nsRanges.append(range.nsRange)
}
return nsRanges
}
}
extension String {
func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
var ranges: [Range<Index>] = []
while let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex) ..< self.endIndex, locale: locale) {
ranges.append(range)
}
return ranges
}
}
Swift 3. Я разработал расширение:
extension UILabel {
///Find the index of character (in the attributedText) at point
func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
assert(self.attributedText != nil, "This method is developed for attributed string")
let textStorage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.frame.size)
textContainer.lineFragmentPadding = 0
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.lineBreakMode = self.lineBreakMode
layoutManager.addTextContainer(textContainer)
let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return index
}
}
И теперь я могу проверить, находится ли повернутый символ в диапазоне:
let range = SOME_RANGE
let tapLocation = gesture.location(in: MY_TEXT_LABEL)
let index = textLbl.indexOfAttributedTextCharacterAtPoint(point: tapLocation)
if index > range.location && index < range.location + range.length {
//YES, THE TAPPED CHARACTER IS IN RANGE
}
Ваш стек текстового набора неисправен. Вы забыли добавить текстовый контейнер в менеджер макетов! Поэтому нет текста для разметки, и менеджер макета не может сообщить о любом прямоугольнике глифа. Следовательно, этим глифом является NSRectZero, поэтому вы никогда не сможете сообщить о касании в нем.
Другая проблема заключается в том, что вы звоните characterRangeForGlyphRange
когда ты должен звонить glyphRangeForCharacterRange
и вы, похоже, не знаете, как использовать результат (на самом деле, вы выбрасываете результат).
Вот рабочий код, который показывает только часть об использовании текстового стека. Я начинаю со строки "Привет тебе". Я покажу, как узнать, где находится прямоугольник для "to":
let s = "Hello to you"
let ts = NSTextStorage(
attributedString: NSAttributedString(string:s))
let lm = NSLayoutManager()
ts.addLayoutManager(lm)
let tc = NSTextContainer(size: CGSizeMake(4000,400))
lm.addTextContainer(tc) // ****
tc.lineFragmentPadding = 0
let toRange = (s as NSString).rangeOfString("to")
let gr = lm.glyphRangeForCharacterRange(
toRange, actualCharacterRange: nil) // ****
let glyphRect = lm.boundingRectForGlyphRange(
gr, inTextContainer: tc)
Результат {x 30.68 y 0 w 10.008 h 13.8}
, Теперь мы можем проверить, находится ли ответвление в этом прямоугольнике. Иди и делай так же.
Согласитесь с ответом, получившим наибольшее количество голосов. Но этот ответ подходит для центрального textAlignment, когда textAlignment вашего UIlabel не является центральным, а также ваш UILabel имеет edgeInset, вам нужно изменить некоторый код расчета. Мой ответ на сообщение рассматривает textAlignment и edgetInset.
func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange, edgeInset: UIEdgeInsets? = nil) -> 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)
let alignment = label.textAlignment ?? .center
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = label.lineBreakMode
textContainer.maximumNumberOfLines = label.numberOfLines
let labelSize = CGSize(width: label.bounds.width - (edgeInset?.left ?? 0) - (edgeInset?.right ?? 0), height: label.bounds.height - (edgeInset?.top
?? 0) - (edgeInset?.bottom ?? 0))
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)
var xOffset: CGFloat = 0
var yOffset: CGFloat = 0
if alignment == .left {
xOffset = (edgeInset?.left ?? 0) - textBoundingBox.origin.x
yOffset = (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y + (edgeInset?.top ?? 0)
} else if alignment == .right {
xOffset = label.bounds.width - (edgeInset?.right ?? 0) - labelSize.width - textBoundingBox.origin.x
yOffset = (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y + (edgeInset?.top ?? 0)
} else {
xOffset = (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x + (edgeInset?.left ?? 0)
yOffset = (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y + (edgeInset?.top ?? 0)
}
let textContainerOffset = CGPoint(
x: xOffset,
y: yOffset
)
let locationOfTouchInTextContainer = CGPoint(
x: locationOfTouchInLabel.x - textContainerOffset.x,
y: locationOfTouchInLabel.y - textContainerOffset.y
)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
И еще одна ситуация должна быть рассмотрена. Если ваш текст "magina: linkedword", часть "magina" является обычным текстом, "linkedword" является ссылкой. Когда вы устанавливаете текст для UILabel, вам нужно подключить текст и один пробел, в противном случае, когда вы нажмете на конец метки, функция 'didTapAttributedTextInLabel' вернет значение true, но на самом деле оно выходит за пределы текста.
var attrString = NSMutableAttributedString(string: text + " ")//add one space to present
Я разработал небольшую библиотеку SmartString для iOS , которую можно использовать для обработки касаний определенной подстроки внутри UILabel.
Использование:
// Example 1
let smartString = "hello, " + "tap here".onTap { string in
print(string) // -> "tap here"
}
label.smartString = smartString
// Example 2 (style your string to make it looks nice)
let smartString = "hello"
+ "tap here".font(.systemFont(ofSize: 22))
.color(.blue)
.underline()
.onTap { string in
print(string) // -> "tap here"
}
label.smartString = smartString