👔TimerView

CombineTimer ⟩ TimerView

// 2022.03.24: refactored to be more flexible

// ------------------------------------------------------
// ⭐️ 潛藏問題:
//    如果同時使用兩個 TimerView,timer 竟然不是各自獨立❓
//    ( 👉 參看:「👁️ 預覽」)
// ------------------------------------------------------

import SwiftUI
import Combine

/// 👔 TimerView
/// ```
/// TimerView(every: 1) { ... }
/// TimerView(every: 1, update: { time in ... }) { ... }
/// ```
struct TimerView<Content: View>: View {
    
    // ⭐️ time interval on which to publish events
    var interval: TimeInterval = 1
    
    // ⭐️ update with time
    var update: (Date) -> Void = { _ in }
    
    // ⭐️ generate timer content
    @ViewBuilder var content: () -> Content 
    
    // ⭐️ timer that fires on the main thread.
    let timer: Publishers.Autoconnect<Timer.TimerPublisher>
        
    // init
    init(
        every interval: TimeInterval, 
        update: @escaping (Date) -> Void = {_ in},     // default: do nothing
        @ViewBuilder content: @escaping () -> Content
    ) {
        self.content = content
        self.update = update
        self.timer = Timer
            .publish(every: interval, on: .main, in: .common)
            .autoconnect()    // ⭐️ auto-connect when subscribed
    }
    
    // ⭐️ detect whether app has gone background
    @Environment(\.scenePhase) private var scenePhase
    @State private var isActive = true
    
    var body: some View {
        content()
            // ⭐️ whenever `timer` fires, update with time
            .onReceive(timer) { time in
                // ⭐️ if app goes background, stop updating immediately.
                guard isActive else { return }
                // ⭐️ update with time
                update(time)
            }
            // ⭐️ mark the app inactive once it goes background.
            .onChange(of: scenePhase) { newPhase in
                isActive = (newPhase == .active)
            }
    }
}

History

  1. 2022.03.24

Last updated

Was this helpful?