SwiftUI: Как создать настраиваемый таймер обратного отсчета и его отдельный вид - PullRequest
0 голосов
/ 23 апреля 2020

Я видел много блогов / статей о том, как построить таймер с помощью Swift UI. Но я не могу понять, что я действительно хочу работать.

2 основные проблемы, с которыми я сталкиваюсь: 1. Мой TimerView перестраивается всякий раз, когда его родительский вид перестраивается из-за изменения состояний 2. Я не могу отправить параметры в мое свойство @ObservedObject TimeCountDown (2 параметра: длительность, исходящая из другого представления, и завершение onEnded)

class Round: ObservableObject {
   @Publishedvar items: [Item]
   var duration: Double
   init(items: [Item], duration: Double) {
      self.items = items
      self.duration = duration
   }
}

Struct ParentView: View {
  @ObservedObject var round: Round
  @State private var isRoundTerminated: Bool = false

  var body: Some View {
    VStack {
       if isRoundTerminated {
         RoundEndView()
       } else {
         TimerView(duration: round.duration, onEnded: onTimerTerminated)
         RoundPlayView(items: round.items)
       }  
    }
 }
}

struct TimerView: View {
   @ObservedObject countdown = TimeCountDown()
   var duration: Double
   var onEnded: (() -> Void)?

   ///// I DO NOT KNOW HOW TO PROPAGATE THE onEnded completion TO countdown:TimeCountDown

   var body: Some View {
      Text("There are \(countdown.remainingTime) remaining secs")
         .onAppend() {
             timer.start(duration: duration)
             /// MAYBE I COULD ADD THE onEnded WITHIN THE start() CALL?
         }
   }
}

class TimeCountDown : ObservableObject {
   var timer : Timer!
   @Published var remainingTime: Double = 60
   var onEnded: (() -> Void)?

   init(onEnded: @escaping (() -> Void)?) {
      self.onEnded = onEnded
   }

   func start(duration: Double) {
     self.timer?.invalidate()
     self.remainingTime = duration
     timer = Timer.scheduledTimer(timeInterval:1, target: self, selector:#selector(updateCountdown), userInfo: nil, repeats: true)
   }

  @objc private func updateCount() {
     remainingTime -= 1

     if remainingTime <= 0 {
        killTimer()
        self.onEnded?()
     }
  }

 private func killTimer() {
   timer?.invalidate()
   timer = nil
 }

}

Однако , что не работа ...

Я также пытался реализовать следующий TimerView:

struct CountdownView: View {
   @State private var remainingTime: Int = 60
   @Binding var countingDown: Bool

   var onEnded: (() -> Void)?

   let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

   init(durations: TimerDuration, onEnded: (() -> Void)?, start: Binding<Bool>) {
       self.onEnded = onEnded
       self._countingDown = start
       self.remainingTime = durations.duration
   }

   var body: some View {
       Text("Remaining \(remainingTime) secs")
           .onReceive(timer) {_ in
               if self.countingDown {
                   if self.remainingTime > 0 {
                       self.remainingTime -= 1
                   } else {
                       self.onTerminated()
                   }
               }
       }
   }

   func onTerminated() {
       timer.upstream.connect().cancel()
       self.remainingTime = 0
       onEnded?()
   }
}

Однако, когда ParentView перестраивается очень часто (из-за изменений в round.items (@Published from Round: ObservableObject) таймер может быть заморожен.

1 Ответ

0 голосов
/ 23 апреля 2020

@ George_E, пожалуйста, найдите ниже мой код обновления (обратите внимание, что я упростил забаву nextWord () c (но здесь это бесполезно), но она изменяет свойство round.remainingDeck, которое является свойством Опубликовать в раунде ObervableObject. Это вызывает перестройку RoundPlayView и останавливает таймер):

import SwiftUI
import Combine

class Round: ObservableObject {
   @Published var remainingDeck: [String] = ["Round#1", "Round#2", "Round#3", "Round4"]
   var roundDuration: Int = 5
}

struct ContentView: View {
  @ObservedObject var round = Round()

  var body: Some View {
      RoundPlayView(round: round)
  }
}

struct RoundPlayView: View {

   @ObservedObject var round: Round
   var duration: Int {
       return round.roundDuration
   }
   @State private var timerStart: Bool = true
   @State private var isTerminated: Bool = false

   init(round: Round) {
       self.round = round
   }

   var body: some View {
       return VStack {
           if self.isTerminated {
               EmptyView()
           } else {
               VStack {
                   CountdownView(duration: duration, onEnded: timerTerminated, start: $timerStart)

                   Button(action: { self.addWord() }) {
                       Text("Add words to the deck")
                   }
              }
           }
       }
   }


   private func nextWord() {
       round.remainingDeck.append("New Word")
   }

   private func timerTerminated() {
       terminateRound()
   }

   private func terminateRound() {
       self.isTerminated = true
   }
}

, а вот мой код TimerView:

struct CountdownView: View {
   @State private var remainingTime: Int = 60
   @Binding var countingDown: Bool

   var onEnded: (() -> Void)?

   let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

   init(duration: Int, onEnded: (() -> Void)?, start: Binding<Bool>) {
       self.onEnded = onEnded
       self._countingDown = start
       self.remainingTime = duration
   }

   var body: some View {
       Text("Remaining \(remainingTime) secs")
           .onReceive(timer) {_ in
               if self.countingDown {
                   if self.remainingTime > 0 {
                       self.remainingTime -= 1
                   } else {
                       self.onTerminated()
                   }
               }
       }
  }

  func onTerminated() {
       timer.upstream.connect().cancel()
       self.remainingTime = 0
       onEnded?()
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...