'Half circular progress bar with gradient swift

I want to create a half circular progress bar with gradient colour in iOS using swift. The minimum value of progress bar is 300 and maximum value of progress bar is 900. The current value of the progress bar is dynamic. I am attaching screenshot ans css for colour reference. Can someone please provide me a small working demo?

Half Circular progress

below is the CSS -

/* Layout Properties */
top: 103px;
left: 105px;
width: 165px;
height: 108px;

/* UI Properties */

 background: transparent linear-gradient
(269deg, #32E1A0 0%, #EEED56 23%, #EFBF39 50%, #E59148 75%, #ED4D4D 100%)
 0% 0% no-repeat padding-box;
 opacity: 1;

Properties



Solution 1:[1]

you can create not same but similar progress bar with framework called MKMagneticProgress

example code :-

import MKMagneticProgress

@IBOutlet weak var magProgress:MKMagneticProgress!

override func viewDidLoad() {
    magProgress.setProgress(progress: 0.5, animated: true)
    magProgress.progressShapeColor = UIColor.blue
    magProgress.backgroundShapeColor = UIColor.yellow
    magProgress.titleColor = UIColor.red
    magProgress.percentColor = UIColor.black

    magProgress.lineWidth = 10
    magProgress.orientation = .top
    magProgress.lineCap = .round

    magProgress.title = "Title"
    magProgress.percentLabelFormat = "%.2f%%"

}

i hope it will work ... :)

Solution 2:[2]

this could solve your problem in swiftui

enter image description here

import SwiftUI

struct ContentView: View {
    @State var progressValue: Float = 0.3
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State private var degress: Double = -110
    
    var body: some View {
        
        VStack {
            ZStack{
                
                ProgressBar(progress: self.$progressValue)
                    .frame(width: 250.0, height: 250.0)
                    .padding(40.0).onReceive(timer) { _ in
                        withAnimation {
                            if progressValue < 0.8999996 {
                                progressValue += 0.0275
                            }
                        }
                    }
                
                ProgressBarTriangle(progress: self.$progressValue).frame(width: 280.0, height: 290.0).rotationEffect(.degrees(degress), anchor: .bottom)
                    .offset(x: 0, y: -150).onReceive(timer) { input in
                        withAnimation(.linear(duration: 0.01).speed(200)) {
                            if degress < 110.0 {
                                degress += 10
                            }
                            print(degress)
                        }
                    }
            }
            Spacer()
        }
    }
    
    struct ProgressBar: View {
        @Binding var progress: Float
        
        var body: some View {
            ZStack {
                Circle()
                    .trim(from: 0.3, to: 0.9)
                    .stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round))
                    .opacity(0.3)
                    .foregroundColor(Color.gray)
                    .rotationEffect(.degrees(54.5))
                
                Circle()
                    .trim(from: 0.3, to: CGFloat(self.progress))
                    .stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round))
                    .fill(AngularGradient(gradient: Gradient(stops: [
                        .init(color: Color.init(hex: "ED4D4D"), location: 0.39000002),
                        .init(color: Color.init(hex: "E59148"), location: 0.48000002),
                        .init(color: Color.init(hex: "EFBF39"), location: 0.5999999),
                        .init(color: Color.init(hex: "EEED56"), location: 0.7199998),
                        .init(color: Color.init(hex: "32E1A0"), location: 0.8099997)]), center: .center))
                    .rotationEffect(.degrees(54.5))
                
                VStack{
                    Text("824").font(Font.system(size: 44)).bold().foregroundColor(Color.init(hex: "314058"))
                    Text("Great Score!").bold().foregroundColor(Color.init(hex: "32E1A0"))
                }
            }
        }
    }
    
    struct ProgressBarTriangle: View {
        @Binding var progress: Float
        
        
        var body: some View {
            ZStack {
                Image("triangle").resizable().frame(width: 10, height: 10, alignment: .center)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

extension Color {
    init(hex: String) {
        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
        var int: UInt64 = 0
        Scanner(string: hex).scanHexInt64(&int)
        let a, r, g, b: UInt64
        switch hex.count {
        case 3: // RGB (12-bit)
            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
        case 6: // RGB (24-bit)
            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
        case 8: // ARGB (32-bit)
            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
        default:
            (a, r, g, b) = (1, 1, 1, 0)
        }
        
        self.init(
            .sRGB,
            red: Double(r) / 255,
            green: Double(g) / 255,
            blue:  Double(b) / 255,
            opacity: Double(a) / 255
        )
    }
}

Solution 3:[3]

I implemented something like your progress bar, used this for level up - gain exp. progress.,

if you go from 300 to 1000, total bar is 1500 progressFrom: 300/1500, progressTo: 1000/1500 ..

func progressAnimation(duration: TimeInterval, progressFrom: CGFloat, progressTo: CGFloat) {
    if progressFrom == 0 {
        baseProgressLayer.removeFromSuperlayer()
    }
    
    let circlePath = UIBezierPath(ovalIn: CGRect(center: CGPoint(x: 93.0, y: 93.0), size: CGSize(width: 178, height: 178)))
    
    progressLayer.path = circlePath.cgPath
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.strokeColor = getColor(progress: progressTo).cgColor
    progressLayer.lineWidth = 8.0
    layer.addSublayer(progressLayer)
    layer.transform = CATransform3DMakeRotation(CGFloat(90 * Double.pi / 180), 0, 0, -1)
    
    let end = CABasicAnimation(keyPath: "strokeEnd")
    end.fromValue = progressFrom
    end.toValue = progressTo
    end.duration = duration * 0.85
    end.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    end.fillMode = CAMediaTimingFillMode.forwards
    end.isRemovedOnCompletion = false
    
    let color = CABasicAnimation(keyPath: "strokeColor")
    color.fromValue = getColor(progress: progressFrom).cgColor
    color.toValue = getColor(progress: progressTo).cgColor
    color.duration = duration * 0.85
    color.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    color.fillMode = CAMediaTimingFillMode.forwards
    color.isRemovedOnCompletion = false
    
    let group = CAAnimationGroup()
    group.animations = [end, color]
    group.duration = duration * 0.85
    progressLayer.strokeStart = progressFrom
    progressLayer.strokeEnd = progressTo
    progressLayer.add(group, forKey: "move")

    if progressFrom != 0 {
        let color2 = CABasicAnimation(keyPath: "strokeColor")
        color2.fromValue = getColor(progress: progressFrom).cgColor
        color2.toValue = getColor(progress: progressTo).cgColor
        color2.duration = duration * 0.85
        color2.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
        color2.fillMode = CAMediaTimingFillMode.forwards
        color2.isRemovedOnCompletion = false
        
       baseProgressLayer.add(color2, forKey: "baseColor")
    }
    
}

there is a extension for getting color for progress

extension CircularProgressBar {

private func getColor (progress: CGFloat) -> UIColor{
    if progress >= 0 && progress <= 0.5{
        return UIColor(red: CGFloat(255 / 255), green: CGFloat((0+((108-0)*progress*2)) / 255), blue: 0, alpha: 1.0)
    }
    else if progress > 0.5 && progress <= 1.0 {
        return UIColor(red: CGFloat((255-((255-126)*(progress-0.5)*2)) / 255), green: CGFloat((108+((211-108)*(progress-0.5)*2)) / 255), blue: CGFloat((0+((33-0)*(progress-0.5)*2)) / 255), alpha: 1.0)
    }
    else {
        return UIColor.green
    }
}

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shivam Parmar
Solution 2
Solution 3 babacan