Moi
Moi

Reputation: 43

Sound when timer stops swiftUI Apple Watch

I'm working currently on a timer app for the Apple Watch. I'm managed to get a sound when the timer reaches 5 seconds. Only, when I'm pressing "done" the timer sounds still plays and I don't now how to stop the sound. I'm using WKInterfaceDevice.current().play(.success).

I want to stop the sound when the "cancel" button is pressed within the 5 seconds and when the "done" button is pressed.

I cannot find anything on the internet. I think that WKInterfaceDevice does not have an stop function.

struct softView: View {

@State var timerVal = 10

var body: some View {

    VStack {

        if timerVal > 0 {
            Text("Time Remaining")
                .font(.system(size: 14))
            Text("\(timerVal)")
                .font(.system(size: 40))

                .onAppear(){
                    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                        if self.timerVal > 0 {
                            self.timerVal -= 1
                        }
                        if self.timerVal < 6 {
                            WKInterfaceDevice.current().play(.success)
                        }
                    }
            }
            Text("Seconds")
                .font(.system(size: 14))
            Divider()
            Spacer()

            NavigationLink(destination: ContentView(), label: {Text("Cancel")})
            //.cornerRadius(20)
            .foregroundColor(Color.red)
            .background(
                RoundedRectangle(cornerRadius: 20)
                    .stroke(Color.red, lineWidth: 2)

            )



        }
        else {

            NavigationLink(destination: ContentView(), label: {Text("Done")})
                .foregroundColor(Color.green)
                .background(
                    RoundedRectangle(cornerRadius: 20)
                        .stroke(Color.green, lineWidth: 2)

                )
        }

    }         .navigationBarHidden(true)

}

Upvotes: 1

Views: 1260

Answers (1)

staticVoidMan
staticVoidMan

Reputation: 20274

When does your timer know when to stop?
You have to define the event when the timer is to be stopped. That's where.invalidate will come handy.

Basic Example:

Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] (timer) in
  guard let _weakSelf = self else { timer.invalidate(); return }

  _weakSelf.timerVal -= 1

  if _weakSelf.timerVal < 0 { //if timer count goes negative then stop timer
    timer.invalidate()
  } else if _weakSelf.timerVal < 6 {
    WKInterfaceDevice.current().play(.success)
  }
}

For more control, i.e. If you want to stop the timer from, say, a button tap then we will have to make this timer object global.
Furthermore, if you want to pop the View after the timer is completed/cancelled then we need to make more changes.
This all gets a bit complicated but it's simple to understand.
I would suggest you to break out your timer related logic into an ObservableObject class and use it within your View.

Example:

struct ContentView: View {
  @State var isShowingTimer: Bool = false

  var body: some View {
    NavigationView {
      NavigationLink(destination: TimerView(isShowing: $isShowingTimer),
                     isActive: $isShowingTimer) {
                      Text("Start Timer")
      }
    }
  }
}
  • isShowingTimer controls the push/pop event for TimerView
    • It is sent as a binding to TimerView so it can be updated from inside TimerView in order to pop.

struct TimerView: View {
  //Trigger for popping this view
  @Binding var isShowing: Bool

  @ObservedObject var timerControl = TimerControl()

  var body: some View {
    VStack {
      Text("\(timerControl.count)")
        .onAppear(perform: {
          //start timer event
          self.timerControl.startTimer(from: 10)
        })
        .onDisappear(perform: {
          //stop timer if user taps on `Back` from the Navigation Bar
          self.timerControl.stopTimer()
        })
        .onReceive(timerControl.$isComplete, //observe timer completion trigger
                   perform: { (success) in
                    //hide this view
                    self.isShowing = !success
        })
        Text("Cancel")
          .onTapGesture(perform: {
            //stop timer event
            self.timerControl.stopTimer()
        })
    }
  }
}

class TimerControl: ObservableObject {
  @Published var count: Int = 0
  @Published var isComplete: Bool = false

  private var timer: Timer?

  init(){}

  func startTimer(from count: Int) {
    self.count = count

    timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] (timer) in
      guard let _weakSelf = self else { timer.invalidate(); return }

      print(_weakSelf.count)
      _weakSelf.count -= 1

      if _weakSelf.count <= 0 {
        _weakSelf.stopTimer()
      } else if _weakSelf.count < 6 {
        print(">>make some noise here<<")
      }
    }
  }

  func stopTimer() {
    guard isComplete == false else { return }
    timer?.invalidate()
    isComplete = true
  }
}

  • ObservableObject class can emit changes
  • @Published variables emit signals of change
  • @ObservedObject listens for changes on the ObservableObject
  • .onReceive handles Publisher events. In this case listens for changes by timerControl.$isComplete

Upvotes: 1

Related Questions