如何持续检测游戏手柄的连接状态?
解决GameController断开通知不触发的问题
核心问题分析
GCControllerDidDisconnect通知不触发的核心原因,几乎都是未持有已连接GCController实例的强引用。系统仅会在控制器实例被释放前发送断开通知,若代码中没有保留引用,控制器对象会被ARC提前回收,导致断开事件无法正常触发。另外你提到的初始状态显示"CONNECTED",大概率是误判了连接状态,或是启动时存在残留的控制器实例。
修复后的NotificationCenter方案
1. 添加控制器引用存储
在GameViewController中新增属性,用来保存已连接的控制器,避免实例被提前回收:
private var connectedControllers: [GCController] = []
2. 修正通知处理逻辑
更新连接/断开的处理方法,确保正确管理控制器引用:
@objc func connectControllers(_ notification: Notification) { guard let controller = notification.object as? GCController else { return } // 避免重复添加同一控制器 guard !connectedControllers.contains(controller) else { print("控制器已处于连接状态") return } connectedControllers.append(controller) // 修改SKSpriteNode颜色为连接状态 updateSpriteColor(isConnected: true) // 绑定手柄按键监听 setupControllerInputs(controller: controller) print("CONNECTED") } @objc func disconnectControllers(_ notification: Notification) { guard let controller = notification.object as? GCController else { return } if let index = connectedControllers.firstIndex(of: controller) { connectedControllers.remove(at: index) } // 修改SKSpriteNode颜色为断开状态 updateSpriteColor(isConnected: false) stopGame() print("DISCONNECTED") }
3. 调整通知注册时机
将通知注册放在viewWillAppear中,同时主动查询已连接的控制器,避免启动时遗漏已连接的手柄:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) ObserveForGameControllers() // 主动检查并处理已连接的控制器 for controller in GCController.controllers() { connectControllers(Notification(name: .GCControllerDidConnect, object: controller)) } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 移除通知监听,防止内存泄漏 NotificationCenter.default.removeObserver(self) }
4. 手柄按键监听实现
添加按键响应逻辑,确保连接后能正常处理手柄操作:
private func setupControllerInputs(controller: GCController) { guard let gamepad = controller.extendedGamepad else { return } // 示例:监听A键按下事件 gamepad.buttonA.pressedChangedHandler = { button, value, pressed in if pressed { // 此处添加A键按下的业务逻辑 } } // 可扩展监听方向键、其他按键的逻辑... } private func updateSpriteColor(isConnected: Bool) { // 假设你有一个名为targetSprite的SKSpriteNode实例 targetSprite.color = isConnected ? .green : .red }
替代方案:直接使用GCController断开回调
如果NotificationCenter方案仍有问题,可以改用控制器自带的disconnectHandler回调,逻辑更直接:
func setupControllerDiscovery() { // 启动无线手柄搜索 GCController.startWirelessControllerDiscovery { // 发现新手柄的回调(可选) } // 处理已连接的手柄 for controller in GCController.controllers() { bindController(controller) } // 监听新手柄连接通知 NotificationCenter.default.addObserver(self, selector: #selector(handleControllerConnected(_:)), name: .GCControllerDidConnect, object: nil) } @objc private func handleControllerConnected(_ notification: Notification) { guard let controller = notification.object as? GCController else { return } bindController(controller) } private func bindController(_ controller: GCController) { updateSpriteColor(isConnected: true) setupControllerInputs(controller: controller) // 设置断开回调 controller.disconnectHandler = { [weak self] in guard let self = self else { return } self.updateSpriteColor(isConnected: false) self.stopGame() print("DISCONNECTED via handler") } }
额外排查点
- 手柄断开方式:确保用正确操作断开手柄(比如长按Home键选择断开、关闭手柄电源),部分休眠操作不会触发断开事件。
- 模拟器测试:在Xcode模拟器中通过
Hardware > Game Controllers菜单操作虚拟手柄,验证事件触发逻辑。 - 内存检查:用Xcode的Memory Graph Debugger排查是否有控制器实例被意外保留,导致断开通知无法发送。
内容的提问来源于stack exchange,提问作者anon
相关产品推荐
相关产品推荐

