简体   繁体   English

SwiftUI 计时器视图暂停屏幕上的所有其他视图

[英]SwiftUI timer view pausing all other views on screen

I am curious if there is a way to get the timers to update with the UI (as they are now) even while someone is scrolling.我很好奇即使有人在滚动,是否有办法让计时器用 UI 更新(就像现在一样)。 Additionally, I want to make sure that as the UI updates each second the screen does not freeze.此外,我想确保随着 UI 每秒更新一次,屏幕不会冻结。 This question has been updated in response to the helpful answers I received previously.此问题已根据我之前收到的有用答案进行了更新。

struct ContentView: View {
    var body: some View {
        NavigationView{
            NavigationLink(destination: ScrollTest()){
                HStack {
                     Text("Next Screen")
                }
            }
        }
    }
}
struct ScrollTest: View {
    @ObservedObject var timer = SectionTimer(duration: 60)
    var body: some View {
        HStack{
            List{
                Section(header: TimerNavigationView(timer: timer)){
                    ForEach((1...50).reversed(), id: \.self) {
                        Text("\($0)…").onAppear(){
                            self.timer.startTimer()
                        }

                    }
                }
            }.navigationBarItems(trailing: TimerNavigationView(timer: timer))
        }

    }
}
struct TimerNavigationView: View {
    @ObservedObject var timer: SectionTimer
    var body: some View{
        HStack {
            Text("\(timer.timeLeftFormatted) left")
            Spacer()
        }
    }
}
class SectionTimer:ObservableObject {
    private var endDate: Date
    private var timer: Timer?
    var timeRemaining: Double {
        didSet {
            self.setRemaining()
        }
    }
    @Published var timeLeftFormatted = ""

    init(duration: Int) {
        self.timeRemaining = Double(duration)
        self.endDate = Date().advanced(by: Double(duration))
        self.startTimer()

    }

    func startTimer() {
        guard self.timer == nil else {
            return
        }
        self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { (timer) in
            self.timeRemaining = self.endDate.timeIntervalSince(Date())
            if self.timeRemaining < 0 {
                timer.invalidate()
                self.timer = nil
            }
        })
    }

    private func setRemaining() {
        let min = max(floor(self.timeRemaining / 60),0)
        let sec = max(floor((self.timeRemaining - min*60).truncatingRemainder(dividingBy:60)),0)
        self.timeLeftFormatted = "\(Int(min)):\(Int(sec))"
    }

    func endTimer() {
        self.timer?.invalidate()
        self.timer = nil
    }
}

Although SwiftUI has a timer, I don't think using it is the right approach in this case.虽然 SwiftUI 有一个计时器,但我认为在这种情况下使用它不是正确的方法。

Your model should be handling the timing for you.您的 model 应该为您处理时间。

It also helps if your view observes its model object directly rather than trying to observe a member of an array in a property of your observable.如果您的视图直接观察其 model object 而不是尝试观察可观察对象的属性中的数组成员,这也会有所帮助。

You didn't show your SectionTimer , but this is what I created:您没有显示您的SectionTimer ,但这是我创建的:

class SectionTimer:ObservableObject {
    private var endDate: Date
    private var timer: Timer?
    var timeRemaining: Double {
        didSet {
            self.setRemaining()
        }
    }

    @Published var timeLeftFormatted = ""

    init(duration: Int) {
        self.timeRemaining = Double(duration)
        self.endDate = Date().advanced(by: Double(duration))
        self.startTimer()

    }

    func startTimer() {
        guard self.timer == nil else {
            return
        }
        self.timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { (timer) in
            self.timeRemaining = self.endDate.timeIntervalSince(Date())
            if self.timeRemaining < 0 {
                timer.invalidate()
                self.timer = nil
            }
        })
    }

    private func setRemaining() {
        let min = max(floor(self.timeRemaining / 60),0)
        let sec = max(floor((self.timeRemaining - min*60).truncatingRemainder(dividingBy:60)),0)
        self.timeLeftFormatted = "\(Int(min)):\(Int(sec))"
    }

    func endTimer() {
        self.timer?.invalidate()
        self.timer = nil
    }
}

It uses a Date rather than subtracting from a remaining counter;它使用Date而不是从剩余的计数器中减去; this is more accurate as timer's don't tick at precise intervals.这更准确,因为计时器不会以精确的时间间隔滴答作响。 It updates a timeLeftFormatted published property.它更新timeLeftFormatted已发布属性。

To use it I made the following changes to your TimerNavigationView -为了使用它,我对您的TimerNavigationView进行了以下更改 -

struct TimerNavigationView: View {
    @ObservedObject var timer: SectionTimer
    var body: some View{
        HStack {
            Text("\(timer.timeLeftFormatted) left")
            Spacer()
        }
    }
}

You can see how putting the timer in the model vastly simplifies your view.您可以看到将计时器放入 model 中如何极大地简化您的视图。

You would use it via .navigationBarItems(trailing: TimerNavigationView(timer: self.test.timers[self.test.currentSection]))您可以通过.navigationBarItems(trailing: TimerNavigationView(timer: self.test.timers[self.test.currentSection]))使用它

Update更新

The updated code in the question helped demonstrate the issue, and I found the solution in this answer问题中的更新代码有助于演示该问题,我在此答案中找到了解决方案

When the scrollview is scrolling the mode of the current RunLoop changes and the timer is not triggered.当滚动视图正在滚动时,当前RunLoop的模式会发生变化,并且不会触发计时器。

The solution is to schedule the timer in the common mode yourself rather than relying on the default mode that you get with scheduledTimer -解决方案是自己在common模式下安排计时器,而不是依赖于scheduledTimer获得的default模式 -

func startTimer() {
    guard self.timer == nil else {
        return
    }

    self.timer = Timer(timeInterval: 0.2, repeats: true) { (timer) in
        self.timeRemaining = self.endDate.timeIntervalSince(Date())
        if self.timeRemaining < 0 {
            timer.invalidate()
            self.timer = nil
        }
    }
    RunLoop.current.add(self.timer!, forMode: .common)
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM