swiftuiswiftui-stateswiftui-view

SwiftUI - view expand from the bottom of frame


I'd like to display a number of values as a continuous value from 0 to 1. I'd like them to grow from the bottom up, from 0 displaying no value, to 1 displaying a full height.

However, I'm unable to make it "grow from the bottom". I'm not sure what a better term for this is - it's a pretty simple vertical gauge, like a gas gauge in a car. I'm able to make it grow from the middle, but can't seem to find a way to make it grow from the bottom. I've played with mask and clipShape and overlay - but it must be possible to do this with just a simple View, and calculations on its height. I'd specifically like to able to show overlapping gauges, as the view below demonstrates.

My ContentView.swift is as follows:

import SwiftUI

struct ContentView: View {

  // binding values with some defaults to show blue over red
  @State var redPct: CGFloat = 0.75
  @State var bluePct: CGFloat = 0.25

  let DISP_PCT = 0.8 // quick hack - the top "gauge" takes this much so the sliders display below

  var body: some View {
    GeometryReader { geom in
      VStack {
        ZStack {

          // neutral background
          Rectangle()
            .fill(Color.gray)
            .frame(width: geom.size.width, height: geom.size.height * DISP_PCT)

          // the first gauge value display
          Rectangle()
            .fill(Color.red)
            .frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)

          // the second gauge value, on top of the first
          Rectangle() 
            .fill(Color.blue)
            .frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
        }

        HStack {
          Slider(value: self.$redPct, in: 0...1)
          Text("Red: \(self.redPct, specifier: "%.2f")")
        }

        HStack {
          Slider(value: self.$bluePct, in: 0...1)
          Text("Red: \(self.bluePct, specifier: "%.2f")")
        }
      }
    }
  }
}

As you play with the sliders, the red/blue views grows "out" from the middle. I would like them to grow "up" from the bottom of its containing view.

I feel like this is poorly worded - if any clarification is needed, please don't hesitate to ask!

Any help would be greatly appreciated!


Solution

  • You can't have them all in the same stacks. The easiest way to do this is to have your gray rectangle be your case view, and then overlay the others on top in VStacks with Spacers like this:

                // neutral background
                Rectangle()
                    .fill(Color.gray)
                    .frame(width: geom.size.width, height: geom.size.height * DISP_PCT)
                    .overlay (
                        ZStack {
                            VStack {
                                Spacer()
                                
                                // the first gauge value display
                                Rectangle()
                                    .fill(Color.red)
                                    .frame(width: geom.size.width, height: geom.size.height * DISP_PCT * redPct)
                            }
                            VStack {
                                Spacer()
                                // the second gauge value, on top of the first
                                Rectangle()
                                    .fill(Color.blue)
                                    .frame(width: geom.size.width, height: geom.size.height * DISP_PCT * bluePct)
                            }
                        }
                    )
    

    The overlay contains them, and the spacers push your rectangles down to the bottom of the stacks.