Benutzerdefinierte Fortschrittsanzeige
Beschreibung
Diese SwiftUI-View zeigt eine animierte, kreisförmige Fortschrittsanzeige mit Segmenten, die sich entsprechend dem Fortschritt füllen. Die Anzeige kombiniert funktionales Feedback und ein modernes, ästhetisches Design und eignet sich ideal, um Benutzern den Verlauf von Aufgaben oder Ladeprozessen visuell darzustellen.
🔍 Zweck
- Darstellung von Lade- oder Verarbeitungsfortschritten in Apps
- Visualisierung des Abschlussgrads bei Trainings- oder Lernzielen
- Anzeigen von Upload-/Download-Fortschritt in Dateimanagern
- Fortschrittsfeedback bei mehrstufigen Onboarding-Prozessen
- Gamification: Anzeigen von Level-Fortschritt oder Belohnungen
📄 Codebeispiel
import SwiftUI
/// A circular segmented progress indicator with animated segment fill.
struct CircularSegmentedProgressView: View {
var progress: Double // Progress between 0.0 and 1.0
let segmentCount: Int // Number of segments in the circle
let lineWidth: CGFloat // Thickness of the segment strokes
let filledColor: Color // Color for filled segments
let emptyColor: Color // Color for empty segments
let animation: Animation // Animation for progress change
init(
progress: Double,
segmentCount: Int = 12,
lineWidth: CGFloat = 14,
filledColor: Color = .accentColor,
emptyColor: Color = .gray.opacity(0.3),
animation: Animation = .easeInOut(duration: 0.6)
) {
self.progress = min(max(progress, 0), 1) // Clamp between 0...1
self.segmentCount = segmentCount
self.lineWidth = lineWidth
self.filledColor = filledColor
self.emptyColor = emptyColor
self.animation = animation
}
var body: some View {
GeometryReader { geo in
ZStack {
ForEach(0..<segmentCount, id: \.self) { index in
SegmentShape(
segmentIndex: index,
totalSegments: segmentCount,
thickness: lineWidth
)
.stroke(
index < filledSegments ? filledColor : emptyColor,
style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)
)
.animation(animation, value: filledSegments)
}
}
.frame(width: geo.size.width, height: geo.size.height)
.contentShape(Circle())
// Optional overlay for displaying percent text in the center:
.overlay(
Text("\(Int(progress * 100))%")
.font(.title2.bold())
.foregroundColor(.primary)
.accessibilityLabel(Text("Fortschritt \(Int(progress * 100)) Prozent"))
)
}
.aspectRatio(1, contentMode: .fit)
.padding(lineWidth / 2)
}
/// Calculates how many full segments should be filled based on current progress.
private var filledSegments: Int {
Int(round(progress * Double(segmentCount)))
}
}
/// Shape representing a single arc segment.
struct SegmentShape: Shape {
let segmentIndex: Int
let totalSegments: Int
let thickness: CGFloat
func path(in rect: CGRect) -> Path {
let radius = min(rect.width, rect.height) / 2 - thickness / 2
let anglePerSegment = Angle.degrees(360 / Double(totalSegments))
let startAngle = Angle.degrees(-90) + anglePerSegment * Double(segmentIndex)
let endAngle = startAngle + anglePerSegment * 0.92 // Small gap between segments
var path = Path()
path.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: false
)
return path
}
}
// Demo view for interactive testing.
struct CircularSegmentedProgressDemoView: View {
@State private var progressValue = 0.55
var body: some View {
VStack(spacing: 32) {
CircularSegmentedProgressView(
progress: progressValue,
segmentCount: 16,
lineWidth: 16,
filledColor: .blue,
emptyColor: .gray.opacity(0.25)
)
.frame(width: 180, height: 180)
Slider(value: $progressValue, in: 0...1)
.padding(.horizontal)
.accentColor(.blue)
Button("Reset") {
withAnimation { progressValue = 0 }
}
.buttonStyle(.borderedProminent)
Button("Random Progress") {
withAnimation { progressValue = Double.random(in: 0...1) }
}
.buttonStyle(.bordered)
}
.padding()
}
}
#Preview {
CircularSegmentedProgressDemoView()
}