SwiftUI:如何建立一个可自定义的定时器倒计时及其单独的视图



我看到很多关于如何使用Swift UI构建计时器的博客/文章。但我想不出能得到我真正想要的工作。

我面临的两个主要问题是: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(,计时器可能会被冻结。

@George-E,请在下面找到我的更新代码(请注意,我简化了nextWord((函数(这里是一个但没有用的函数(,但它确实修改了round.remainingDeck,这是ObervableObject round中的一个Published属性。这会触发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?()
}
}

最新更新