eugene_prg
eugene_prg

Reputation: 1168

Positioning inside ZStack

I want this red rectangle to be repositioned to the left bottom corner but for some reason my alignments and paddings do not work as I expected. What am i doing wrong and how to move it to the left bottom corner?

Code:

struct ContentView: View {
    
    var body: some View {

        ZStack {
                VStack(spacing: 0) {
                    Rectangle()
                        .fill(Color.red)
                        .frame(width: 197, height: 163)
                }
                .frame(width: 284, height: 269)
                .padding(.leading, 0)
                .padding(.bottom, 0)
                .background(Color.blue)
            }
    }
}

Upvotes: 13

Views: 18408

Answers (2)

Rob Napier
Rob Napier

Reputation: 299355

Yodagama's answer may be what you want, but it's worth exploring your confusion, too, because SwiftUI layout does not work the way you're thinking it does. Calls like .frame(...) does not "set the size of an item" and .fill(...) does not "set the color of the item." Almost everything in SwiftUI creates a brand new View, which is responsible for placing its children. In many cases these can feel like they're the same thing, but they're very different.

I'll break down what's happening in your code, starting at the inside and working out.

                Rectangle()

This creates a Rectangle that fills whatever space it's given.

                Rectangle()
                    .fill(Color.blue)

This embeds the Rectangle in a new View that sets the "fill" color in the Environment to Blue.

                Rectangle()
                    .fill(Color.blue)
                    .frame(width: 197, height: 163)

This embeds that into a new View that has a fixed size (197x163). The embedded View is centered, but since the Rectangle takes all available space, it fills the Frame.

            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }

This embeds that Frame into a VStack that puts no spacing between its elements (but that doesn't matter because there's only one). The VStack is exactly the size of its children (197x163).

            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }
            .frame(width: 284, height: 269)

This centers the 197x163 VStack inside a new View that is fixed to 284x269. It does not set the size of the VStack to 284x269. VStacks are always precisely the size of their children. This is the specific place you went wrong (I'll explain how to fix it after walking through the rest).

            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }
            .frame(width: 284, height: 269)
            .padding(.leading, 0)
            .padding(.bottom, 0)

This create a new View that embeds the 284x269 Frame with 0 extra padding on the left (so this new View is exactly the same size as the current Frame and this does nothing). It then embeds that into a new View that adds 0 extra padding on the bottom. Again, this does nothing at all.

           VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }
            .frame(width: 284, height: 269)
            .padding(.leading, 0)
            .padding(.bottom, 0)
            .background(Color.blue)

This embeds all that in a new View that draws a Blue background exactly the same size as the embedded View (284x269).

    ZStack {
            VStack(spacing: 0) {
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }
            .frame(width: 284, height: 269)
            .padding(.leading, 0)
            .padding(.bottom, 0)
            .background(Color.blue)
        }
}

And finally, this centers all of that inside a ZStack. The ZStack is exactly the size required to wrap its children, so that's 284x269. This does nothing at all.


So how do you fix this. The place you went wrong was how the VStack was positioned in its surrounding Frame. It was centered, but you wanted it aligned. There's no need for a ZStack here, or even a VStack. You do that like this:

    Rectangle()
        .fill(Color.red)
        .frame(width: 197, height: 163)
        .frame(width: 284, height: 269, alignment: .bottomLeading) // <===
        .background(Color.blue)

Notice how two frame Views are created in succession. The first one bounds the Rectangle. The second one embeds and aligns that in a larger frame. This is a good example of how SwiftUI is so different than developers may think. In fact, this can be simplified further, since a Color fills all of its offered space, just like a Rectangle:

    Color.red
        .frame(width: 197, height: 163)
        .frame(width: 284, height: 269, alignment: .bottomLeading)
        .background(Color.blue)

But I often find this is better done and more flexible with Spacers when possible. In my experience, Spacers work better when you don't have fixed frame sizes for everything. (I'm not saying this is necessarily best practice; it's just been my experience. SwiftUI is pretty new and we're still figuring out what best practice is.)

    VStack {
        Spacer()    // Fill all space above
        HStack {
            Rectangle()
                .fill(Color.red)
                .frame(width: 197, height: 163)
            Spacer()    // Fill all space to the right
        }
    }
    .frame(width: 284, height: 269)
    .background(Color.blue)

Upvotes: 28

YodagamaHeshan
YodagamaHeshan

Reputation: 6510

ZStack has a parameter alignment: Alignment which is an alignment in both axes.

  struct ContentView: View {
        
        var body: some View {
            
            ZStack(alignment:.bottomLeading) {
                Rectangle()
                    .fill(Color.blue)
                    .frame(width: 284, height: 269)
                Rectangle()
                    .fill(Color.red)
                    .frame(width: 197, height: 163)
            }
            
            
        }
    }

Upvotes: 17

Related Questions