Translation of www.hackingwithswift.com/books/ios-s…

For more content, please follow the public account “Swift Garden”.

Like articles? How about πŸ”ΊπŸ’›βž• company 3? Follow this column, follow me πŸš€πŸš€πŸš€

Use Timer to count down

If we Combine the Foundation, SwiftUI and Combine frameworks, we can add a timer to the app to put a little pressure on the user. The simplest implementations are almost effortless, but there is a bug that needs to be fixed.

First we create two new properties: the timer itself, which launches once per second, and a timeRemaining property, which decays by 1 with each launch of the timer. This tells the user how much time is left in the current cycle to remind them to accelerate.

Add the following two new properties to the ContentView:

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

We create and start a timer on the main thread, each shot, giving the user 100 seconds to respond.

Whenever the timer launches, we need to subtract 1 from the timeRemaining. Of course, we could count down the time by date, but that’s not necessary.

Add an onReceive() modifier to the Outermost layer of the ContentView ZStack.

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

Tip: Add a test condition to make sure we don’t run into negative values.

Our timer goes from 100 to 0, but we still need to display that number.

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

If you put it in the right place, the layout code should now look like this:

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 {
Copy the code

Run the app again — it seems to be working, right? But there’s a slight problem:

  1. View the current time
  2. Hit Cmd+H to return to the home screen
  3. Wait 10 seconds
  4. Click the app icon to return to the app
  5. What time is displayed on the timer?

I found that the timer had three seconds less time left than when we left the app earlier — meaning the timer ran in the background for a few seconds and then paused — until the app came back.

We can optimize it by pausing or restarting the timer when the application is in the background or foreground.

First, add this property to store whether the application is currently active or not:

@State private var isActive = true
Copy the code

Next, we need to add two more onReceive() modifier to maintain isActive for the application. To this, we can capture the UIApplication. WillResignActiveNotification and UIApplication willEnterForegroundNotification notice, as follows:

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

Finally, modify the onReceive(timer) function so that it does not count down when isActive, like this:

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

With this small change, the countdown will automatically pause when the app goes into the background — we won’t mysteriously lose seconds anymore.


My official account here Swift and computer programming related articles, as well as excellent translation of foreign articles, welcome to pay attention to ~