You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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!)

    }
}

解决方案

核心思路

移除自定义手势,通过单个点击手势+文本位置检测实现精准跳转:

  1. 给不同可点击文本设置唯一标识字符串作为link属性值
  2. 给UILabel添加单个点击手势,在回调中判断点击位置对应的文本标识
  3. 根据标识执行对应的跳转逻辑

修改后的完整代码

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,可直接删除该类。


原代码问题解析

  1. 点击整个Label触发响应:添加了两个全局手势,未对点击位置做文本范围校验,导致任意点击Label都会触发手势回调。
  2. 统一跳转到隐私政策页面:后添加的隐私政策手势会覆盖先添加的服务条款手势,且手势未区分点击区域,最终只会执行最后添加的手势回调。

内容的提问来源于stack exchange,提问作者noobietubie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:48:01