[SWIFT] iOS App Entwicklung: Timer App (8. Implementierung des Fortschrittsbalkens)

スクリーンショット 2020-10-28 11.22.39.png

Inhalt

Die Punkte zum Erstellen einer Timer-App werden in mehreren Artikeln veröffentlicht. In diesem Artikel werde ich über das Erstellen eines kreisförmigen Fortschrittsbalkens schreiben, der die verbleibende Zeit des Countdown-Timers anzeigt.

Umgebung

Git-Repository

Sie können den Beispielcode unter der folgenden Git-Repository-URL sehen. https://github.com/msnsk/Qiita_Timer.git

Verfahren

  1. Erstellen Sie eine Ansicht des Fortschrittsbalkens
  2. Erstellen Sie einen Kreis für den Hintergrund des Fortschrittsbalkens
  3. Erstellen Sie einen Kreis für den Fortschrittsbalken
  4. Verknüpfen Sie die Länge des Fortschrittsbalkens mit der verstrichenen Zeit
  5. Platzieren Sie einen Fortschrittsbalken in der Hauptansicht
  6. Glätten Sie die Bewegung des Fortschrittsbalkens

1. Erstellen Sie eine Ansicht des Fortschrittsbalkens

Erstellen Sie eine neue Datei mit dem Namen ProgressBar.swift. Diese Ansicht bezieht sich auch auf den Eigenschaftswert aus der TimeManager-Klasse. Erstellen Sie daher eine Instanz der TimeManager-Klasse mit dem Eigenschafts-Wrapper @EnvironmentObject vor var.

ProgressBar.swift


import SwiftUI

struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        Text("Hello, World!")
    }
}

2. Erstellen Sie einen Kreis für den Hintergrund des Fortschrittsbalkens

Der Fortschrittsbalken erfordert zwei Kreise, einen, der sich mit der Zeit verkürzt, und einen, der den Hintergrund bildet. Die Hintergrundkreise sind gleich groß und ändern sich im Laufe der Zeit nicht in der Länge. Zunächst erstellen wir aus dem Hintergrundkreis.

Platziere den Kreis im Körper {}. SwiftUI bietet eine Grafikkomponente namens Circle (), die Sie verwenden können.

ProgressBar.swift


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        Circle()
    }
}

Eine Figur besteht aus einer Konturlinie und einer Fläche. Um sie zu einem hohlen Kreis zu machen, zeigen Sie die Fläche nicht an, sondern nur die Konturlinie und passen Sie die Dicke, Länge und Farbe der Konturlinie an.

Fügen Sie einen Circle () -Modifikator hinzu, um die gewünschte Form zu erhalten.

Fügen Sie im Modifikator .stroke Color () in das Argument ein und setzen Sie das Argument auf .darkGray, um eine graue hintergrundähnliche Farbe zu erhalten.

Setzen Sie im Modifikator .stroke das Stilargument lineWidth auf 20, um die Dicke des Fortschrittsbalkens anzugeben.

Verwenden Sie den Modifikator .scaledToFit, um die Kreisgröße anzupassen, um die Bildschirmgröße zu füllen, und den Modifikator .padding, um die Ränder an den Bildschirmkanten anzupassen.

ProgressBar.swift


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        Circle()
                .stroke(Color(.darkGray), style: StrokeStyle(lineWidth: 20))
                .scaledToFit()
                .padding(25)
    }
}

3. Erstellen Sie einen Kreis für den Fortschrittsbalken

Der von Ihnen erstellte Fortschrittsbalkenkreis überlappt den in Schritt 2 erstellten Hintergrundkreis auf geschichtete Weise. Wenn Sie also eine weitere Circle () -Komponente für den Fortschrittsbalken hinzufügen, schließen Sie die beiden Kreise mit ZStack {} ein. Ich werde.

ProgressBar.swift


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        ZStack {
            //Kreis für Hintergrund
            Circle()
                .stroke(Color(.darkGray), style: StrokeStyle(lineWidth: 20))
                .scaledToFit()
                .padding(15)
            
            //Kreis für Fortschrittsbalken
            Circle()
        }
    }
}

Wie der Hintergrundkreis wird auch der Fortschrittsbalken durch Hinzufügen von Modifikatoren geformt.

Im Modifikator .stroke habe ich Color () in das Argument eingefügt und .cyan vorerst als Farbe des Fortschrittsbalkens angegeben.

Fügen Sie im Modifikator .stroke eine detaillierte Spezifikation des Stilarguments in StrokeStyle ein. Geben Sie mit lineWidth die Breite bis 20 an, geben Sie mit lineCap .round an, um die Ecken der Linienenden abzurunden, und geben Sie mit lineJoin .round an, um das Zeilenende um die Hälfte der Länge der Linienbreite zu überschreiten. Mach es rund.

Fügen Sie den Modifikator .rotationEffect hinzu und nehmen Sie Angle (Grad: -90) als Argument. Auf diese Weise können Sie die Startposition des Standardkreisumrisses von der 3-Uhr-Richtung in die 12-Uhr-Richtung ändern.

