Home > Enterprise >  How to create a Battery Badge that shows current Battery Level in SwiftUI
How to create a Battery Badge that shows current Battery Level in SwiftUI

Time:10-28

What I want to do is almost identical to the badge on MacOS and iOS devices I tried to use SFSymbols for this but it only supports %0, %, P, u and 0 but I want to update my battery badge to the current battery level.

img

I've tried to use battery.0 icon in SFSymbols with an RoundedRectangle overlapping it like this:

struct BatteryView : View {
    var body: some View {
            ZStack {
                Image(systemName: "battery.0")
                RoundedRectangle(cornerRadius: 15)
                    .foregroundColor(.green)
            }
    }
}

but it acts really weird when I try to scale it up or down because of the RoundedRectangle.

Thanks in advance!

CodePudding user response:

Here's what I came up with:

As far as I can see, the battery icon in SFSymbols consists of only 3 elements, a rounded rectangle, a half circle that stands on top of the battery, and another rounded rectangle that indicates the current battery level.

First rounded Rectangle (Battery Case)

struct BatteryView : View {
    var body: some View {
        GeometryReader { geo in
            RoundedRectangle(cornerRadius: 15)
                .stroke(lineWidth: 3)
       } 
   }
}

and it looks like this(with a frame of 180, 60):

battery case

I just used a rounded rectangle for the battery case as you suggested.

Half circle

I created a simple shape called HalfCircleShape for this.

struct HalfCircleShape : Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        path.move(to: CGPoint(x: rect.minX, y: rect.midY))
        path.addArc(center: CGPoint(x: rect.minX, y: rect.midY), radius: rect.height , startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
        return path
    }
}

and in the GeometryReader I give a frame of 1/7 of the current frame because it looks elegant.

and I combine these with a HStack

battery_2

like this:

GeometryReader { geo in
    HStack {
        RoundedRectangle(cornerRadius: 15)
            .stroke(lineWidth: 3)
        
        HalfCircleShape()
            .frame(width: geo.size.width / 7, height: geo.size.height / 7)
    }
}

Battery Indicator

I create an extension in Color to choose the color with the current battery level


extension Color {
    static var BatteryLevel : Color {
        let batteryLevel = 0.6
        print(batteryLevel)
        switch batteryLevel {
            // returns red color for range %0 to  
            case 0...0.2:
                return Color.red
            // returns yellow color for range   to P
            case 0.2...0.5:
                return Color.yellow
            // returns green color for range P to 0
            case 0.5...1.0:
                return Color.green
            default:
                return Color.clear
        }
    }
}

And I just created another RoundedRectangle and combine the two RoundedRectangles in a ZStack

Result looks like this: red green yellow

Final code:


import SwiftUI

struct ContentView: View {
    var body: some View {
            BatteryView()
            .frame(width: 170, height: 60)
            .padding()
    }
}

struct HalfCircleShape : Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        path.move(to: CGPoint(x: rect.minX, y: rect.midY))
        path.addArc(center: CGPoint(x: rect.minX, y: rect.midY), radius: rect.height , startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true)
        return path
    }
}

struct BatteryView : View {
    init() {
        UIDevice.current.isBatteryMonitoringEnabled = true
    }
    var body: some View {
        // UIDevice.current.batteryLevel always returns -1, and I don't know why. so here's a value for you to preview
        let batteryLevel = 0.4
        GeometryReader { geo in
            HStack(spacing: 5) {
                GeometryReader { rectangle in
                    RoundedRectangle(cornerRadius: 15)
                        .stroke(lineWidth: 3)
                    RoundedRectangle(cornerRadius: 15)
                        .padding(5)
                        .frame(width: rectangle.size.width - (rectangle.size.width * (1 - batteryLevel)))
                        .foregroundColor(Color.BatteryLevel)
                }
                HalfCircleShape()
                .frame(width: geo.size.width / 7, height: geo.size.height / 7)
                
            }
            .padding(.leading)
        }
    }
}

extension Color {
    static var BatteryLevel : Color {
        let batteryLevel = 0.4
        print(batteryLevel)
        switch batteryLevel {
            // returns red color for range %0 to  
            case 0...0.2:
                return Color.red
            // returns yellow color for range   to P
            case 0.2...0.5:
                return Color.yellow
            // returns green color for range P to 0
            case 0.5...1.0:
                return Color.green
            default:
                return Color.clear
        }
    }
}

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

Also, in UIDevice, there's a propety called batteryLevel that gives you the current battery level, but in this case, it only returns -1, it looks like a SwiftUI bug so I'll create another StackOverFlow question and leave a link to here.

  • Related