SwiftUI 倒计时器弹跳(计时器在时间用完时改变其大小)

问题描述 投票:0回答:1

当时间用完时,计时器会改变其大小,例如从 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
        )
    }
}

固定计时器位置

ios swift swiftui timer
1个回答
0
投票

如果您只是不想让黑色背景跳来跳去,您可以将文本作为最宽数字的

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

© www.soinside.com 2019 - 2024. All rights reserved.