Swift开发:如何定位弹窗使其指向文本视图中被点击的灰色单词
嘿,我来帮你搞定这个popover精准定位到点击单词的问题!要让弹窗刚好指向文本视图里被点击的灰色单词,咱们得从获取点击单词的屏幕位置和设置popover的源矩形这两步来,下面是具体的实现思路和代码:
第一步:拿到被点击单词的位置矩形
首先,你得在文本视图的点击事件里,算出被点击单词对应的屏幕区域。假设你用的是UITextView,我们可以借助NSLayoutManager来把字符范围转换成实际的CGRect:
如果你是通过属性文本(比如把灰色单词设成可交互的链接)来处理点击,那可以用textView(_:shouldInteractWith:in:interaction:)这个代理方法:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { // 先判断是不是你要处理的灰色单词(比如通过URL的scheme或者文本内容判断) let layoutManager = textView.layoutManager // 计算字符范围在文本容器内的矩形 var wordRect = layoutManager.boundingRect(forGlyphRange: characterRange, in: textView.textContainer) // 把容器内的坐标转换成textView的全局坐标 wordRect = textView.convert(wordRect, from: layoutManager.textContainer) // 把这个矩形传给popover的展示逻辑 self.showTargetPopover(at: wordRect, from: textView) return false // 阻止默认的链接跳转行为 }
要是你用的是自定义手势识别器,那可以通过手势的点击点来找到对应的字符范围,再计算矩形:
@objc func handleTextViewTap(_ gesture: UITapGestureRecognizer) { guard let textView = gesture.view as? UITextView else { return } let tapLocation = gesture.location(in: textView) // 找到点击位置对应的字符索引 guard let characterIndex = textView.layoutManager.characterIndex(for: tapLocation, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) else { return } // 假设灰色单词是连续的,这里需要你自己实现获取整个单词范围的逻辑,比如从当前索引向前后找空格/标点 let wordRange = textView.text.rangeOfWord(at: characterIndex) // 自定义方法,需要你实现 let nsWordRange = NSRange(wordRange, in: textView.text) // 同样计算矩形 var wordRect = textView.layoutManager.boundingRect(forGlyphRange: nsWordRange, in: textView.textContainer) wordRect = textView.convert(wordRect, from: textView.layoutManager.textContainer) self.showTargetPopover(at: wordRect, from: textView) }
第二步:让Popover指向这个矩形
接下来,不管你是用segue还是直接present控制器,都要给popover的popoverPresentationController设置好sourceView和sourceRect,这样弹窗就会精准指向你算出的单词位置:
方式一:直接present控制器
func showTargetPopover(at sourceRect: CGRect, from sourceView: UIView) { // 从storyboard获取你的popover控制器,替换成你实际的标识符和类型 guard let popoverVC = storyboard?.instantiateViewController(withIdentifier: "YourPopoverVC") as? YourPopoverViewController else { return } // 设置popover展示样式 popoverVC.modalPresentationStyle = .popover if let popoverPC = popoverVC.popoverPresentationController { popoverPC.sourceView = sourceView // 这里传textView popoverPC.sourceRect = sourceRect // 刚才算出的单词矩形 // 可以设置箭头允许的方向,比如让系统自动选合适的方向 popoverPC.permittedArrowDirections = .any } // 还可以把被点击的单词内容传给popover,方便展示详情 let clickedWord = textView.text.substring(with: nsWordRange) // 替换成你获取单词的逻辑 popoverVC.targetWord = clickedWord present(popoverVC, animated: true) }
方式二:用Segue跳转
如果还是想用你现有的prepare(for:sender:)方法,那可以把计算好的sourceRect作为sender传进去:
// 先在点击事件里触发segue,把sourceRect当sender self.performSegue(withIdentifier: "popOverSegue", sender: wordRect) // 然后在prepare方法里设置popover参数 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "popOverSegue", let popoverVC = segue.destination as? YourPopoverViewController, let sourceRect = sender as? CGRect, let textView = self.view.subviews.first(where: { $0 is UITextView }) as? UITextView else { return } popoverVC.modalPresentationStyle = .popover if let popoverPC = popoverVC.popoverPresentationController { popoverPC.sourceView = textView popoverPC.sourceRect = sourceRect popoverPC.permittedArrowDirections = .up } // 这里同样可以传递单词内容给popover // ... 你的逻辑 }
几个要注意的小细节
- 确保你的
UITextView的isSelectable属性是true,不然没法获取字符范围 - 如果灰色单词是用
NSAttributedText标记的(比如只有灰色的单词有特定属性),你可以在点击时用textView.attributedText.attributes(at: characterIndex, effectiveRange: nil)来判断是不是目标单词 - 要是textView有滚动,
boundingRect方法会自动考虑滚动偏移,不用额外处理
这样调整后,你的popover就会完美对准被点击的灰色单词啦!
内容的提问来源于stack exchange,提问作者doodle
相关产品推荐
相关产品推荐

