Proportional height (or width) in SwiftUI

25,955

You can make use of GeometryReader. Wrap the reader around all other views and use its closure value metrics to calculate the heights:

let propHeight = metrics.size.height * 0.43

Use it as follows:

import SwiftUI

struct ContentView: View {
    var body: some View {
        GeometryReader { metrics in
            VStack(spacing: 0) {
                Color.red.frame(height: metrics.size.height * 0.43)
                Color.green.frame(height: metrics.size.height * 0.37)
                Color.yellow
            }
        }
    }
}

import PlaygroundSupport

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
Share:
25,955
superpuccio
Author by

superpuccio

I’ve been a passionate iOS developer for more than six years. I started with Objective-C, then moved to Swift. SwiftUI/Combine early adopter. Author of https://github.com/matteopuc/swiftui-navigation-stack

Updated on August 19, 2021

Comments

  • superpuccio
    superpuccio over 2 years

    I started exploring SwiftUI and I can't find a way to get a simple thing: I'd like a View to have proportional height (basically a percentage of its parent's height). Let's say I have 3 views vertically stacked. I want:

    • The first to be 43% (of its parent's height) high
    • The second to be 37% (of its parent's height) high
    • The last to be 20% (of its parent's height) high

    I watched this interesting video from the WWDC19 about custom views in SwiftUI (https://developer.apple.com/videos/play/wwdc2019/237/) and I understood (correct me if I'm wrong) that basically a View never has a size per se, the size is the size of its children. So, the parent view asks its children how tall they are. They answer something like: "half your height!" and then... what? How does the layout system (that is different from the layout system we are used to) manage this situation?

    If you write the below code:

    struct ContentView : View {
        var body: some View {
            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                Rectangle()
                    .fill(Color.green)
                Rectangle()
                    .fill(Color.yellow)
            }
        }
    }
    

    The SwiftUI layout system sizes each view to be 1/3 high and this is right according to the video I posted here above. You can wrap the rectangles in a frame this way:

    struct ContentView : View {
        var body: some View {
            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(height: 200)
                Rectangle()
                    .fill(Color.green)
                    .frame(height: 400)
                Rectangle()
                    .fill(Color.yellow)
            }
        }
    }
    

    This way the layout system sizes the first rectangle to be 200 high, the second one to be 400 high and the third one to fit all the left space. And again, this is fine. What you can't do (this way) is specifying a proportional height.

    • Marc T.
      Marc T. over 4 years
      You could use GeometryReader to get the size of the parent to calculate it by yourself.
    • impression7vx
      impression7vx over 4 years
      According to the slides on the link, the parent can propose a size for the child. The child then manages itself and creates it size relation to that proposition. What results are you getting when you do the above?
    • superpuccio
      superpuccio over 4 years
      @impression7vx Take a look at the EDIT.
  • superpuccio
    superpuccio over 4 years
    Thanks for your help. When you get the chance can you slightly improve your answer? What I don't get here is why you have to specify that "layoutPriority(1)" modifier (I know what layoutPriority does, but I don't understand why it's needed here). Also, can you take a look at my EDIT and explain us why the "relativeHeight" modifier won't work here? Thank you again.
  • rob mayoff
    rob mayoff over 4 years
    You have to specify layoutPriority because it doesn’t work otherwise. I don’t understand why it doesn’t work otherwise. It might be a SwiftUI bug. All I know is that, from experimentation, I found that using layoutPriority makes it work. As for relativeHeight, it is deprecated, so I’m not interested in investigating how to make it work.
  • superpuccio
    superpuccio over 4 years
    Thx. I didn't know that relativeHeight was deprecated. For the layoutPriority issue I'll keep trying to find out what's happening.
  • kontiki
    kontiki over 4 years
    It seems beta 5 fixed the .layoutPriority() problem and might not be needed anymore for this to work.
  • superpuccio
    superpuccio over 4 years
    Just updated MacOS and xCode to Beta 5 and, as suggested by @kontiki, there's no need for the .layoutPriority() anymore. Rob, would you mind update your answer? Thank you guys.
  • superpuccio
    superpuccio over 4 years
    relativeHeight and relativeWidth are completely disappeared :)