Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// gist file for Creating Activity Rings in SwiftUI
// https://swdevnotes.com/swift/2021/create-activity-rings-in-swiftui/
//
// All code in one ContentView.swift file just for sharing
//
// Created by Eric on 05/09/2021.
//
import SwiftUI
struct ContentView: View {
@State private var progressValues = [0.0, 0.0, 0.0, 0.0]
var body: some View {
ZStack {
Color(red: 208/255, green: 225/255, blue: 242/255, opacity: 0.4)
.edgesIgnoringSafeArea(.all)
VStack {
ZStack() {
ActivityRingView(progress: progressValues[0],
ringRadius: 140.0,
thickness: 20.0,
startColor: Color(red: 1.000, green: 0.596, blue: 0.588),
endColor: Color(red: 0.839, green: 0.153, blue: 0.157))
ActivityRingView(progress: progressValues[1],
ringRadius: 120.0,
thickness: 20.0,
startColor: Color(red: 0.596, green: 0.875, blue: 0.541),
endColor: Color(red: 0.173, green: 0.627, blue: 0.173))
ActivityRingView(progress: progressValues[2],
ringRadius: 100.0,
thickness: 20.0,
startColor: Color(red: 1.000, green: 0.733, blue: 0.471),
endColor: Color(red: 1.000, green: 0.498, blue: 0.055))
ActivityRingView(progress: progressValues[3],
ringRadius: 80.0,
thickness: 20.0,
startColor: Color(red: 0.784, green: 0.659, blue: 0.941),
endColor: Color(red: 0.278, green: 0.129, blue: 0.620))
}
.frame(height:350)
Spacer().frame(height:50)
HStack(spacing:20) {
Spacer()
Button("Increase Activity") {
progressValues = progressValues.map { ($0 + Double.random(in: 0.2...0.5)) }
}
Button("Reset") {
progressValues = [0.0, 0.0, 0.0, 0.0]
}
Spacer()
}
Spacer()
}
.padding()
.animation(.linear(duration:1.0))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ActivityRingView: View {
var progress: Double
var ringRadius: Double = 60.0
var thickness: CGFloat = 20.0
var startColor = Color(red: 0.784, green: 0.659, blue: 0.941)
var endColor = Color(red: 0.278, green: 0.129, blue: 0.620)
private var ringTipShadowOffset: CGPoint {
let ringTipPosition = tipPosition(progress: progress, radius: ringRadius)
let shadowPosition = tipPosition(progress: progress + 0.0075, radius: ringRadius)
return CGPoint(x: shadowPosition.x - ringTipPosition.x,
y: shadowPosition.y - ringTipPosition.y)
}
private func tipPosition(progress:Double, radius:Double) -> CGPoint {
let progressAngle = Angle(degrees: (360.0 * progress) - 90.0)
return CGPoint(
x: radius * cos(progressAngle.radians),
y: radius * sin(progressAngle.radians))
}
var body: some View {
let activityAngularGradient = AngularGradient(
gradient: Gradient(colors: [startColor, endColor]),
center: .center,
startAngle: .degrees(0),
endAngle: .degrees(360.0 * progress))
ZStack {
Circle()
.stroke(startColor.opacity(0.15), lineWidth: thickness)
.frame(width:CGFloat(ringRadius) * 2.0)
Circle()
.stroke(Color(.systemGray2), lineWidth: 1.0)
.frame(width:(CGFloat(ringRadius) * 2.0) + thickness)
Circle()
.stroke(Color(.systemGray2), lineWidth: 1.0)
.frame(width:(CGFloat(ringRadius) * 2.0) - thickness)
Circle()
.trim(from: 0, to: CGFloat(self.progress))
.stroke(
activityAngularGradient,
style: StrokeStyle(lineWidth: thickness, lineCap: .round))
.rotationEffect(Angle(degrees: -90))
.frame(width:CGFloat(ringRadius) * 2.0)
ActivityRingTip(progress: progress,
ringRadius: Double(ringRadius))
.fill(progress>0.95 ? endColor : .clear)
.frame(width:thickness, height:thickness)
.shadow(color: progress>0.95 ? .black.opacity(0.3) : .clear,
radius: 2.5,
x: ringTipShadowOffset.x,
y: ringTipShadowOffset.y)
}
}
}
struct ActivityRingTip: Shape {
var progress: Double
var ringRadius: Double
private var position: CGPoint {
let progressAngle = Angle(degrees: (360.0 * progress) - 90.0)
return CGPoint(
x: ringRadius * cos(progressAngle.radians),
y: ringRadius * sin(progressAngle.radians))
}
var animatableData: Double {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
if progress > 0.0 {
path.addEllipse(in: CGRect(
x: position.x,
y: position.y,
width: rect.size.width,
height: rect.size.height))
}
return path
}
}
@EvolvingParty
Copy link

EvolvingParty commented Nov 14, 2022

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment