TimerView
Last updated
ๅ๏ผใ ๐๏ธ ้ ่ฆฝไธญ็ๅ ฉๅ timer ็ซ็ถไธๆฏๅ่ช็จ็ซ๏ผ่ๆฏไธๅไธๅพโใ
2022.03.24
๐ TimerView
Last updated
โฉ โฉ TimerView
โฉ โญ๏ธ
โฉ โฉ (class)
-> Timer.
Timer. (class)
-> .<Timer.TimerPublisher>
โฉ (protocol) โฉ
โญ๏ธ - connects to the publisher, allowing it to produce elements, and returns an instance with which to cancel publishing.
do with .
example for how to implement .
compare:
่จป๏ผๅจ้กไผผ็ ไธญไธฆๆฒๆ้็จฎ็พ่ฑกใ
// 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)
}
}
}
struct ContentView: View {
@State private var progress: CGFloat = 0
@State private var timeLeft = 5
let sec: TimeInterval = 4
var body: some View {
HStack {
// TimerView 1
TimerView(
every: 1,
update: {_ in
if timeLeft > 0 { timeLeft -= 1 }
}
) {
Text("\(timeLeft)")
.font(.system(.title3, design: .rounded))
.frame(width: 80, height:40)
.background(Capsule().fill(.pink))
}
.padding(.trailing, 20)
// TimerView 2
TimerView(
every: sec/100,
update: { _ in
if progress < 1 { progress += 0.01 }
}
) {
ZStack {
Circle()
.stroke(Color(.systemGray5), lineWidth: 14)
Circle()
.trim(from: 0, to: progress)
.stroke(.green, lineWidth: 12)
.rotationEffect(.degrees(-90))
Text("\(Int(progress * 100))%")
.font(.system(.title3, design: .rounded))
}
}
.frame(100)
}
.padding()
}
}
struct ContentView: View {
var body: some View {
HStack {
// โญ๏ธ timer 1 (default timer content)
TimerView(seconds: 10)
.font(.system(.body, design: .monospaced))
// โญ๏ธ timer 2 (custom timer content)
TimerView(seconds: 15) { sec in
Text("\(sec)")
.font(.title2)
.foregroundColor(.white)
.padding(.horizontal, 30)
.padding(.vertical, 5)
.background(.pink.opacity(0.75))
.clipShape(Capsule())
}
// โญ๏ธ timer 3 (custom timer content)
TimerView(seconds: 20) { sec in
ZStack {
Color.blue
Text("\(sec)")
.fixedSize()
}
.frame(50)
.border(.secondary)
}
}
.padding(8)
}
}
import SwiftUI
/// ๐ TimerView
/// ```
/// TimerView(seconds: 10)
/// TimerView(seconds: 10) { sec in ... }
/// ```
struct TimerView<Content: View>: View {
// seconds remaining
@State private var timeRemaining: Int
// โญ๏ธ timer that fires once a second on the main thread.
let timer = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect() // โญ๏ธ auto-connect when subscribed
// โญ๏ธ given seconds remaining, generate timer content
@ViewBuilder var content: (Int) -> Content
// โญ๏ธ detect whether app has gone background
@Environment(\.scenePhase) private var scenePhase
@State private var isActive = true
var body: some View {
content(timeRemaining)
// โญ๏ธ whenever `timer` fires, subtract 1 from `timeRemaining`
.onReceive(timer) { time in
// โญ๏ธ if app goes background, stop count-down immediately.
guard isActive else { return }
if timeRemaining > 0 { timeRemaining -= 1 }
}
// โญ๏ธ mark the app inactive once it goes background.
.onChange(of: scenePhase) { newPhase in
isActive = (newPhase == .active)
}
}
}
// โญ๏ธ timer with custom content
extension TimerView {
/// `TimerView(seconds: 10) { sec in ... }`
init(
seconds: Int,
@ViewBuilder content: @escaping (Int) -> Content
){
// โญ๏ธ initialize an @State property
_timeRemaining = State(initialValue: seconds)
self.content = content
}
}
// โญ๏ธ timer with default content
extension TimerView where Content == Text
{
/// `TimerView(seconds: 10)`
init(seconds: Int){
self.init(seconds: seconds) { sec in
Text("\(sec)") as! Content
}
}
}