iOS 11:如何在viewDidAppear中同步显示UIAlertController并等待点击
解决iOS 11中viewDidAppear同步显示UIAlertController的问题
兄弟,你遇到的这个问题我之前也踩过坑!核心原因其实是UIKit的运行机制:viewDidAppear是在主线程的视图渲染周期里被调用的,如果你直接在这里同步阻塞主线程(比如等着用户点弹窗按钮),会彻底打断UIKit的渲染流程——弹窗根本来不及绘制出来,界面自然就僵住了。
直接说解决方案,分两种场景给你参考:
1. 最推荐:遵循UIKit回调驱动模式(异步逻辑拆分)
这是最符合iOS设计理念的写法,把用户点击后要执行的代码放到弹窗按钮的action回调里,完全不用纠结“同步”的问题:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // 先创建弹窗 let alert = UIAlertController(title: "提示", message: "请点击按钮继续操作", preferredStyle: .alert) // 添加按钮并绑定后续逻辑 let confirmAction = UIAlertAction(title: "确定", style: .default) { [weak self] _ in // 用户点击后,执行你原本要“同步继续”的代码 self?.executePostTapLogic() } alert.addAction(confirmAction) // 用DispatchQueue.main.async把present放到当前runloop的下一个周期 // 避免和viewDidAppear的视图初始化流程冲突 DispatchQueue.main.async { self.present(alert, animated: true, completion: nil) } } private func executePostTapLogic() { // 这里写用户点击后的所有逻辑 print("用户已确认,开始执行后续操作") // 比如刷新数据、跳转页面等等 }
2. 模拟“同步等待”效果(不推荐,但满足需求)
如果你非要实现“等用户点击后再继续”的直观写法,可以用DispatchSemaphore,但绝对不能在主线程调用wait()(会卡死),必须放到子线程等待,主线程处理弹窗:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let semaphore = DispatchSemaphore(value: 0) // 主线程present弹窗 DispatchQueue.main.async { let alert = UIAlertController(title: "提示", message: "请点击按钮", preferredStyle: .alert) let confirmAction = UIAlertAction(title: "确定", style: .default) { _ in // 用户点击后发信号,唤醒等待的子线程 semaphore.signal() } alert.addAction(confirmAction) self.present(alert, animated: true, completion: nil) } // 子线程等待信号,不阻塞主线程 DispatchQueue.global().async { semaphore.wait() // 等待结束后,切回主线程执行UI相关的后续逻辑 DispatchQueue.main.async { self.executePostTapLogic() } } } private func executePostTapLogic() { print("等待结束,继续执行") }
关键提醒
- 为什么直接
present不行?因为viewDidAppear执行时,UIKit还在处理视图的布局和显示流程,直接调用present会打乱这个周期,弹窗无法被正常渲染。用DispatchQueue.main.async把present推迟到当前runloop结束后执行,就能确保视图已经完全准备好,弹窗正常显示。 - 第二种方法虽然模拟了同步等待,但本质还是异步逻辑,而且子线程等待的写法容易产生隐蔽的线程问题,除非特殊需求,优先用第一种回调驱动的写法。
内容的提问来源于stack exchange,提问作者M.Gonbe




