Skip to content

Instantly share code, notes, and snippets.

@sledsworth
Last active April 17, 2021 16:48
Show Gist options
  • Save sledsworth/67528bfc04a630cf8d5a9147bb9c514d to your computer and use it in GitHub Desktop.
Save sledsworth/67528bfc04a630cf8d5a9147bb9c514d to your computer and use it in GitHub Desktop.
Generic Animated Progress Bar in SwiftUI
import Foundation
import UIKit
import SwiftUI
import Combine
class NutrientModel: Progressable {
var willChange = PassthroughSubject<BaseNutrient, Never>()
var id = UUID.init()
var name: String
var description: String {
get {
return "\(current) / \(total)"
}
}
var current: CGFloat
var total: CGFloat
var progress: CGFloat {
get {
return current / total
}
}
var gradient: LinearGradient
init(name: String, unit: NutrientUnit, total: CGFloat, colors: [Color] = [Color(red: 239.0 / 255, green: 120.0 / 255, blue: 221.0 / 255), Color(red: 239.0 / 255, green: 172.0 / 255, blue: 120.0 / 255)], start: CGFloat = 0) {
self.name = name
self.total = total
self.current = start
self.gradient = LinearGradient(gradient: .init(colors: colors), startPoint: .init(x: 0.0, y: 0),
endPoint: .init(x: 1.0, y: 0))
}
func addProgress(_ progress: CGFloat) {
willChange.send(self)
self.current += progress
}
func removeProgress(_ progress: CGFloat) {
willChange.send(self)
self.current -= progress
if self.current < 0 {
self.current = 0
}
}
}
import SwiftUI
protocol Progressable: ObservableObject {
/// Name of the progressable field
var name: String { get }
/// The text form of the progress being made (ex. formatted percentage or fraction)
var description: String { get }
/// The percentage of progress made as float
var progress: CGFloat { get }
/// The linear gradient use to fill the completed portion of the bar
var gradient: LinearGradient { get set }
}
import SwiftUI
struct ProgressBarComponent<P: Progressable> : View {
@ObservedObject var progression: P
var progressAnimation: Animation {
Animation
.interpolatingSpring(mass: 43, stiffness: 3.2, damping: 0.5)
.speed(3)
.delay(0.07)
}
var barHeight: CGFloat = 12.0
var body: some View {
VStack {
HStack {
Text(self.progression.name)
.font(.subheadline)
.foregroundColor(.gray)
Spacer()
Text(self.progression.description)
.font(.subheadline)
.foregroundColor(.gray)
}
.padding(.bottom, -8)
.padding(.horizontal, 4)
ZStack {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 100.0)
.fill(
Color(.displayP3,
red: 150 / 255,
green: 150 / 255,
blue: 150 / 255,
opacity: 0.1)
)
.overlay(
RoundedRectangle(cornerRadius: 100.0)
.size(
width: max(geometry.size.width * self.progression.progress, self.barHeight),
height: geometry.size.height)
.fill(self.progression.gradient)
.animation(self.progressAnimation)
)
}
}
.frame(height: self.barHeight, alignment: .leading)
}
}
}
import SwiftUI
struct DayProgressContainer : View {
var trackedNutreints: [NutrientModel]
var body: some View {
return VStack {
ForEach(self.trackedNutreints, id: \.self) { nutrient in
ProgressBarComponent(progression: nutrient)
.padding(.vertical, 4)
}
}
}
}
@mwcs01
Copy link

mwcs01 commented Oct 11, 2019

I have been struggling on how to use it. Any chance you could include how you used it to create one of your example views above?

@sledsworth
Copy link
Author

sledsworth commented Oct 14, 2019

@mwcs01 I added an example--I didn't have a chance to test it, but hopefully you get the idea? This code is a bit different in my app now, I'll try to update it sometime soon.

@siddharth952
Copy link

Thanks!

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