Swift 4基于AppDelegate检测全控件用户无活动实现屏保
解决iOS全应用触摸检测与用户无活动状态识别问题
我明白你的痛点——原来的代码只能检测空白区域的触摸,UIButton、UITextField这些控件上的操作完全没被捕获,导致无活动状态的判断不准确。这其实是因为触摸事件的传递机制:控件会优先处理触摸,不会把事件传到AppDelegate;而你原来的TimerUIApplication的逻辑也有疏漏,没覆盖所有用户交互场景。
下面给你一套完整的解决方案,确保能捕获所有用户交互(包括控件触摸、键盘输入等):
一、核心原理:自定义UIApplication子类
UIApplication是所有事件的入口,不管是触摸、键盘还是手势,最终都会经过它的sendEvent(_:)方法。所以我们只需要重写这个方法,就能全局捕获用户活动。
二、修正后的TimerUIApplication代码
import UIKit class TimerUIApplication: UIApplication { static let applicationDidTimeoutNotification = Notification.Name("AppTimeout") // 可根据需求调整超时时间 private let timeoutInterval: TimeInterval = 5 private var idleTimer: Timer? override init() { super.init() resetIdleTimer() } // 重置无活动计时器 private func resetIdleTimer() { idleTimer?.invalidate() idleTimer = Timer.scheduledTimer(withTimeInterval: timeoutInterval, repeats: false) { [weak self] _ in self?.handleTimeout() } } // 处理超时逻辑 private func handleTimeout() { NotificationCenter.default.post(name: Self.applicationDidTimeoutNotification, object: nil) print("用户已无活动") // 这里可以直接跳转到指定页面,或者让监听者处理 DispatchQueue.main.async { let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) guard let promoVC = mainStoryboard.instantiateViewController(withIdentifier: "mainPromo") as? UIViewController else { return } self.keyWindow?.rootViewController = promoVC self.keyWindow?.makeKeyAndVisible() } } override func sendEvent(_ event: UIEvent) { super.sendEvent(event) // 捕获所有触摸事件:只要有触摸开始/移动/结束,都算用户活动 if let touches = event.allTouches { for touch in touches { if touch.phase != .cancelled { resetIdleTimer() break } } } // 额外捕获键盘事件(比如输入内容也算用户活动) if event.type == .keyboard { resetIdleTimer() } } }
三、让应用使用自定义UIApplication子类
默认Xcode生成的项目用的是UIApplication,我们需要修改配置让系统使用TimerUIApplication:
方法1:修改Info.plist
- 打开项目的
Info.plist文件 - 添加一个新的键:
Principal class(或直接用英文键名NSPrincipalClass) - 设置值为你的类名,比如
$(PRODUCT_MODULE_NAME).TimerUIApplication(确保模块名正确,一般就是你的项目名)
方法2:替换@UIApplicationMain
如果你原来的AppDelegate用了@UIApplicationMain,可以把它删掉,然后创建一个新的Swift文件(比如Main.swift),添加以下代码:
import UIKit UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(TimerUIApplication.self), NSStringFromClass(AppDelegate.self))
四、移除AppDelegate中的冗余代码
现在TimerUIApplication已经接管了计时器逻辑,你可以把AppDelegate里的idleTimer相关代码、touches系列方法都删掉,只保留必要的启动配置:
import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { application.isIdleTimerDisabled = true let userDefaults = UserDefaults.standard let defaultValues = ["promotionIsEnabled_preference" : "YES", "promotionDuration_preference" : "10"] userDefaults.register(defaults: defaultValues) return true } // 保留其他生命周期方法(applicationWillResignActive等),但移除计时器相关逻辑 }
五、监听超时通知(可选)
如果需要在某个ViewController里处理超时事件,可以注册通知:
override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(handleAppTimeout), name: TimerUIApplication.applicationDidTimeoutNotification, object: nil) } @objc private func handleAppTimeout() { // 处理超时逻辑,比如弹出提示、清理数据等 } deinit { NotificationCenter.default.removeObserver(self) }
为什么这个方案能捕获所有触摸?
sendEvent(_:)是UIApplication的核心方法,所有用户交互事件都会经过这里,包括控件上的触摸(因为控件的触摸事件也是通过UIApplication分发的)- 我们不仅处理了
.began阶段的触摸,还覆盖了.moved、.ended,同时排除了.cancelled(比如触摸被系统中断的情况) - 额外添加了键盘事件的处理,确保用户输入文字也会重置计时器
内容的提问来源于stack exchange,提问作者Matthew Mitchell
相关产品推荐
相关产品推荐

