Swift/UIKit:实现UILabel内指定文本点击跳转对应ViewController
UILabel内指定文本跳转问题解决方法
问题背景
在注册页面中,需要实现:
- 点击UILabel内的「Terms of Service」文本,跳转至
TermsOfServiceViewController - 点击「Privacy Policy」文本,跳转至
PrivacyPolicyViewController
当前代码存在两个问题:
- 点击整个UILabel都会触发响应
- 无论点击哪个文本,都只会跳转到
PrivacyPolicyViewController
用户提供的原代码:
注册页面代码
override func viewDidLoad() { super.viewDidLoad() let termsOfServiceViewController = NavigationTapGestureRecognizer(target: self, action: #selector(termsOfServiceVCTapped)) termsOfServiceViewController.viewController = self let privacyPolicyViewController = NavigationTapGestureRecognizer(target: self, action: #selector(privacyPolicyVCTapped)) privacyPolicyViewController.viewController = self let string = NSMutableAttributedString(string: "By checking this box, you agree to our ") let attributedTermsOfService = NSMutableAttributedString(string: "Terms of Service", attributes: [NSAttributedString.Key.link: termsOfServiceViewController, NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue, NSAttributedString.Key.underlineColor: UIColor.appColor(LPColor.LightestPurple)!, NSAttributedString.Key.foregroundColor: UIColor.appColor(LPColor.LightestPurple)!]) let additionalString = NSMutableAttributedString(string: " and our ") let attributedPrivacyPolicy = NSMutableAttributedString(string: "Privacy Policy", attributes: [NSAttributedString.Key.link: privacyPolicyViewController, NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue, NSAttributedString.Key.underlineColor: UIColor.appColor(LPColor.LightestPurple)!, NSAttributedString.Key.foregroundColor: UIColor.appColor(LPColor.LightestPurple)!]) string.append(attributedTermsOfService) string.append(additionalString) string.append(attributedPrivacyPolicy) agreementLabel.attributedText = string agreementLabel.addGestureRecognizer(termsOfServiceViewController) agreementLabel.addGestureRecognizer(privacyPolicyViewController) agreementLabel.isUserInteractionEnabled = true } @objc func termsOfServiceVCTapped() { let vc = TermsOfServiceViewController.storyboardInstance(storyboardName: "Login") as! TermsOfServiceViewController navigationController?.pushViewController(vc, animated: true) } @objc func privacyPolicyVCTapped() { let vc = PrivacyPolicyViewController.storyboardInstance(storyboardName: "Login") as! PrivacyPolicyViewController navigationController?.pushViewController(vc, animated: true) }
自定义手势代码
class NavigationTapGestureRecognizer: UITapGestureRecognizer { var viewController: UIViewController? override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event!) } }
解决方案
核心思路
移除自定义手势,通过单个点击手势+文本位置检测实现精准跳转:
- 给不同可点击文本设置唯一标识字符串作为
link属性值 - 给UILabel添加单个点击手势,在回调中判断点击位置对应的文本标识
- 根据标识执行对应的跳转逻辑
修改后的完整代码
1. 更新viewDidLoad方法
override func viewDidLoad() { super.viewDidLoad() // 构建基础文本 let baseText = NSMutableAttributedString(string: "By checking this box, you agree to our ") // 服务条款文本:设置link标识为"terms" let termsText = NSMutableAttributedString(string: "Terms of Service", attributes: [ .link: "terms", .underlineStyle: NSUnderlineStyle.single.rawValue, .underlineColor: UIColor.appColor(LPColor.LightestPurple)!, .foregroundColor: UIColor.appColor(LPColor.LightestPurple)! ]) let middleText = NSMutableAttributedString(string: " and our ") // 隐私政策文本:设置link标识为"privacy" let privacyText = NSMutableAttributedString(string: "Privacy Policy", attributes: [ .link: "privacy", .underlineStyle: NSUnderlineStyle.single.rawValue, .underlineColor: UIColor.appColor(LPColor.LightestPurple)!, .foregroundColor: UIColor.appColor(LPColor.LightestPurple)! ]) // 拼接所有文本 baseText.append(termsText) baseText.append(middleText) baseText.append(privacyText) agreementLabel.attributedText = baseText agreementLabel.isUserInteractionEnabled = true // 添加单个点击手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(labelTapped(_:))) agreementLabel.addGestureRecognizer(tapGesture) }
2. 实现点击检测与跳转逻辑
@objc private func labelTapped(_ gesture: UITapGestureRecognizer) { guard let label = gesture.view as? UILabel, let attributedText = label.attributedText else { return } // 获取点击位置并转换为Label内部坐标 let tapLocation = gesture.location(in: label) let adjustedTapPoint = CGPoint(x: tapLocation.x, y: tapLocation.y - (label.textInsets.top)) // 初始化文本布局相关对象 let textContainer = NSTextContainer(size: label.bounds.size) textContainer.lineFragmentPadding = 0 textContainer.maximumNumberOfLines = label.numberOfLines textContainer.lineBreakMode = label.lineBreakMode let layoutManager = NSLayoutManager() layoutManager.addTextContainer(textContainer) let textStorage = NSTextStorage(attributedString: attributedText) textStorage.addLayoutManager(layoutManager) // 获取点击位置对应的字符索引 let characterIndex = layoutManager.characterIndex( for: adjustedTapPoint, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil ) // 检查是否超出文本范围 guard characterIndex < attributedText.length else { return } // 获取该位置的链接属性 let attributes = attributedText.attributes(at: characterIndex, effectiveRange: nil) if let linkIdentifier = attributes[.link] as? String { switch linkIdentifier { case "terms": termsOfServiceVCTapped() case "privacy": privacyPolicyVCTapped() default: break } } } @objc private func termsOfServiceVCTapped() { guard let vc = TermsOfServiceViewController.storyboardInstance(storyboardName: "Login") as? TermsOfServiceViewController else { return } navigationController?.pushViewController(vc, animated: true) } @objc private func privacyPolicyVCTapped() { guard let vc = PrivacyPolicyViewController.storyboardInstance(storyboardName: "Login") as? PrivacyPolicyViewController else { return } navigationController?.pushViewController(vc, animated: true) }
3. 移除自定义手势类
不再需要NavigationTapGestureRecognizer,可直接删除该类。
原代码问题解析
- 点击整个Label触发响应:添加了两个全局手势,未对点击位置做文本范围校验,导致任意点击Label都会触发手势回调。
- 统一跳转到隐私政策页面:后添加的隐私政策手势会覆盖先添加的服务条款手势,且手势未区分点击区域,最终只会执行最后添加的手势回调。
内容的提问来源于stack exchange,提问作者noobietubie
相关产品推荐
相关产品推荐

