> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/ios/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/ios/swift/scope/framework/built-in-frameworks/combine/timer/cancellable-timer.md).

# cancellable timer

{% tabs %}
{% tab title="💾 程式" %}
{% embed url="<https://youtu.be/7hlO52XBq5g>" %}

📗 參考：Paul ⟩ [Counting down with a Timer](https://www.hackingwithswift.com/books/ios-swiftui/counting-down-with-a-timer)

👥 相關：[.onChange()](https://developer.apple.com/documentation/swiftui/view/onchange\(of:perform:\)), [.onRecieve()](https://developer.apple.com/documentation/swiftui/view/onreceive\(_:perform:\)) [events](/ios/swiftui/view/events.md).

```swift
import SwiftUI

struct TimerView: View {
    
    // gives the user 100 seconds to start with
    @State private var timeRemaining = 100
    @State private var isTimerStopped = false
    @State private var isTimerCancelled = false
    
    // ⭐️ timer that fires once a second on the main thread.
    let timer = Timer
        // ⭐️ Timer.TimerPublisher
        .publish(
            every: 1,       // timer fires every 1 second.
            on: .main,      // run on the .main thread (UI thread).
            in: .common     // run on the .common run loop.
            // Run loop lets iOS handle running code while the user is actively
            // doing something, such as scrolling in a list
        )
        // ⭐️ connect automatically when a subscriber attaches
        .autoconnect()
    
    // ⭐️ detect whether app has gone background
    @Environment(\.scenePhase) private var scenePhase
    @State private var isActive = true
    
    var body: some View {
        VStack {
            timerView
            HStack {
                stopButton
                cancelButton
            }
        }
        .padding(8)
        .border(.secondary)
    }
    
    /// text for timer
    var text: some View {
        Text("Time: \(timeRemaining)")
            .font(.largeTitle)
            .foregroundColor(isTimerCancelled ? .secondary : .white)
            .padding(.horizontal, 20)
            .padding(.vertical, 5)
            .background(.pink.opacity(0.75))
            .clipShape(Capsule())
    }
    
    /// timer view
    var timerView: some View {
        text
            // ⭐️ connect `timer` automatically❓
            .onReceive(timer) { time in
                // ⭐️ if app goes background or timer stopped, stop count-down immediately.
                guard isActive && !isTimerStopped else { return }
                // ⭐️ count down
                if timeRemaining > 0 { timeRemaining -= 1 }
            }
            // ⭐️ mark the app inactive once it goes background.
            .onChange(of: scenePhase) { newPhase in
                isActive = (newPhase == .active)
            }
    }
    
    /// Stop/Resume button
    var stopButton: some View {
        Button {
            isTimerStopped.toggle()
        } label: {
            Text(isTimerStopped ? "Resume" : "Stop")
        }
        .disabled(isTimerCancelled)
    }
    
    /// cancel button
    var cancelButton: some View {
        Button {
            // ⭐️ cancel the timer
            timer               // Publishers.Autoconnect<Timer.TimerPublisher>
                .upstream       // Timer.TimerPublisher (ConnectablePublisher)
                .connect()      // allow to produce elements, return an instance (Cancellable)
                .cancel()       // ⭐️
            isTimerCancelled = true
        } label: {
            Text("Cancel")
        }
        .disabled(isTimerCancelled)
    }
}
```

{% endtab %}

{% tab title="📗 參考" %}

* [x] Paul ⟩&#x20;
  * [x] [Counting down with a Timer](https://www.hackingwithswift.com/books/ios-swiftui/counting-down-with-a-timer)
  * [x] [Triggering events repeatedly using a timer](https://www.hackingwithswift.com/books/ios-swiftui/triggering-events-repeatedly-using-a-timer)
* [x] [Combine](https://developer.apple.com/documentation/combine) ⟩ [Replacing Foundation Timers with Timer Publishers](https://developer.apple.com/documentation/combine/replacing-foundation-timers-with-timer-publishers) ⭐️
* [ ] Ray ⟩ Combine: Asynchronous Programming with Swift, [Ch. 11: Timers](https://www.raywenderlich.com/books/combine-asynchronous-programming-with-swift/v3.0/chapters/11-timers)
* [ ] Sundell ⟩ [Managing self and cancellable references when using Combine](https://www.swiftbysundell.com/articles/combine-self-cancellable-memory-management/)
  {% endtab %}

{% tab title="📘 手冊" %}

* [Foundation ](https://developer.apple.com/documentation/foundation)⟩ [Task Management](https://developer.apple.com/documentation/foundation/task_management) ⟩ [Timer](https://developer.apple.com/documentation/foundation/timer) (<mark style="color:red;">**class**</mark>)
  * [.publish(every:tolerance:on:in:options:)](https://developer.apple.com/documentation/foundation/timer/3329589-publish) -> <mark style="color:purple;">**Timer**</mark>.[<mark style="color:orange;">**TimerPublisher**</mark>](https://developer.apple.com/documentation/foundation/timer/timerpublisher)
  * <mark style="color:purple;">**Timer**</mark>.[<mark style="color:orange;">**TimerPublisher**</mark>](https://developer.apple.com/documentation/foundation/timer/timerpublisher) (<mark style="color:red;">**class**</mark>)
    * [.autoconnect()](https://developer.apple.com/documentation/foundation/timer/timerpublisher/3329490-autoconnect) -> [Publishers](https://developer.apple.com/documentation/combine/publishers).[<mark style="color:orange;">**Autoconnect**</mark>](https://developer.apple.com/documentation/combine/publishers/autoconnect)`<Timer.TimerPublisher>`&#x20;
* [Combine](https://developer.apple.com/documentation/combine) ⟩ [ConnectablePublisher](https://developer.apple.com/documentation/combine/connectablepublisher) (<mark style="color:orange;">**protocol**</mark>) ⟩&#x20;
  * [.connect()](https://developer.apple.com/documentation/combine/connectablepublisher/connect\(\)) ⭐️ - connects to the publisher, allowing it to <mark style="color:orange;">**produce elements**</mark>, and <mark style="color:red;">**returns an instance**</mark> with which to <mark style="color:orange;">**cancel**</mark> publishing.
* [SwiftUI](https://developer.apple.com/documentation/swiftui) ⟩&#x20;
  * [Scenes](https://developer.apple.com/documentation/swiftui/scenes) ⟩ [ScenePhase](https://developer.apple.com/documentation/swiftui/scenephase) (<mark style="color:red;">**enum**</mark>)
  * [State](https://developer.apple.com/documentation/swiftui/state-and-data-flow) ⟩ [EnvironmentValues](https://developer.apple.com/documentation/swiftui/environmentvalues) (<mark style="color:red;">**struct**</mark>) ⟩ .[scenePhase](https://developer.apple.com/documentation/swiftui/environmentvalues/scenephase) (instance property)
  * [View](https://developer.apple.com/documentation/swiftui/view) ⟩ [Input and Event Modifiers](https://developer.apple.com/documentation/swiftui/view-input-and-events) ⟩&#x20;
    * [.onReceive(\_:perform:)](https://developer.apple.com/documentation/swiftui/view/onreceive\(_:perform:\)) - perform action when <mark style="color:orange;">**emitted data**</mark> detected.
    * [.onChange(of:perform:)](https://developer.apple.com/documentation/swiftui/view/onchange\(of:perform:\)) - perform action when <mark style="color:orange;">**specific value**</mark> changes.
      {% endtab %}

{% tab title="👥 相關" %}

* use [@Environment](/ios/swiftui/view/environment/environment.md) [values](https://developer.apple.com/documentation/swiftui/environmentvalues).
* use <mark style="color:purple;">**Timer**</mark>.[<mark style="color:orange;">**TimerPublisher**</mark>](https://developer.apple.com/documentation/foundation/timer/timerpublisher) ([`ConnectablePublisher`](https://developer.apple.com/documentation/combine/connectablepublisher)) [Publisher](/ios/swift/scope/framework/built-in-frameworks/combine/publishers.md).
  {% endtab %}
  {% endtabs %}
