当时间用完时,计时器会改变其大小,例如从 00:52 到 00:51,因为占用的空间较小。如何防止计时器遇到占用空间较小的数字时跳动?我想把它固定在中间,让它不能动。非常感谢。
import SwiftUI
import AVFoundation
struct TimerView: View {
@Binding var duration: TimeInterval
@Binding var isEMOTM: Bool // Neues Binding
@State private var remainingTime: TimeInterval = 0
@State private var preStartCountdownTime: TimeInterval = 10 // Umbenannte Variable für den initialen Countdown
@State private var timer: Timer?
@State private var isRunning: Bool = false
@State private var isCountdown: Bool = true
@State private var soundEffect: AVAudioPlayer?
var isCountUp: Bool = false
var isInterval: Bool = false
var isCustomTimer: Bool = false // Neues Flag für den CustomTimer
var highDuration: TimeInterval = 0
var lowDuration: TimeInterval = 0
var numberOfRounds: Int = 0
@State private var currentRound: Int = 0
@State private var isHighInterval: Bool = true
@Environment(\.presentationMode) var presentationMode
var body: some View {
GeometryReader { geometry in
let iconSize = min(geometry.size.width, geometry.size.height) * 0.15
VStack {
Spacer() // Push timer down
VStack {
Spacer()
Text(isCountdown ? "\(Int(preStartCountdownTime))" : timeString(remainingTime))
.font(.custom("digital-7", size: 150)) // Adjust .padding(.horizontal, 20)
.padding(.vertical, 5)
.background(.black.opacity(0.75))
.clipShape(.capsule)
.fixedSize() // Keep size constant
Spacer()
}
.frame(maxWidth: .infinity)
// Zeigt die aktuelle Runde an, nur wenn es ein Intervall- oder EMOTM-Timer ist
if isInterval || isEMOTM {
HStack {
// Text links unten ausrichten
Text("\(currentRound + 1) / \(numberOfRounds)")
.foregroundColor(.gray)
}
}
Spacer()
// Konfiguriert die Steuerungsschaltflächen
HStack(spacing: 20) {
Button(action: {
presentationMode.wrappedValue.dismiss() // Schließt die Ansicht
}) {
Image(systemName: "arrow.left.circle") // Symbol für Zurück
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
.padding()
.frame(width: iconSize, height: iconSize)
.background(Color.gray)
.cornerRadius(10)
}
Button(action: {
if isRunning {
stopTimer() // Stoppt den Timer
} else {
if isCountdown {
startCountdown() // Startet den Countdown
} else {
startTimer() // Startet den Timer
}
}
}) {
Image(systemName: isRunning ? "pause.circle" : (isCountdown ? "play.circle" : "play.circle")) // Symbol für Start/Pause
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
.padding()
.frame(width: iconSize, height: iconSize)
.background(isRunning ? Color.orange : Color.green)
.cornerRadius(10)
}
Button(action: {
resetTimer() // Setzt den Timer zurück
}) {
Image(systemName: "arrow.counterclockwise.circle") // Symbol für Zurücksetzen
.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
.padding()
.frame(width: iconSize, height: iconSize)
.background(Color.red)
.cornerRadius(10)
}
}
.padding(.horizontal)
}
.background(Color(.black)) // Hintergrundfarbe der Ansicht
}
.onAppear {
resetTimer() // Setzt den Timer zurück, wenn die Ansicht erscheint
}
}
// Startet den Countdown-Timer
private func startCountdown() {
stopTimer() // Stoppt den laufenden Timer
isRunning = true
isCountdown = true
preStartCountdownTime = 2 // Setzt die Countdown-Zeit (später ändern)
playSound(sound: "countdown") // Spielt den Countdown-Sound ab
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in // Erstellt einen wiederholenden Timer
if preStartCountdownTime == 4 {
playSound(sound: "racestart") // Spielt den Start-Sound ab, wenn 4 Sekunden verbleiben
}
if preStartCountdownTime > 0 {
preStartCountdownTime -= 1 // Reduziert die Countdown-Zeit um 1 Sekunde
print("Countdown: \(preStartCountdownTime)")
} else {
isCountdown = false
startTimer() // Startet den Haupt-Timer, wenn der Countdown endet
}
}
}
// Startet den Haupt-Timer
private func startTimer() {
stopTimer() // Stoppt den laufenden Timer
isRunning = true
playSound(sound: "start") // Spielt den Start-Sound ab
if isInterval {
remainingTime = isHighInterval ? highDuration : lowDuration // Setzt die Dauer für High- oder Low-Intervall
} else if isCustomTimer {
remainingTime = 0 // Setzt die Dauer für den CustomTimer
} else if isCountUp {
remainingTime = 0 // Setzt die Dauer für Count-Up
} else {
remainingTime = duration // Setzt die Dauer für Count-Down
}
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in // Erstellt einen wiederholenden Timer
if isInterval {
if remainingTime > 0 {
remainingTime -= 1 // Reduziert die verbleibende Zeit um 1 Sekunde
print("Timer running: \(remainingTime) seconds left")
} else {
if isHighInterval {
isHighInterval.toggle() // Wechselt zu Low-Intervall
remainingTime = lowDuration
print("Switching to low interval: \(remainingTime) seconds")
} else {
currentRound += 1 // Erhöht die Rundenzahl nach High- und Low-Intervall
if currentRound >= numberOfRounds {
stopTimer() // Stoppt den Timer, wenn alle Runden abgeschlossen sind
print("Completed all rounds")
return
}
isHighInterval.toggle() // Wechselt zu High-Intervall
remainingTime = highDuration
print("Switching to high interval: \(remainingTime) seconds")
}
}
} else if isCustomTimer {
if remainingTime < duration {
remainingTime += 1 // Erhöht die vergangene Zeit um 1 Sekunde
print("Custom Timer running: \(remainingTime) seconds passed")
} else {
stopTimer() // Stoppt den Timer, wenn die Dauer erreicht ist
print("Completed custom timer")
}
} else if isCountUp {
if remainingTime < duration {
remainingTime += 1 // Erhöht die vergangene Zeit um 1 Sekunde
print("Count-Up Timer running: \(remainingTime) seconds passed")
} else {
stopTimer() // Stoppt den Timer, wenn die Dauer erreicht ist
print("Completed count-up timer")
}
} else { //Countdown Struktur auch von Emom genutzt
if remainingTime > 0 {
remainingTime -= 1 // Reduziert die verbleibende Zeit um 1 Sekunde
print("Timer running: \(remainingTime) seconds left")
} else {
if isEMOTM {
currentRound += 1
if currentRound < numberOfRounds {
remainingTime = duration
} else {
stopTimer() // Stoppt den Timer
}
} else {
stopTimer() // Stoppt den Timer, wenn die Zeit abgelaufen ist
print("Timer completed")
}
}
}
}
}
// Setzt den Timer zurück
private func resetTimer() {
stopTimer() // Stoppt den laufenden Timer
if isInterval {
remainingTime = highDuration // Setzt die verbleibende Zeit auf die High-Intervall-Dauer
currentRound = 0 // Setzt die aktuelle Runde auf 0
isHighInterval = true
} else if isCustomTimer {
remainingTime = 0 // Setzt die verbleibende Zeit auf 0 für den CustomTimer
} else if isCountUp {
remainingTime = 0 // Setzt die verbleibende Zeit auf 0 für Count-Up
} else {
remainingTime = duration // Setzt die verbleibende Zeit auf die Dauer für Count-Down
}
isCountdown = true
preStartCountdownTime = 10 // Setzt den Countdown auf 10 Sekunden
print("Timer reset")
}
// Stoppt den Timer
private func stopTimer() {
isRunning = false
timer?.invalidate() // Invalidiert den Timer
timer = nil
print("Timer stopped")
}
// Formatiert die verbleibende Zeit als String
private func timeString(_ interval: TimeInterval) -> String {
let minutes = Int(interval) / 60
let seconds = Int(interval) % 60
return String(format: "%02d:%02d", minutes, seconds) // Gibt die Zeit im Format MM:SS zurück
}
// Spielt einen Soundeffekt ab
private func playSound(sound: String) {
guard let url = Bundle.main.url(forResource: sound, withExtension: "mp3") else {
print("Sound file not found: \(sound).mp3")
return
}
do {
soundEffect = try AVAudioPlayer(contentsOf: url)
soundEffect?.play() // Spielt den Sound ab
print("Playing sound: \(sound).mp3")
} catch {
print("Error playing sound: \(error.localizedDescription)")
}
}
}
// Vorschau der TimerView für das SwiftUI Preview
struct TimerView_Previews: PreviewProvider {
static var previews: some View {
TimerView(
duration: .constant(600),
isEMOTM: .constant(false), // Füge dieses Binding hinzu
isCountUp: false,
isInterval: true,
isCustomTimer: false, // Setze isCustomTimer auf false für die Vorschau
highDuration: 60,
lowDuration: 30,
numberOfRounds: 5 // Konfiguriert die Vorschau der TimerView
)
}
}
固定计时器位置
如果您只是不想让黑色背景跳来跳去,您可以将文本作为最宽数字的
overlay
。
Text("00:00") // suppose 00:00 produces the widest text
.font(.custom("digital-7", size: 150))
.padding(.vertical, 5)
.opacity(0) // hide the text
.background(.black.opacity(0.75))
.clipShape(.capsule)
.overlay {
Text(...) // the actual timer text goes here...
.font(.custom("digital-7", size: 150))
.padding(.vertical, 5)
}
这可以阻止背景改变其大小,但文本的其他数字仍然会跳来跳去,因为数字的宽度不同。这最终是一个字体问题,所以如果您也想防止 that ,您需要使用不同的字体。
如果您的字体已经支持等宽数字(就像系统字体一样),您可以通过以下方式启用它:
.font(.custom("digital-7", size: 150).monospacedDigit())
而且您甚至不需要
overlay
。