给iOS顶层keyWindow添加UITapGestureRecognizer无法获取非零点击位置
问题原因
- 核心异常:当
UITapGestureRecognizer添加到UIWindow实例上时,调用location(in: gestureRecognizer.view)计算坐标会出现UIWindow层级的转换异常,系统无法正确计算触摸点相对于Window本身的坐标,因此统一返回(0.0, 0.0)。 - 设计隐患:当前手势识别器作为单个ViewController的私有属性持有,若该ViewController生命周期结束被释放,手势也会同步销毁,无法实现跨页面的全局点击检测需求。
- 兼容风险:iOS13+多场景(Scene)下,现有keyWindow获取逻辑在存在浮窗、弹窗临时window等多window场景时,可能取到非当前展示的主window,也会触发坐标计算异常。
解决方案
- 修正坐标获取方式:调用
location(in:)时传入nil,系统会自动返回触摸点相对于当前应用keyWindow的坐标,不需要手动传入手势所属view,修改后的代码如下:
@objc func tap(_ gestureRecognizer: UITapGestureRecognizer) { // 传入nil默认取keyWindow作为坐标参照系 let location = gestureRecognizer.location(in: nil) print("location \(location)") }
- 全局持有点击手势:如果需要实现全局点击检测,不要将手势放在单个ViewController中持有,可以放到AppDelegate/SceneDelegate或自定义全局单例中持有,避免随VC销毁失效,参考实现如下:
// 全局单例管理全局点击手势 class GlobalGestureManager { static let shared = GlobalGestureManager() private lazy var gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap)) private init() {} @objc private func tap(_ gestureRecognizer: UITapGestureRecognizer) { let location = gestureRecognizer.location(in: nil) print("全局点击位置:\(location)") } func addGlobalTapGesture() { guard let keyWindow = UIWindow.key else { return } keyWindow.addGestureRecognizer(gestureRecognizer) } } // 应用启动时调用,比如在AppDelegate的didFinishLaunchingWithOptions方法中执行 GlobalGestureManager.shared.addGlobalTapGesture()
- 优化keyWindow获取逻辑,适配多Scene场景:
extension UIWindow { static var key: UIWindow? { if #available(iOS 13.0, *) { return UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first?.windows .first { $0.isKeyWindow } } else { return UIApplication.shared.keyWindow } } }
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

