Listings Hauser/SwiftUI

Listing 1: Die Datei Package.swift mit Konfiguration
import PackageDescription

let package = Package(
    name: "CountDown",
    platforms: [
      .macOS(.v10_15),
      .iOS(.v14),
      .watchOS(.v7),
      .tvOS(.v14)
    ],
    products: [
      .library(
        name: "CountDown",
        targets: ["CountDown"]),
    ],
    targets: [
      .target(
        name: "CountDown",
        dependencies: []),
      .testTarget(
        name: "CountDownTests",
        dependencies: ["CountDown"]),
    ]
)

-----

Listing 2: Countdown View mit Modifier stroke(style:)
public struct CountDownView: View {
    public var body: some View {
      Circle()
        .trim(from: 0.0, to: 0.3)
        .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
    }
}

-----

Listing 3: Komponente in ZStack einbetten
public struct CountDownView: View {
    public var body: some View {
      ZStack {
        Circle()
          .trim(from: 0.0, to: 0.3)
          .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
          .padding()
      }
    }
}

-----

Listing 4: Vorschau für iPhone, Mac und Apple Watch
struct CountDownView_Previews: PreviewProvider {
    static var previews: some View {
      Group {
        CountDownView()
          .previewDevice(
            PreviewDevice(
              rawValue: "iPhone 12 mini"))
        CountDownView()
          .previewDevice(
            PreviewDevice(
              rawValue: "My Mac"))
        CountDownView()
          .previewDevice(
            PreviewDevice(
              rawValue: "Apple Watch Series 6 - 40mm"))
      }
    }
}

-----

Listing 5: CountDownView anpassen
GeometryReader { geometry in
    let minWidthHeight = min(geometry.size.width, geometry.size.height)
    ZStack {
      Circle()
        .trim(from: 0.0, to: 0.3)
        .stroke(style: StrokeStyle(
           lineWidth: minWidthHeight / 20,
           lineCap: .round))
        .padding()
      Text("12:34")
        .font(.system(size: minWidthHeight / 5,
                    weight: .bold,
                    design: .monospaced))
    }
}

-----

Listing 6: body anpassen
public var body: some View {
    GeometryReader { geometry in
      let minWidthHeight = min(geometry.size.width, geometry.size.height)
      ZStack {
        Circle()
          .trim(from: 0.0,
            to: CGFloat(remainingSeconds/totalSeconds))
          .stroke(style: StrokeStyle(
                        lineWidth: minWidthHeight / 20,
                        lineCap: .round))
          .padding()
        Text(formatter.string(from: remainingSeconds) ?? "-:-")
          .font(.system(size: minWidthHeight / 5,
                     weight: .bold,
                     design: .monospaced))
      }
    }
}

-----

Listing 7: Einfache Timer-Implementierung
@State private var timer: Timer? = nil
@State private var endDate: Date?
@State private var totalSeconds: TimeInterval = 0 {
    didSet {
      startTimer()
    }
}
@State private var remainingSeconds: TimeInterval = 0

-----

Listing 8: Die Methode startTimer()
private func startTimer() {
    timer?.invalidate()
    endDate = Date(
      timeIntervalSinceNow: TimeInterval(totalSeconds))
    timer = Timer.scheduledTimer(
      withTimeInterval: 0.1,
      repeats: true,
      block: { timer in
        if let endDate = endDate {
          let seconds = endDate.timeIntervalSince(Date())
          if seconds <= 0 {
            timer.invalidate()
          }
          withAnimation {
            remainingSeconds  = seconds
          }
        }
      })
}

-----

Listing 9: Countdown-View
import SwiftUI
import CountDown

struct ContentView: View {
    @State private var timer: Timer? = nil
    @State private var endDate: Date?
    @State private var totalSeconds: TimeInterval = 0 {
      didSet {
        startTimer()
      }
    }
    @State private var remainingSeconds: TimeInterval = 0

    var body: some View {
      CountDownView(remainingSeconds: remainingSeconds,
                                      totalSeconds: totalSeconds)
      Button("Start", action: {
        totalSeconds = 5 * 60
})
    }

    private func startTimer() {
      timer?.invalidate()
      endDate = Date(
        timeIntervalSinceNow: TimeInterval(totalSeconds))
      timer = Timer.scheduledTimer(
        withTimeInterval: 0.1,
        repeats: true,
        block: { timer in
          if let endDate = endDate {
            let seconds = endDate.timeIntervalSince(Date())
            if seconds <= 0 {
              timer.invalidate()
            }
            withAnimation {
              remainingSeconds  = seconds
            }
          }
        })
    }
}
