Swift使用UILabel如何实现服务条款与隐私政策行内可点击文本?
问题原因
点击错位90%以上的情况是didTapAttributedTextInLabel方法的字符位置计算逻辑有缺陷,没有考虑UILabel的默认内边距、富文本排版属性、多行布局适配问题,其次是range计算的数据源和实际渲染文本不一致。
解决步骤
1. 先修正UILabel基础配置
给你的instructionLabel添加以下配置,消除默认边距导致的偏移:
instructionLabel.isUserInteractionEnabled = true instructionLabel.textContainerInset = .zero instructionLabel.lineFragmentPadding = 0
2. 替换正确的点击区域判断方法
替换你当前用的didTapAttributedTextInLabel实现,使用支持多行、适配富文本属性的版本:
extension UITapGestureRecognizer { func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool { guard let attributedText = label.attributedText else { return false } // 创建文本容器 let textStorage = NSTextStorage(attributedString: attributedText) let layoutManager = NSLayoutManager() let textContainer = NSTextContainer(size: label.bounds.size) textContainer.lineFragmentPadding = 0 textContainer.maximumNumberOfLines = label.numberOfLines textContainer.lineBreakMode = label.lineBreakMode layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) // 获取点击位置 let locationOfTouch = self.location(in: label) // 排除label内边距外的点击 if locationOfTouch.y < 0 || locationOfTouch.y > label.bounds.height { return false } // 计算点击位置对应的字符索引 let textBoundingBox = layoutManager.usedRect(for: textContainer) let textContainerOffset = CGPoint( x: (label.bounds.size.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (label.bounds.size.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y ) let locationOfTouchInTextContainer = CGPoint( x: locationOfTouch.x - textContainerOffset.x, y: locationOfTouch.y - textContainerOffset.y ) let indexOfCharacter = layoutManager.characterIndex( for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil ) return NSLocationInRange(indexOfCharacter, targetRange) } }
3. 修正你的点击处理代码
从attributedText取字符串计算range,避免普通text和富文本内容不一致导致的range错位,同时删除无效的匹配逻辑:
@objc func tapLabel(_ gesture:UITapGestureRecognizer){ guard let attributedText = instructionLabel.attributedText else { return } let text = attributedText.string as NSString let range1 = text.range(of: "the Terms of Service") let range3 = text.range(of: "Privacy Policy") if gesture.didTapAttributedTextInLabel(label: instructionLabel, inRange: range1) { print("the Terms of Service") // 跳转服务条款逻辑 } else if gesture.didTapAttributedTextInLabel(label: instructionLabel, inRange: range3){ print("Privacy Policy") // 跳转隐私政策逻辑 } else { print("Somewhere else") } }
额外注意事项
- 如果你的可点击文本字体和普通文本字体不一样,一定要确保计算range的字符串和
attributedText的字符串完全一致,不要出现隐藏字符、空格差异。 - 多行文本场景下,不要给UILabel设置
sizeToFit后再修改宽度,保持label的尺寸和文本渲染尺寸一致。 - 你当前代码中用于匹配" and "的逻辑无效,你的实际文本中连接两个政策的是中文“和”,该匹配会返回无效范围,已在上述修正代码中移除。
内容的提问来源于stack exchange,提问作者Punreach Rany
相关产品推荐
相关产品推荐