Andere sind die gleichen wie der Hintergrundkreis.

ProgressBar.swift


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        ZStack {
            //Kreis für Hintergrund
            Circle()
                .stroke(Color(.darkGray), style: StrokeStyle(lineWidth: 20))
                .scaledToFit()
                .padding(15)
            
            //Kreis für Fortschrittsbalken
            Circle()
                .stroke(Color(.cyan), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .bevel))
                .scaledToFit()
                //Stellen Sie die Startposition der Konturlinie auf 12 Uhr ein
                .rotationEffect(Angle(degrees: -90))
                .padding(15)
        }
    }
}

4. Verknüpfen Sie die Länge des Fortschrittsbalkens mit der verstrichenen Zeit

Fügen Sie dem Fortschrittsbalkenkreis weitere Modifikatoren hinzu, damit der Fortschrittsbalken mit Ablauf des Countdown-Timers kürzer wird.

Fügen Sie einen .trim-Modifikator hinzu. Auf diese Weise können Sie den Fortschrittsbalken auf die erforderliche Länge zuschneiden.

Die Startposition ist immer auf 12 Uhr festgelegt, setzen Sie also 0 in das Argument from. Da der Fortschrittsbalken mit der Zeit kürzer werden muss, sollte die Endposition immer an die verbleibende Zeit gebunden sein. Außerdem muss der Wert für die Argumente from und to zwischen 0 und 1 liegen.

Hier ist eine kleine Arithmetik. Die TimeManager-Klasse bietet eine Eigenschaft maxValue, in der die in Picker festgelegte maximale Zeit und eine Eigenschaftsdauer, in der die verbleibende Zeit gespeichert ist, gespeichert werden. Bei Verwendung dieser beiden Werte lautet die Formel, die zu Beginn des Countdowns einen Maximalwert von 1 und am Ende des Countdowns einen Minimalwert von 0 aufweist, dauer / maxWert.

Da der Datentyp des Werts des Arguments CGFloat sein muss, lautet der Wert, der endgültig in das Argument eingefügt werden soll, wie folgt.

CGFloat(self.timeManager.duration / self.timeManager.maxValue)

Der Code sieht also so aus:

ProgressBar.swift


struct ProgressBarView: View {
    @EnvironmentObject var timeManager: TimeManager

    var body: some View {
        ZStack {
            //Kreis für Hintergrund
            Circle()
                //(Modifikator weggelassen)
            
            //Kreis für Fortschrittsbalken
            Circle()
                .trim(from: 0, to: CGFloat(self.timeManager.duration / self.timeManager.maxValue))
                .stroke(Color(.cyan), style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                .scaledToFit()
                //Stellen Sie die Startposition der Konturlinie auf 12 Uhr ein
                .rotationEffect(Angle(degrees: -90))
                .padding(15)
        }
    }
}

Überprüfen Sie die ProgressBarView in Canvas. Unten ist der Code für die Vorschau.

struct ProgressBarView_Previews: PreviewProvider {
    static var previews: some View {
        ProgressBarView()
            .environmentObject(TimeManager())
            .previewLayout(.sizeThatFits)
    }
}

Es sollte wie im Bild unten aussehen. スクリーンショット 2020-10-28 11.22.51.png

5. Platzieren Sie einen Fortschrittsbalken in der Hauptansicht

Fügen Sie eine Instanz von ProgressBarView oben im äußersten ZStack {} im Hauptteil {} der Hauptansicht hinzu. Der obere Rand des ZStack-Codes befindet sich auf der Rückseite der Hierarchie der UI-Komponentenschicht. Als Bild stehen die Anzeige der verbleibenden Zeit und die Auswahl zum Einstellen der Zeit im Vordergrund auf dem Bildschirm.

Da im SettingView-Element des Einstellungsbildschirms zuerst ein Kippschalter zum Ein- / Ausblenden des Fortschrittsbalkens vorbereitet ist, wird der Fortschrittsbalken nur angezeigt, wenn die isProgressBaron-Eigenschaft der mit dieser Einstellung verknüpften TimeManager-Klasse true ist. Beschreiben Sie es mit einer if-Anweisung, damit es angezeigt wird.

MainView.swift


struct MainView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    var body: some View {
        ZStack {
            if timeManager.isProgressBarOn {
                ProgressBarView()
            }
            
            if timeManager.timerStatus == .stopped {
                PickerView()
            } else {
                TimerView()
            }
            
            VStack {
                Spacer()
                ZStack {
                    ButtonsView()
                        .padding(.bottom)

                    SettingButtonView()
                        .padding(.bottom)
                        .sheet(isPresented: $timeManager.isSetting) {
                            SettingView()
                                .environmentObject(self.timeManager)
                        }
                }
            }
        }
        .onReceive(timeManager.timer) { _ in
            //(Auslassung der Beschreibung in onReceive)
        }
    }
}

