SwiftUI运行定时器时Alert无法正常dismiss的问题该如何解决?
问题根因
Timer每0.1秒触发@Published属性更新,会导致整个TimerView全局重绘,Alert的点击事件会和视图重绘时机冲突,单次点击触发的isAlertPresented状态变更没有被正确响应,所以需要多次点击才能关闭弹窗。
可行解决方案
方案1:拆分频繁刷新的子视图(最推荐)
把需要随Timer实时更新的计时文本拆分为独立子视图,只有子视图会随Timer更新重绘,父视图不会被频繁刷新,Alert绑定的状态就不会受冲突影响。就算按钮需要和Stopwatch对象交互,也可以正常传递对象,完全不影响业务逻辑。
拆分后的计时子视图代码:
// 仅负责计时展示的独立子视图 struct TimerDisplayView: View { @ObservedObject var stopwatch: Stopwatch var body: some View { Text(String(format: "%.1f", stopwatch.secondsElapsed)) .font(.system(size: 70.0)) .minimumScaleFactor(0.1) .lineLimit(1) } }
修改后的主视图代码:
struct TimerView: View { // iOS14+ 用@StateObject持有ObservableObject比@ObservedObject更稳定 @StateObject var stopwatch = Stopwatch() @State var isAlertPresented: Bool = false var body: some View { VStack{ // 替换为拆分后的计时子视图 TimerDisplayView(stopwatch: stopwatch) Button(action:{ stopwatch.actionStartStop() }){ Text("Toggle Timer") } Button(action:{ do { // 和Stopwatch交互的业务逻辑完全不受影响 try stopwatch.actionDoThis() } catch { isAlertPresented = true } }){ Text("Toggle Alert") } } .alert(isPresented: $isAlertPresented){ Alert(title:Text("Error"),message:Text("I am presented")) } } }
方案2:调整Timer的RunLoop运行模式
默认Timer添加在RunLoop的default模式下,用户交互时RunLoop会切换到tracking模式,Timer触发和交互事件会出现抢占。你可以在创建Timer时手动把它添加到common模式,避免交互时Timer阻塞,也能解决冲突。
修改Stopwatch的start方法即可:
func start() { secondsElapsed = 0.0 mode = .running let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in self.secondsElapsed += 0.1 } // 手动添加到common模式,兼容交互场景 RunLoop.main.add(timer, forMode: .common) self.timer = timer }
方案3:延迟更新Alert状态(兜底方案)
如果上述方案都不适用,可以在修改isAlertPresented状态时添加极短的异步延迟,确保交互事件不会和视图重绘冲突:
Button(action:{ do{ try stopwatch.actionDoThis() }catch{ DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { isAlertPresented = true } } }){ Text("Toggle Alert") }
内容的提问来源于stack exchange,提问作者itsBryan
相关产品推荐
相关产品推荐

