SwiftUI:在设备方向上重绘会使计时器无效

Posted

技术标签:

【中文标题】SwiftUI:在设备方向上重绘会使计时器无效【英文标题】:SwiftUI: Repaint on Device orientation invalidates Timer 【发布时间】:2020-04-14 19:15:52 【问题描述】:

我正在使用 SwiftUI 制作秒表。

出于设计目的,我在方向更改时重新绘制 UI,但这会使我的计时器无效。我用了these solutions之一

如何在方向更改时保持计时器?

这是模型:

class Model: ObservableObject 
    @Published var isLandScape: Bool = false
    @Published var isPhone: Bool =  UIDevice.current.userInterfaceIdiom == .phone
    @Published var isPhoneAndLandscape: Bool = false;

自定义 UIHostingController:

extension Notification.Name 
    static let my_onViewWillTransition = Notification.Name("MainUIHostingController_viewWillTransition")


class MyUIHostingController<Content> : UIHostingController<Content> where Content : View 

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) 
        NotificationCenter.default.post(name: .my_onViewWillTransition, object: nil, userInfo: ["size": size])
        super.viewWillTransition(to: size, with: coordinator)
    


我在 SceneDelegate 中使用的:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) 
    if let windowScene = scene as? UIWindowScene 
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = MyUIHostingController(rootView: HomeView().environmentObject(model))
        self.window = window
        window.makeKeyAndVisible()
    

还有秒表类:

class StopWatch: ObservableObject 
    @Published var stopWatchTime: String = "00:00:00";
    @Published var isPaused = true;
    var timer = Timer()
    private var counter: Int = 0

    func start() 
        isPaused.toggle();
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true)  timer in
           self.counter += 1
           self.stopWatchTime = StopWatch.convertCountToTimeString(counter: self.counter)
        
    


    func pause() 
        isPaused.toggle();
        timer.invalidate()
    


    func reset() 
        self.timer.invalidate()
        self.isPaused = true;
        self.counter = 0;
        self.stopWatchTime = "00:00:00"
    

编辑这是秒表在视图中的使用方式

struct StopWatchUI: View 
    @ObservedObject var stopWatch = StopWatch()
    @Binding var isSureToResetWatch: Bool;
    @EnvironmentObject var model: Model

    var body: some View 

        return VStack 

            Text(self.stopWatch.stopWatchTime)
                .foregroundColor(.textPrimary)
                .font(.custom("Courier",size: model.isPhoneAndLandscape ? 50 : 120))
                .fontWeight(.heavy)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: nil, maxHeight: nil)
                .padding(15)
                .minimumScaleFactor(0.4)
                .cornerRadius(5)
                .lineLimit(1)

            HStack 
                StopWatchResetButton(isSureToResetWatch: $isSureToResetWatch, resetTime: self.stopWatch.reset)
                Spacer()
                StopWatchStartButton(
                    start: self.stopWatch.start,
                    pause: self.stopWatch.pause,
                    isStopWatchPaused: self.stopWatch.isPaused
                )
            
        
    

【问题讨论】:

如何链接StopWatch 的实例和您的视图? StopWatch 实例应该是您的Model 的一部分。此外,使用计时器增加计数器也不准确;由于Timer 不准确,您将获得相当大的偏差。您应该使用 start Date 而不是计数器来跟踪经过的时间。 谢谢。我用StopWatch 的实例编辑问题。感谢您的建议,我会尝试重构以使用Date 按照我说的将秒表移到模型上,这样你就不会在每次创建新的视图实例时都创建一个新的秒表实例。 【参考方案1】:

由于您的StopWatch 是您视图中的一个属性,因此每次创建您的视图的新实例时都会创建一个新的秒表实例,例如当设备布局发生变化时。

您的秒表应该是您的Model 的财产。您的 Model 实例存在于环境中,因此它的生命周期就是宿主视图控制器的生命周期:

class Model: ObservableObject 
    @Published var isLandScape: Bool = false
    @Published var isPhone: Bool =  UIDevice.current.userInterfaceIdiom == .phone
    @Published var isPhoneAndLandscape: Bool = false;
    var stopwatch = StopWatch()

【讨论】:

我不得不将整个 StopWatch 类移动到 Model 内,因为在 Model 内创建 StopWatch 的新实例只是在方向更改时更新 UI,但现在它位于功能最少。 您不需要这样做。您需要将您的文本内容绑定到self.model.stopwatch.$stopWatchTime.value

以上是关于SwiftUI:在设备方向上重绘会使计时器无效的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:具有计时器样式的文本在提供的日期更改后不会更新

Timer 计时器 (SwiftUI中文文档手册)

SwiftUI - 如何制作启动/停止计时器

使用计时器切换图像 (SwiftUI)

导航到其他页面后,如何在 SwiftUI 中恢复已发布的计时器?

Swiftui NavigationView 计时器延迟