Skip to content

Instantly share code, notes, and snippets.

@MahdiBM
Last active June 16, 2021 20:32
Show Gist options
  • Save MahdiBM/d087ffb031a7bc98b14d19d6005f2914 to your computer and use it in GitHub Desktop.
Save MahdiBM/d087ffb031a7bc98b14d19d6005f2914 to your computer and use it in GitHub Desktop.
Simple Circular Progress View
import SwiftUI
struct ContentView: View {
@State var progress: CGFloat = 0 /* 0 <= progress <= 1 */
let radius: CGFloat = 150
let lineWidth: CGFloat = 10
let startAngle: Angle = .degrees(35)
let endAngle: Angle = .degrees(145)
var deltaArc: CGFloat {
(endAngle - startAngle).radians
}
var wholeArc: CGFloat {
(2 * .pi) - deltaArc
}
var progressedArc: CGFloat {
wholeArc * progress
}
var circleAngle: Angle {
.radians(CGFloat.pi / 2 - (endAngle.radians + progressedArc))
}
var progressArcStartAngle: Angle {
.radians(endAngle.radians + progressedArc)
}
var body: some View {
VStack {
CircleArc(startAngle: startAngle, endAngle: endAngle, lineWidth: lineWidth)
.frame(width: radius * 2, height: radius * 2)
.foregroundColor(.gray)
.overlay(
CircleArc(
startAngle: progressArcStartAngle,
endAngle: endAngle,
lineWidth: lineWidth
).foregroundColor(.blue)
)
.overlay(
CircleOnArea.init(angle: circleAngle)
.foregroundColor(.green)
)
.animation(.easeIn.speed(0.7), value: progress)
VStack {
Text("progress: \(progress)")
Slider(value: $progress, in: 0...1)
Button("Toggle") {
withAnimation {
if progress == 0 {
progress = 1
} else {
progress = 0
}
}
}
}
.padding(.horizontal)
}
}
}
struct CircleOnArea: Shape {
var angle: Angle
let circleRadius: CGFloat = 25
func path(in rect: CGRect) -> Path {
let radius = rect.width / 2
let x = sin(angle.radians) * radius + radius - circleRadius / 2
let y = cos(angle.radians) * radius + radius - circleRadius / 2
let cornerSize: CGSize = .init(width: circleRadius, height: circleRadius)
let rect: CGRect = .init(x: x, y: y, width: circleRadius, height: circleRadius)
var path = Path()
path.addRoundedRect(in: rect, cornerSize: cornerSize) // Circle
return path
}
}
extension CircleOnArea: Animatable {
var animatableData: Double {
get { angle.radians }
set { angle = .radians(newValue) }
}
}
struct CircleArc: Shape {
var startAngle: Angle
var endAngle: Angle
var lineWidth: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.width / 2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true
)
path = path.strokedPath(.init(lineWidth: lineWidth, lineCap: .round))
return path
}
}
extension CircleArc: Animatable {
var animatableData: AnimatablePair<Double, Double> {
get { .init(startAngle.radians, endAngle.radians) }
set {
startAngle = .radians(newValue.first)
endAngle = .radians(newValue.second)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment