Semyon Tikhonenko
Semyon Tikhonenko

Reputation: 4262

SwiftUI. How to place a view at the top of ZStack?

I've tried this code and set alignment of the nested view to .top. But it's still cenetered.

struct ContentView: View {
    var body: some View {
        ZStack {
            Rectangle().fill(Color.red).frame(maxWidth: .infinity, maxHeight: 100, alignment: .top)
        }
    }
}

enter image description here

Upvotes: 2

Views: 7435

Answers (5)

swiftPunk
swiftPunk

Reputation: 1

You do not need use Rectangle for this work, use Color instead, it make your View be cheaper/faster in render and less CPU than using Rectangle and then trying to fill it with Color.


struct ContentView: View {
    var body: some View {
        ZStack {
            
            Color.yellow
            
            VStack {
                
                Color.red.frame(height: 100)
                
                Spacer()
                
                Color.blue.frame(height: 100)
                
            }
            
        }
    }
}

enter image description here

Upvotes: 4

Amisha Italiya
Amisha Italiya

Reputation: 246

You can try this :

struct ContentView: View {
    var body: some View {
        ZStack(alignment: .top) {
            Rectangle()
               .fill(Color.red)
               .frame(maxWidth: .infinity, maxHeight: 100)
        }
    }
}

Upvotes: 0

Rob Napier
Rob Napier

Reputation: 299613

The answers here are good, but it's worth diving into why you were confused in the first place. Let's look at your code:

ZStack {
    Rectangle()
        .fill(Color.red)
        .frame(maxWidth: .infinity, maxHeight: 100, alignment: .top)
}

This is a Rectangle, enclosed in a Fill, enclosed in a Frame, enclosed in a ZStack. Each of these is its own View.

First, the Rectangle declares that it will fill whatever space it is offered with a stroked and filled rectangle, using whatever stroke and fill color are set by its parents.

The Fill, which wraps the Rectangle, sets the fill color to red for its children.

The Frame offers "the lessor of what the Frame is offered and infinity" as the width and "the lessor of what the Frame is offered and 100" as the hight to its children. Its children (the Fill, which contains the Rectangle), accept all the space they're offered, which in the end will be "the width of the screen and 100 points high." The frame sets its own width to this, and aligns its one child at the top.

The ZStack offers the whole screen size to the Frame. When the Frame is done, the ZStack sets its own size to the size of its children (the Frame), and centers its children (since that's its alignment).

The containing View (which is the size of the screen), then centers the small ZStack.

The key point is that the .top applies to the Frame, not the ZStack, and not the entire View.

So how do you get what you want? First of all, you want the ZStack to be the size of the entire View. That means its children must be the size of the entire View. A ZStack is always the exact size of its children.

But really, the natural way to do what you're describing is with a VStack, since you really want a vertical stack of views:

VStack {
    Rectangle().fill(Color.red).frame(height: 100)
    Spacer()
}

Or as Swiftpunk points out, you can use Color directly, though it's a trivial difference. It just is a little shorter in code. Setting the width here isn't meaningful.

VStack {
    Color.red.frame(maxHeight: 100)
    Spacer()
}

If you wanted a ZStack (I don't know why, but if you did), then you could achieve the same thing by forcing the ZStack to be as big as it's offered by including a Color, and then also ask it to align its elements at the topLeading.

ZStack(alignment: .topLeading) {
    Color.clear // Color is always as large as the space offered
    Color.red.frame(maxHeight: 100)
}

Upvotes: 1

skaak
skaak

Reputation: 3018

Another very direct way of doing it - you need a GeometryReader but to position the x not the y.

        GeometryReader { reader in
            ZStack {
                Rectangle()
                    .fill ( Color.red )
                    .frame ( height: h )
                    .position ( x: 0.5 * reader.size.width, y: 0.5 * h )
            }
        }

Here h is the desired height.

Upvotes: 2

jnpdx
jnpdx

Reputation: 52615

The surrounding ZStack needs to have a frame that will take up the whole view -- otherwise it'll just be the height of the rectangle. It's also the one that needs the .top alignment:

struct ContentView: View {
    var body: some View {
        ZStack {
            Rectangle()
                .fill(Color.red)
                .frame(height: 100)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}

Update, based on comment:

struct ContentView: View {
    var body: some View {
        ZStack {
            VStack {
                Rectangle()
                    .fill(Color.red)
                    .frame(height: 100)
                Spacer()
                Rectangle()
                    .fill(Color.green)
                    .frame(height: 100)
            }
        }
    }
}

Upvotes: 2

Related Questions