Home > Enterprise >  Alignment for GeometryReader and .frame
Alignment for GeometryReader and .frame

Time:05-17

I'm using GeometryReader() and .frame() to show their comparability and playing with their width. However the object wrapped in GeometryReader() doesn't have center alignment when width is less than minimum. Please refer to attached gifs for demo.

Could you please help me with alignment?

Ideal:

Ideal

Current: Current

struct ExampleView: View {
@State private var width: CGFloat = 50

var body: some View {
    VStack {
        SubView()
            .frame(width: self.width, height: 120)
            .border(Color.blue, width: 2)
        
        Text("Offered Width is \(Int(width))")
        Slider(value: $width, in: 0...200, step: 5)
      }
   }
}

struct SubView: View {
    var body: some View {
        GeometryReader { geometry in
            Rectangle()
                .fill(Color.yellow.opacity(0.6))
                .frame(width: max(geometry.size.width, 120), height: max(geometry.size.height, 120))
        }
    }
}

CodePudding user response:

That's because GeometryReader doesn't center its children.

You have to manually position the Rectangle by adding either a .position modifier or a .offset.

.position will move the origin of the rectangle's frame relative to the parent's center.

.offset will not change the frame, but rather change the rendering (working like a CGAffineTransform with translation).

The following modifiers to your Rectangle will yield the same results visually (though different under the hood):

Rectangle()
    .fill(Color.yellow.opacity(0.6))
    .frame(width: max(geometry.size.width, 120), height: max(geometry.size.height, 120))
    .position(x: geometry.size.width / 2, y: geometry.size.height / 2)

or

let rectWidth = max(geometry.size.width, 120)
Rectangle()
    .fill(Color.yellow.opacity(0.6))
    .frame(width: rectWidth, height: max(geometry.size.height, 120))
    .offset(x: ((geometry.size.width - rectWidth) / 2), y: 0)

Note that your rectangle's frame exceeds the bounds of its parent. I'd suggest to avoid that because it will cause all sorts of difficulties laying out other UI elements on the screen.

You could build it the other way around (unless your practical goal is to just understand how GeometryReader works):

struct ExampleView: View {
    @State private var width: CGFloat = 50

    var body: some View {
        VStack {
            let minWidth: CGFloat = 120
            let subViewWidth: CGFloat = max(minWidth, width)
            SubView(desiredWidth: width)
                .frame(width: subViewWidth, height: 120)
                .background(.yellow.opacity(0.6))

            Text("Offered Width is \(Int(width))")
            Slider(value: $width, in: 0...200, step: 5)
        }
    }
}

struct SubView: View {
    let desiredWidth: CGFloat
    var body: some View {
        Rectangle()
            .fill(.clear)
            .border(Color.blue, width: 2)
            .frame(width: desiredWidth, height: nil)
    }
}

In this example the SubView has a yellow fill and a minimal frame width while the inner rectangle just takes whatever width is set on the slider. It also doesn't need a GeometryReader anymore. It looks and behaves the same but none of the views exceeds its parent's bounds anymore.

  • Related