iOS如何检测手掌触摸/离开iPhone屏幕 实现求救呼叫按钮功能
解决方案
方案1:自定义高优先级长按时手势识别器(推荐,无私有API,可上架)
这是适配性最高的落地方案,实现逻辑如下:
- 首先创建自定义
UILongPressGestureRecognizer实例,配置基础参数:
// OC示例,Swift逻辑完全一致 UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)]; gesture.minimumPressDuration = 0; // 触摸立刻触发识别 gesture.allowableMovement = CGFLOAT_MAX; // 允许任意位置移动不中断识别 gesture.cancelsTouchesInView = NO; gesture.delaysTouchesBegan = NO; gesture.delaysTouchesEnded = NO; gesture.delegate = self; [self.view addGestureRecognizer:gesture];
- 实现手势代理方法,提升手势优先级避免被系统拦截:
// 允许和所有其他手势同时识别 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; } // 接收所有类型的触摸事件,包含大面积手掌触摸 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { return YES; } // 始终允许自定义手势触发 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { return YES; }
- 处理手势状态,匹配业务逻辑:
- (void)handleGesture:(UILongPressGestureRecognizer *)gesture { if (gesture.state == UIGestureRecognizerStateBegan) { // 首次检测到触摸,对应「屏幕被触摸」事件 } else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) { // 所有触摸释放/手势结束,对应「所有触摸均被释放」事件 // 额外判断触摸点数量避免误触发 if (gesture.numberOfTouches == 0) { [self sendSOSSignal]; } } }
- 额外配置屏蔽系统手势抢占:
遍历当前window的所有手势,设置自定义手势优先级最高:
for (UIGestureRecognizer *sysGesture in self.view.window.gestureRecognizers) { [sysGesture requireGestureRecognizerToFail:gesture]; }
同时配置当前ViewController属性,屏蔽系统级交互干扰:
self.modalInPresentation = YES; self.prefersStatusBarHidden = YES; self.view.multipleTouchEnabled = YES; self.view.userInteractionEnabled = YES;
方案2:重写UIWindow的sendEvent方法(补充兜底)
之前重写sendEvent无效是因为没有全量过滤事件类型,正确实现逻辑:
- 子类化UIWindow,重写
sendEvent:方法:
- (void)sendEvent:(UIEvent *)event { [super sendEvent:event]; if (event.type == UIEventTypeTouches) { NSSet *allTouches = [event allTouches]; NSInteger activeTouchCount = 0; for (UITouch *touch in allTouches) { if (touch.phase != UITouchPhaseCancelled && touch.phase != UITouchPhaseEnded) { activeTouchCount++; } } // 全局维护触摸计数,不需要走responder chain即可获取当前活跃触摸数 [GlobalTouchManager sharedInstance].activeTouchCount = activeTouchCount; if (activeTouchCount == 0 && [GlobalTouchManager sharedInstance].hadTouch) { // 所有触摸释放,触发求救逻辑 [self sendSOSSignal]; [GlobalTouchManager sharedInstance].hadTouch = NO; } else if (activeTouchCount > 0) { [GlobalTouchManager sharedInstance].hadTouch = YES; } } }
该方案可以拿到所有系统分发的触摸事件,包括被默认responder chain丢弃的大面积触摸事件,只要系统生成了触摸事件都会走到该方法中。
常见问题解答
- 不通过responder chain直接查询屏幕触摸状态:没有公开API支持,私有API
GSEventGetSnapshot()可以获取全局触摸状态,但会被App Store审核拒绝,不建议使用。 - 禁止touchesCancelled触发:按照方案1配置屏蔽所有系统手势抢占后,99%的非用户主动取消的
touchesCancelled事件都会被消除,剩余极端场景(如触摸时APP退后台、来电弹窗)本身属于需要中断求救流程的场景,无需额外处理。
内容的提问来源于stack exchange,提问作者xpsysdev
相关产品推荐
相关产品推荐

