[SwiftUI 100 天] 用 Timer 倒计时

1,479 阅读3分钟

译自 www.hackingwithswift.com/books/ios-s…

更多内容,欢迎关注公众号 「Swift花园」

喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

用 Timer 倒计时

如果我们结合 Foundation,SwiftUI 和 Combine 框架,可以添加一个定时器到应用中,给用户一点压力。最简单的实现几乎不费力就能做到,但是有一个 bug 需要解决。

首先我们要创建两个新属性:定时器本身,它每秒发射一次,以及一个 timeRemaining 属性,随着定时器的发射每次自减 1,这样就可以告诉用户当前轮次还剩多少时间可用,以便提醒他们加速。

把下面两个新属性添加到 ContentView

@State private var timeRemaining = 100
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

我们在主线程创建和启动定时器,每条发射,给用户 100 秒来做出回答。

每当定时器发射时,我们需要从 timeRemaining 减 1。当然,我们也可以借助日期的计算来实现倒计时,但这里完全没有必要。

ContentView 最外层的 ZStack 添加一个 onReceive() modifier。

.onReceive(timer) { time in
    if self.timeRemaining > 0 {
        self.timeRemaining -= 1
    }
}

提示: 添加一个测试条件确保我们不会跑进负值。

我们的定时器从 100 倒计到 0,但还需要显示这个数字。

Text("Time: \(timeRemaining)")
    .font(.largeTitle)
    .foregroundColor(.white)
    .padding(.horizontal, 20)
    .padding(.vertical, 5)
    .background(
        Capsule()
            .fill(Color.black)
            .opacity(0.75)
    )

如果你放的位置是正确的,现在的布局代码应该是这样的:

ZStack {
    Image("background")
        .resizable()
        .scaledToFill()
        .edgesIgnoringSafeArea(.all)

    VStack {
        Text("Time: \(timeRemaining)")
            .font(.largeTitle)
            .foregroundColor(.white)
            .padding(.horizontal, 20)
            .padding(.vertical, 5)
            .background(
                Capsule()
                    .fill(Color.black)
                    .opacity(0.75)
            )

        ZStack {

再次运行应用 —— 看起来工作正常,对吧?不过这里有一个小问题:

  1. 查看当前的时间
  2. 点击 Cmd+H 回到主屏幕
  3. 等待 10 秒
  4. 点击应用图标回到应用
  5. 定时器显示的时间是?

我发现定时器剩余的时间比我们之前离开应用的时候少了 3 秒 —— 也就是说定时器在后台运行了几秒,然后就暂停了 —— 直到应用重新回来。

我们可以做出优化:当应用进入后台或者前台时,相应地暂停或者重启定时器。

首先,添加这个属性来存储应用当前是否激活的状态:

@State private var isActive = true

接下来,我们需要再添加两个 onReceive() modifier,用来维护应用的 isActive。对此,我们可以捕捉 UIApplication.willResignActiveNotificationUIApplication.willEnterForegroundNotification 通知,像下面这样:

.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
    self.isActive = false
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
    self.isActive = true
}

最后,修改 onReceive(timer) 函数,当 isActive 时不做倒计时,像这样:

.onReceive(timer) { time in
    guard self.isActive else { return }
    if self.timeRemaining > 0 {
        self.timeRemaining -= 1
    }
}

经过这个小修改,倒计时会在应用进入后台时自动暂停 —— 我们不会再神秘地丢失一些秒数。


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~

Swift花园微信公众号