Search code examples
iosxcodeswiftuigaugespeedometer

Want to make a gauge view with indicate arrow and over gauge view display circle which indicate progress


gauge view with arrow and over gauge view white circle

As per the image, Arrow-based works but now how to add one more white circle over gauge View and move the same as a white arrow.

Click here for Project


Solution

  • First of all, to reproduce the Indicator of the gauge I would suggest you to use a Shape like this one:

    struct IndicatorShape: Shape {
        
        func path(in rect: CGRect) -> Path {
            return Path { path in
                let width = rect.width
                let height = rect.height
                
                path.move(to: CGPoint(x: width / 2, y: 0))
                path.addLine(to: CGPoint(x: 0, y: height))
                path.addLine(to: CGPoint(x: width, y: height))
            }
        }
        
    }
    

    And then use it in your code as any other View. To add a Circle over your indicator you could use a simple overlay. Also, you need a masking effect to show the advancement of your gauge. It's a bit complicated and require some fine tuning... Here's the code:

    struct MeterReadiness : View {
    //speedometerBgColor
    let colors = [Color.red,Color.yellow,Color.green,Color.blue]
    @State var progress : CGFloat = 0.0
    
    var body: some View{
        
        GeometryReader {
            let size = $0.size
            
            
            ZStack{
                
                Circle()
                    .trim(from: 0.0, to: 0.5)
                    .stroke(Color.red, lineWidth: 10)
                    .frame(width: 280, height: 280)
                
                
                Circle()
                    .trim(from: 0, to: self.setProgress())
                    .stroke(AngularGradient(gradient: .init(colors: self.colors), center: .center, angle: .init(degrees: 180)), lineWidth: 10)
                    .frame(width: 280, height: 280)
                
                ZStack{
                    
                    Circle()
                        .trim(from: 0.0, to: 0.5)
                        .stroke(Color.blue, lineWidth: 10)
                        .frame(width: 280, height: 280)
                    
                    
                    Circle()
                        .trim(from: 0, to: self.setProgress())
                        .stroke(AngularGradient(gradient: .init(colors: self.colors), center: .center, angle: .init(degrees: 180)), lineWidth: 10)
                        .frame(width: 280, height: 280)
                    
                }
                /// Mask the red gauge with the blue one
                .mask {
                    /// To make the blue advance
                    Circle()
                        .trim(from: 0, to: (progress / 2) + 0.002)
                        .stroke(.white, lineWidth: 40)
                }
                
            }
            .rotationEffect(.init(degrees: 180))
            .overlay {
                /// Custom Indicator
                IndicatorShape()
                    .fill(.black)
                    .overlay(alignment: .bottom) {
                        /// Circle at the base of the Indicator
                        Circle()
                            .fill(.purple)
                            .frame(width: 30, height: 30)
                            .offset(y: 10)
                    } //: Overlay Indicator Bottom Dot
                    .frame(width: 25, height: 110)
                    .padding(.top, 40)
                    .offset(y: -5)
                    .overlay(alignment: .top) {
                        /// Here is the Circle above the Indicator
                        Circle()
                            .fill(.white)
                            .frame(width: 20, height: 20)
                            .offset(y: 0)
                            .shadow(radius: 1)
                    }
                /// These two lines are to make the Indicator and the Circle move
                    .rotationEffect(.degrees(-90), anchor: .bottom)
                    .rotationEffect(.degrees(progress * 180), anchor: .bottom)
                    .offset(y: -75)
            } //: Overlay Indicator
            .frame(width: size.width, height: size.height, alignment: .center)
            
        }
        .padding(.bottom, -140)
        .onAppear(perform: {
            withAnimation(Animation.default.speed(0.1)){
                self.progress = 1
            }
        })
        .frame(maxHeight: .infinity, alignment: .center)
        
    }
    

    In order for this to work I had to set the progress to be in the 0-1 range. You can easily convert that to show 0-100. You may need to adjust the offset position of the circle of course and make any other adjustment you need. Here's the result:

    Gauge with masking effect

    Let me know if this worked for you!