Swift 单个UILabel添加多段可点击子串跳转不同页面问题问询
UILabel多段可点击子串实现方案
问题根源
现有代码存在两个核心问题导致仅最后一段可点击子串的事件能触发:
- 循环为同一个UILabel重复添加点击手势,同时使用
stringToBeCalled、pageToBeCalled这类单值属性存储当前匹配的可点击内容,每轮遍历都会覆盖上一轮的属性值,最终属性中仅保留最后一段可点击子串的信息,所有手势触发时都只会匹配最后一段的范围。 - 同一UILabel无需添加多个点击手势,单个手势即可完成所有可点击区域的判断。
优化实现思路
- 新增结构体存储所有可点击子串的信息,包括显示文本、对应的跳转页面对象、在整段文本中的NSRange区间。
- 预处理阶段遍历所有可点击子串,计算每段的区间后存入数组,仅给UILabel添加一次点击手势。
- 点击触发时遍历可点击数组,判断点击位置落在哪个区间内,触发对应跳转逻辑。
核心代码修改
1. 新增可点击项结构体
在ViewModel中定义结构体存储可点击项的完整信息:
struct ClickableTextItem { let displayText: String let targetPage: Page let range: NSRange }
在ViewModel中新增数组属性存储所有可点击项,废弃原有的单值存储属性stringToBeCalled、pageToBeCalled:
var clickableItems: [ClickableTextItem] = []
2. 重构手势添加与预处理逻辑
// 仅调用一次,无需放在循环中 public func setupClickableLabel(label: UILabel, clickableValues: [String]) { label.lineBreakMode = .byWordWrapping label.isUserInteractionEnabled = true // 仅添加一次点击手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tappedOnLabel(_:))) label.addGestureRecognizer(tapGesture) // 预处理所有可点击项,计算对应区间存入数组 clickableItems.removeAll() guard let fullText = label.text as? NSString else { return } for value in clickableValues { guard checkIfClickable(string: value), let targetPage = getPageForString(value) else { continue } // 若存在同一字符串多次出现的情况,可自行扩展遍历拿到所有匹配的range let range = fullText.range(of: value) guard range.location != NSNotFound else { continue } clickableItems.append(ClickableTextItem( displayText: value, targetPage: targetPage, range: range )) } }
3. 重构点击回调逻辑
@objc func tappedOnLabel(_ gesture: UITapGestureRecognizer) { guard let label = gesture.view as? UILabel, let allPages = self.pages else { return } // 遍历所有可点击项匹配点击区间 for item in clickableItems { if gesture.didTapAttributedText(label: label, inRange: item.range) { let pageDetailVC = PageDetailViewController(viewModel: PageDetailViewModel()) pageDetailVC.populatePage(page: item.targetPage, allPages: allPages) pageDetailVC.modalPresentationStyle = .fullScreen UIApplication.topViewController()?.navigationController?.pushViewController(pageDetailVC, animated: true) // 匹配到对应项后直接返回,避免重复触发 return } } }
4. ViewController调用逻辑修改
if let clickableArray = self.viewModel.descriptionClickableValuesArray { // 一次性初始化所有可点击配置,仅调用一次 self.viewModel.setupClickableLabel(label: self.pageDescription, clickableValues: clickableArray) // 统一设置所有可点击文本的蓝色样式 let highlightStrings = clickableArray.filter { self.viewModel.checkIfClickable(string: $0) } self.pageDescription.set(color: .systemBlue, on: highlightStrings) }
注意事项
- 若存在同一个可点击字符串在文本中多次出现的情况,需要扩展预处理逻辑,遍历拿到所有匹配的NSRange,分别生成
ClickableTextItem存入数组。 - 针对
[[Bretagne|Brittany]]这类别名格式的双中括号内容,预处理阶段需要拆分内容,将显示文本设置为别名(Brittany),跳转匹配的key设置为原名称(Bretagne)。 - 可直接调用现有扩展的
didTapAttributedText方法传入NSRange判断点击,比didTapAttributedString通过字符串匹配范围更准确。
内容的提问来源于stack exchange,提问作者Kacper Cichosz
相关产品推荐
相关产品推荐