Überprüfen Sie die Hauptansicht in Canvas. Es sollte wie im Bild unten aussehen. スクリーンショット 2020-10-28 11.27.45.png

6. Glätten Sie die Bewegung des Fortschrittsbalkens

Ich konnte den Fortschrittsbalken implementieren, aber als ich die tatsächliche Bewegung des MainView mit Xcode Canvas und Simulator überprüfte, wurde der Fortschrittsbalken mit jeder Sekunde kürzer (es ruckelte), je kürzer die Timer-Einstellzeit war. Du kannst sehen. Es ist kein Fehler als Fortschrittsbalken, aber der optisch glattere macht den Eindruck von Raffinesse, also werde ich ihn ein wenig beheben.

Es gibt zwei Ursachen für diese ruckartige Bewegung, daher werden wir jede korrigieren.

Die erste ist die timer-Eigenschaft der TimeManager-Klasse. Diese Eigenschaft enthält die Veröffentlichungsmethode der Timer-Klasse, aber der Wert ihres Arguments ist jeweils 1. Dies bedeutet, dass es jede Sekunde aktiviert wird. Ändern Sie diesen Wert auf ca. 0,05. Als Ergebnis der Überprüfung wird es einen Fehler zwischen dem tatsächlichen Zeitablauf und der Aktualisierung der verbleibenden Zeit der Timer-App von ungefähr 0,01 geben, daher denke ich, dass ungefähr 0,05 die Grenze ist.

Aktualisieren Sie die TimeManager-Klasse wie folgt:

TimeManager.swift


class TimeManager: ObservableObject {
    //(Andere Eigenschaften weggelassen)

    //Die Veröffentlichungsmethode der Timer-Klasse, die jede Sekunde aktiviert wird
    var timer = Timer.publish(every: 0.05, on: .main, in: .common).autoconnect()

    //(Methode weggelassen)

Die zweite ist die Beschreibung im onReceive-Modifikator von MainView. Dieser Modifikator löst die timer-Eigenschaft der TimeManager-Klasse aus, die wir zuvor geändert haben, um den Code innerhalb des Abschlusses {} auszuführen. Da wir den Trigger für 0,05 Sekunden aktualisiert haben, muss auch die Aktualisierung der Dauer-Eigenschaft (verbleibende Zeit) der TimeManager-Klasse, die im Abschluss {} des Modifikators onReceive beschrieben ist, um 0,05 Sekunden abgezogen werden.

Aktualisieren Sie in MainView wie folgt.

MainView.swift


struct MainView: View {
    @EnvironmentObject var timeManager: TimeManager
    
    var body: some View {
        ZStack {
            //(Kürzung)
        }
        //Führt den Code im Abschluss aus, der von einem Timer ausgelöst wird, der jedes Mal aktiviert wird (1 Sekunde).
        .onReceive(timeManager.timer) { _ in
            //Der Timer-Status ist.Mach nichts außer rennen
            guard self.timeManager.timerStatus == .running else { return }
            //Wenn die verbleibende Zeit größer als 0 ist
            if self.timeManager.duration > 0 {
                //Von der verbleibenden Zeit-0.05
                self.timeManager.duration -= 0.05 //Update hier!
                //Wenn die verbleibende Zeit 0 oder weniger beträgt
            } else {
                //Timer-Status.Wechseln Sie zu gestoppt
                self.timeManager.timerStatus = .stopped
                //Alarm auslösen
                AudioServicesPlayAlertSoundWithCompletion(self.timeManager.soundID, nil)
                //Vibration aktivieren
                AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) {}
            }
        }
    }
}

Selbst wenn Sie beispielsweise den Timer auf 5 Sekunden einstellen, bewegt sich der Fortschrittsbalken jetzt relativ reibungslos. Nächstes Mal werde ich die Farbe des Fortschrittsbalkens schöner anzeigen, was ein kleines zusätzliches Element ist.

Recommended Posts

iOS App Entwicklung: Timer App (8. Implementierung des Fortschrittsbalkens)
Entwicklung der iOS-App: Timer-App (9. Passen Sie die Farbe des Fortschrittsbalkens an)
iOS App Entwicklung: Timer App (4. Implementierung des Countdowns)
iOS App Entwicklung: Timer App (7. Implementierung der Alarmtonauswahl)
iOS App Entwicklung: Timer App (5. Implementierung von Alarm und Vibration)
iOS App Entwicklung: Timer App (2. Timer Anzeige)
iOS App Entwicklung: Timer App (Zusammenfassung)
iOS App Entwicklung: Timer App (1. Timer Zeiteinstellung)
iOS App Entwicklung: Timer App (10. Animation erstellen)
Entwicklung der iOS-App: Timer-App (3. Start / Stopp-Taste, Reset-Taste)
Komplettes IOS-App-Entwicklungstagebuch zum Selbststudium
Roadmap für die Entwicklung von iOS-Apps (Einführung)
Implementierungsnotiz für SKStoreReviewController in der Swift-Benutzeroberfläche von iOS14