Animated Spending Bar Using SwiftUI
SwiftUI Showcase

Animated Spending Bar Using SwiftUI

Jean-Marc Boullianne
Jean-Marc Boullianne

I had some free time ealrier today so I decided to create something quick and simple. As I was browsing my spending app of choice, I was inspired by the 'Spending' view they had. I decided, "why not re-create it using SwiftUI?".

Below I've provided the code to my implementation, as well as a test of it in action. Enjoy and please consider subscribing to support my future dev efforts!

SegmentedBar Code

struct SegmentedBar: View {
    
    @Binding var values:[Double]
    @State var colors:[Color]
    @State var isVertical:Bool
    
    private var totalValue:Double {
        get {
            return values.reduce(0) { (res, val) -> Double in
                return res + val
            }
        }
    }
        
    var body: some View {
        GeometryReader { geometry in
            if self.isVertical { // Vertical Stack
                VStack(alignment: .center, spacing: 4) {
                    ForEach(self.values.indices) { i in
                        Rectangle()
                            .frame(width: nil, height: geometry.size.height * CGFloat(self.values[i] / self.totalValue), alignment: .center)
                            .foregroundColor(self.colors[i])
                            .cornerRadius(geometry.size.width/2.0)
                            .animation(.easeInOut)
                    }
                }
            } else { // Horizontal Stack
                HStack(alignment: .center, spacing: 4.0) {
                    ForEach(self.values.indices) { i in
                        Rectangle()
                            .frame(width: geometry.size.width * CGFloat(self.values[i] / self.totalValue), height: nil, alignment: .center)
                            .foregroundColor(self.colors[i])
                            .cornerRadius(geometry.size.height/2.0)
                            .animation(.easeInOut)
                    }
                }
            }
        }
    }
}

Example Usage in App

//
//  SegmentedBarTest.swift
//  TrailingClosureSwiftUI
//
//  Created by Jean-Marc Boullianne on 12/1/19.
//  Copyright © 2019 Jean-Marc Boullianne. All rights reserved.
//

import SwiftUI

struct SegmentedBarTest: View {
    
    @State var values:[Double] = [500, 320]
    var colors:[Color] = [Color.green, Color.red]
    var isVertical = false
    
    // Total of all Value data Combined
    var totalValue:Double {
        get {
            values.reduce(0) { (res, val) -> Double in
                return res + val
            }
        }
    }
    
    var body: some View {
            
        VStack(alignment: .center, spacing: 10) {
            
            Text("Spending")
                .font(Font.system(size: 22, weight: .bold, design: .rounded))
                .padding(.top, 20)
            
            VStack(alignment: .center, spacing: 12) {
                
                HStack(alignment: .center, spacing: 20) {
                    Text("Total")
                        .font(Font.system(size: 22, weight: .bold, design: .rounded))
                    Spacer()
                    
                    VStack {
                        Text("Spent")
                            .font(Font.system(size: 14, weight: .medium, design: .default))
                            .foregroundColor(Color.gray)
                        HStack(alignment: .center, spacing: 0) {
                            Text("-")
                                .font(Font.system(size: 14, weight: .medium, design: .default))
                                .foregroundColor(Color.red)
                            Text("$\(Int(values[1]))")
                                .font(Font.system(size: 14, weight: .bold, design: .rounded))
                                .animation(.none)
                        }
                    }
                    
                    VStack {
                        Text("Income")
                            .font(Font.system(size: 14, weight: .medium, design: .default))
                            .foregroundColor(Color.gray)
                        HStack(alignment: .center, spacing: 0) {
                            Text("+")
                                .font(Font.system(size: 14, weight: .medium, design: .default))
                                .foregroundColor(Color.green)
                            Text("$\(Int(totalValue))")
                                .font(Font.system(size: 14, weight: .bold, design: .rounded))
                                .animation(.none)
                        }
                        
                    }
                }
                
                /* -------------------------------
                    MARK: SegmentedBar
                   -------------------------------
                */
                SegmentedBar(values: $values, colors: colors, isVertical: isVertical)
                    .frame(width: nil, height: 6, alignment: .center)
            }
            .padding(18)
            .background(Color.white)
            .cornerRadius(10)
            .padding()
            .shadow(color: Color.gray.opacity(0.5), radius: 50, x: 0, y: 2)
            
            // MARK: Spending Buttons
            // Buttons To Spend/Unspend Money
            HStack(alignment: .center, spacing: 20) {
                Button(action: {
                    withAnimation {
                        self.values[0] += 150
                        self.values[1] -= 150
                    }
                }) {
                    Text("$")
                        .font(Font.system(size: 20, weight: Font.Weight.bold, design: .default))
                        .foregroundColor(Color.green)
                        .padding(.horizontal, 25)
                        .padding(.vertical, 8)
                        .background(Color.white)
                        .cornerRadius(5)
                        .shadow(color: Color.gray.opacity(0.5), radius: 25, x: 0, y: 2)
                }
                Button(action: {
                    withAnimation {
                        self.values[0] -= 150
                        self.values[1] += 150
                    }
                }) {
                    Text("$")
                        .font(Font.system(size: 20, weight: Font.Weight.bold, design: .default))
                        .foregroundColor(Color.red)
                        .padding(.horizontal, 25)
                        .padding(.vertical, 8)
                        .background(Color.white)
                        .cornerRadius(5)
                        .shadow(color: Color.gray.opacity(0.5), radius: 25, x: 0, y: 2)
                }
            }
            Spacer()
        }
        
    }
}

struct SegmentedBarTest_Previews: PreviewProvider {
    static var previews: some View {
        SegmentedBarTest()
    }
}