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

iOS技术问题:重现UIActionSheet/UIAlertController拖拽高亮及命中检测原理

实现iOS UIActionSheet/UIAlertController拖拽高亮效果 + 解析其命中检测机制

嘿,这个问题我刚好研究过,下面分两部分给你拆解清楚:

一、实现拖拽高亮效果(以UIAlertController为例)

毕竟UIActionSheet在iOS 8之后就被UIAlertController取代了,所以咱们基于UIAlertController来实现类似的拖拽高亮效果,核心思路就是监听触摸事件,实时判断手指落在哪个action按钮上,然后切换高亮状态:

  1. 自定义UIAlertController子类
    先建一个子类,比如HighlightableAlertController,专门用来处理触摸事件:

    class HighlightableAlertController: UIAlertController {
        private var currentHighlightedAction: UIAlertAction?
        
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesMoved(touches, with: event)
            guard let touch = touches.first, let alertView = view.subviews.first else { return }
            
            // 把触摸点转换成alertView坐标系下的位置
            let touchPoint = touch.location(in: alertView)
            
            // 挨个检查每个action按钮,看触摸点是不是在它范围内
            for subview in alertView.subviews {
                if let actionButton = subview as? UIButton, actionButton.frame.contains(touchPoint) {
                    // 找到这个按钮对应的UIAlertAction
                    let action = actionForButton(actionButton)
                    if action != currentHighlightedAction {
                        // 取消上一个按钮的高亮
                        currentHighlightedAction?.setValue(false, forKey: "highlighted")
                        // 给当前按钮设置高亮
                        action?.setValue(true, forKey: "highlighted")
                        currentHighlightedAction = action
                    }
                    return
                }
            }
            
            // 如果触摸不在任何按钮上,就取消所有高亮
            currentHighlightedAction?.setValue(false, forKey: "highlighted")
            currentHighlightedAction = nil
        }
        
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            super.touchesEnded(touches, with: event)
            // 手指抬起后,把高亮状态重置
            currentHighlightedAction?.setValue(false, forKey: "highlighted")
            currentHighlightedAction = nil
        }
        
        // 通过按钮找到对应的UIAlertAction
        private func actionForButton(_ button: UIButton) -> UIAlertAction? {
            // 这里用了UIAlertController的私有属性关联,如果怕审核风险,可以改成给按钮加tag,和action的下标对应
            guard let actions = value(forKey: "actions") as? [UIAlertAction],
                  let buttonIndex = button.value(forKey: "index") as? Int,
                  buttonIndex < actions.count else { return nil }
            return actions[buttonIndex]
        }
    }
    

    小提示:如果担心私有属性过不了审核,你可以在添加action的时候,给对应的按钮设置tag(比如第n个action对应tag n),然后通过tag来匹配对应的action。

  2. 调用自定义的AlertController
    用法和普通UIAlertController完全一样:

    let alert = HighlightableAlertController(title: "测试拖拽高亮", message: "滑动手指试试切换高亮", preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction(title: "选项一", style: .default, handler: { _ in
        print("选了选项一")
    }))
    alert.addAction(UIAlertAction(title: "选项二", style: .default, handler: { _ in
        print("选了选项二")
    }))
    alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { _ in }))
    present(alert, animated: true)
    

二、UIActionSheet的命中检测工作机制

UIActionSheet的这套检测逻辑其实是基于UIKit的触摸事件处理链来实现的,核心步骤大概是这样:

  • 触摸事件的捕获:当用户手指按下时,触摸事件会从UIApplication一路传递到UIActionSheet的视图层级,UIActionSheet先判断触摸点是不是在自己的视图范围内,是才继续处理。
  • 实时位置判断:在touchesMoved事件触发时,UIActionSheet会持续获取触摸点的坐标,然后遍历所有选项按钮的frame,判断触摸点落在哪个按钮的范围内。
  • 滚动状态识别:当选项过多需要滚动时,UIActionSheet内部其实封装了一个UIScrollView。它会监听scrollView的isDragging和isDecelerating状态——如果scrollView处于滚动状态,就暂时禁用拖拽高亮逻辑,避免用户滚动时误触发高亮。
  • 高亮状态切换:一旦检测到触摸点从一个按钮移到另一个按钮,会立刻取消前一个按钮的高亮,给当前按钮加上高亮;如果触摸移出所有按钮的范围,就把所有高亮都取消。
  • 触摸结束的处理:当手指抬起时,UIActionSheet会检查当前高亮的按钮,触发对应的点击事件,然后重置所有按钮的高亮状态。

另外提一句:UIAlertController的actionSheet样式其实就是沿用了UIActionSheet的这套检测逻辑,只是内部实现更封装了而已。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:52:45