254

Given this code:

import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Title")
        .font(.title)

      Text("Content")
        .lineLimit(nil)
        .font(.body)

      Spacer()
    }
    .background(Color.red)
  }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
#endif

It results in this interface:

preview

How can I make the VStack fill the width of the screen even if the labels/text components don't need the full width?


A trick I've found is to insert an empty HStack in the structure like so:

VStack(alignment: .leading) {
  HStack {
    Spacer()
  }
  Text("Title")
    .font(.title)

  Text("Content")
    .lineLimit(nil)
    .font(.body)

  Spacer()
}

Which yields the desired design:

desired output

Is there a better way?

EricSchaefer
  • 25,272
  • 21
  • 67
  • 103
ielyamani
  • 17,807
  • 10
  • 55
  • 90
  • 1
    See [my answser](https://stackoverflow.com/a/61444475/1966109) that shows up to 5 alternative ways to have a similar result. – Imanou Petit Apr 26 '20 at 20:43
  • I add a new way of doing this much less code and reusable in compare to other answers which they are not reusable! https://stackoverflow.com/a/67147114/14998134 – ios coder Apr 18 '21 at 09:55

18 Answers18

377

Try using the .frame modifier with the following options:

.frame(
      minWidth: 0,
      maxWidth: .infinity,
      minHeight: 0,
      maxHeight: .infinity,
      alignment: .topLeading
    )
struct ContentView: View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Hello World")
        .font(.title)
      Text("Another")
        .font(.body)
      Spacer()
    }
    .frame(
      minWidth: 0,
      maxWidth: .infinity,
      minHeight: 0,
      maxHeight: .infinity,
      alignment: .topLeading
    )
    .background(Color.red)
  }
}

This is described as being a flexible frame (see the documentation), which will stretch to fill the whole screen, and when it has extra space it will center its contents inside of it.

egeeke
  • 753
  • 1
  • 6
  • 18
svarrall
  • 8,545
  • 2
  • 27
  • 32
  • The *subviews* don't align to left (`.leading`). Takes the full width if placed before `.background(Color.red)` – ielyamani Jun 07 '19 at 14:00
  • Added code to align to the left as well. Should match exactly now – svarrall Jun 07 '19 at 14:21
  • 31
    This is wrong. The `.backgound(Color.red)` is the background of the frame's view, but not the background of the `VStack` that is in the frame view. (The reason for this is explained in the WWDC videos :P). If you put `.background(Color.green)` before the `.frame` you will see the `VStack` still has a width that fits its content while the frame has a red background...When you put `.frame(...)`, it's not editing the `VStack`, it's actually creating another view. – Jake Jun 26 '19 at 18:30
  • 2
    Have a question tho: Why to use alignment: .topLeading since we're having VStack(alignment: .leading) ?? Thanks for sharing – UIChris Mar 26 '20 at 03:37
  • [Deleted - commented on the wrong answer] – PipEvangelist Nov 03 '21 at 03:17
  • I've been debugging for a couple of hours now, pulling my hairs out, 'til I saw the frame options above. I know how to use that but never thought of trying that solution. For context, my problem is that my custom rows/views hstacks with the typical layout icon + multi-line text are having inconsistent width. Thanks for this reminder. – Glenn Posadas Mar 21 '22 at 07:49
  • In fact `.frame(maxWidth: .infinity, alignment: .topLeading)` is sufficient for most situations, if you are a minimalist. – mojuba May 26 '22 at 14:48
  • `alignment: .topLeading` was the argument that I've been required. Kudos! – Konstantin Nikolskii Jul 11 '22 at 23:34
85

With Swift 5.2 and iOS 13.4, according to your needs, you can use one of the following examples to align your VStack with top leading constraints and a full size frame.

Note that the code snippets below all result in the same display, but do not guarantee the effective frame of the VStack nor the number of View elements that might appear while debugging the view hierarchy.


1. Using frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) method

The simplest approach is to set the frame of your VStack with maximum width and height and also pass the required alignment in frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:):

struct ContentView: View {

    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            Text("Content")
                .font(.body)
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity,
            alignment: .topLeading
        )
        .background(Color.red)
    }

}

2. Using Spacers to force alignment

You can embed your VStack inside a full size HStack and use trailing and bottom Spacers to force your VStack top leading alignment:

struct ContentView: View {

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)

                Spacer() // VStack bottom spacer
            }

            Spacer() // HStack trailing spacer
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity
        )
        .background(Color.red)
    }

}

3. Using a ZStack and a full size background View

This example shows how to embed your VStack inside a ZStack that has a top leading alignment. Note how the Color view is used to set maximum width and height:

struct ContentView: View {

    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.red
                .frame(maxWidth: .infinity, maxHeight: .infinity)

            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
        }
    }

}

4. Using GeometryReader

GeometryReader has the following declaration:

A container view that defines its content as a function of its own size and coordinate space. [...] This view returns a flexible preferred size to its parent layout.

The code snippet below shows how to use GeometryReader to align your VStack with top leading constraints and a full size frame:

struct ContentView : View {

    var body: some View {
        GeometryReader { geometryProxy in
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
            .frame(
                width: geometryProxy.size.width,
                height: geometryProxy.size.height,
                alignment: .topLeading
            )
        }
        .background(Color.red)
    }

}

5. Using overlay(_:alignment:) method

If you want to align your VStack with top leading constraints on top of an existing full size View, you can use overlay(_:alignment:) method:

struct ContentView: View {

    var body: some View {
        Color.red
            .frame(
                maxWidth: .infinity,
                maxHeight: .infinity
            )
            .overlay(
                VStack(alignment: .leading) {
                    Text("Title")
                        .font(.title)
                    Text("Content")
                        .font(.body)
                },
                alignment: .topLeading
            )
    }

}

Display:

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
66

An alternative stacking arrangement which works and is perhaps a bit more intuitive is the following:

struct ContentView: View {
    var body: some View {
        HStack() {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
            }
            Spacer()
        }.background(Color.red)
    }
}

The content can also easily be re-positioned by removing the Spacer()'s if necessary.

adding/removing Spacers to change position

svarrall
  • 8,545
  • 2
  • 27
  • 32
35

There is a better way!

To make the VStack fill the width of it's parent you can use a GeometryReader and set the frame. (.relativeWidth(1.0) should work but apparently doesn't right now)

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("test")
            }
                .frame(width: geometry.size.width,
                       height: nil,
                       alignment: .topLeading)
        }
    }
}

To make the VStack the width of the actual screen you can use UIScreen.main.bounds.width when setting the frame instead of using a GeometryReader, but I imagine you likely wanted the width of the parent view.

Also, this way has the added benefit of not adding spacing in your VStack which might happen (if you have spacing) if you added an HStack with a Spacer() as it's content to the VStack.

UPDATE - THERE IS NOT A BETTER WAY!

After checking out the accepted answer, I realized that the accepted answer doesn't actually work! It appears to work at first glance, but if you update the VStack to have a green background you'll notice the VStack is still the same width.

struct ContentView : View {
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
                }
                .background(Color.green)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
                .background(Color.red)
        }
    }
}

This is because .frame(...) is actually adding another view to the view hierarchy and that view ends up filling the screen. However, the VStack still does not.

This issue also seems to be the same in my answer as well and can be checked using the same approach as above (putting different background colors before and after the .frame(...). The only way that appears to actually widen the VStack is to use spacers:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            HStack{
                Text("Title")
                    .font(.title)
                Spacer()
            }
            Text("Content")
                .lineLimit(nil)
                .font(.body)
            Spacer()
        }
            .background(Color.green)
    }
}
Jake
  • 13,097
  • 9
  • 44
  • 73
  • Had tried `relativeWidth`, didn't work. Somehow `GeometryReader` works if `.frame` is placed before `.background(Color.red)` – ielyamani Jun 07 '19 at 14:38
  • I can't see `GeometryReader` in the list shown when I "Add Modifier" after I `Show SwiftUI Inspector`, or any where else within the UI. How did you discover `GeometryReader`? – gone Mar 03 '20 at 00:40
  • 1
    @gone - Stack Overflow :) – Jake Mar 03 '20 at 00:53
  • After testing the proposed solutions (also with iOS 15 beta), it turned out, the only way to accomplish a _correct_ solution is to use a GeometryReader. Partly, because filling the whole screen with `.infinity` values with the `frame` modifier is actually not always what you want at the end. – CouchDeveloper Jul 24 '21 at 14:45
19

EDIT: answer updated with simple (better) approach using .frame

Just use frame modifiers!

struct Expand: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            
            Text("Content")
                .lineLimit(nil)
                .font(.body)
        }
        .frame(maxWidth:.infinity,maxHeight:.infinity,alignment:.topLeading)
        .background(Color.red)
    }
}

note - you don't even need the spacer in the VStack!

note2 - if you don't want the white at top & bottom, then in the background use:

Color.red.edgesIgnoringSafeArea(.all)

enter image description here

Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
14

The simplest way I manage to solve the issue was is by using a ZStack + .edgesIgnoringSafeArea(.all)

struct TestView : View {

    var body: some View {
        ZStack() {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack {
                Text("Hello World")
            }
        }
    }
}
thiezn
  • 1,874
  • 1
  • 17
  • 24
10

Preview Image

Way Number 1 -> Using MaxWidth & MaxHeight

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Way Number 2 -> Using Main Screen Bounds

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Way Number 3 -> Using Geometry Reader

import SwiftUI

struct SomeView: View {
    var body: some View {
        GeometryReader { geometryReader in
            VStack {
                Text("Hello, World!")
            }
            .frame(maxWidth: geometryReader.size.width, maxHeight: geometryReader.size.height)
            .background(.red)
        }
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

Way Number 4 -> Using Spacers

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
            HStack{
                Spacer()
            }
            Spacer()
        }
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}
Umair Khan
  • 993
  • 8
  • 14
6

use this

.edgesIgnoringSafeArea(.all)
Tom
  • 1,516
  • 1
  • 14
  • 34
fouad mazouz
  • 71
  • 1
  • 3
6

A good solution and without "contraptions" is the forgotten ZStack

ZStack(alignment: .top){
    Color.red
    VStack{
        Text("Hello World").font(.title)
        Text("Another").font(.body)
    }
}

Result:

enter image description here

Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Kellvem Alves
  • 81
  • 1
  • 7
5

You can do it by using GeometryReader

GeometryReader

Code:

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
               Text("Turtle Rock").frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading).background(Color.red)
            }
        }
    }
}

Your output like:

enter image description here

Community
  • 1
  • 1
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
5

One more alternative is to place one of the subviews inside of an HStack and place a Spacer() after it:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {

            HStack {
                Text("Title")
                    .font(.title)
                    .background(Color.yellow)
                Spacer()
            }

            Text("Content")
                .lineLimit(nil)
                .font(.body)
                .background(Color.blue)

            Spacer()
            }

            .background(Color.red)
    }
}

resulting in :

HStack inside a VStack

ielyamani
  • 17,807
  • 10
  • 55
  • 90
4

This is a useful bit of code:

extension View {
    func expandable () -> some View {
        ZStack {
            Color.clear
            self
        }
    }
}

Compare the results with and without the .expandable() modifier:

Text("hello")
    .background(Color.blue)

-

Text("hello")
    .expandable()
    .background(Color.blue)

enter image description here

jeremyabannister
  • 3,796
  • 3
  • 16
  • 25
3

This is what worked for me (ScrollView (optional) so more content can be added if needed, plus centered content):

import SwiftUI

struct SomeView: View {
    var body: some View {
        GeometryReader { geometry in
            ScrollView(Axis.Set.horizontal) {
                HStack(alignment: .center) {
                    ForEach(0..<8) { _ in
                        Text("")
                    }
                }.frame(width: geometry.size.width, height: 50)
            }
        }
    }
}

// MARK: - Preview

#if DEBUG
struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}
#endif

Result

centered

backslash-f
  • 7,923
  • 7
  • 52
  • 80
3

I know this will not work for everyone, but I thought it interesting that just adding a Divider solves for this.

struct DividerTest: View {
var body: some View {
    VStack(alignment: .leading) {
        Text("Foo")
        Text("Bar")
        Divider()
    }.background(Color.red)
  }
}
Jason
  • 2,223
  • 2
  • 20
  • 28
2

enter image description here

Login Page design using SwiftUI

import SwiftUI

struct ContentView: View {

    @State var email: String = "william@gmail.com"
    @State var password: String = ""
    @State static var labelTitle: String = ""


    var body: some View {
        VStack(alignment: .center){
            //Label
            Text("Login").font(.largeTitle).foregroundColor(.yellow).bold()
            //TextField
            TextField("Email", text: $email)
                .textContentType(.emailAddress)
                .foregroundColor(.blue)
                .frame(minHeight: 40)
                .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))

            TextField("Password", text: $password) //Placeholder
                .textContentType(.newPassword)
                .frame(minHeight: 40)
                .foregroundColor(.blue) // Text color
                .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))

            //Button
            Button(action: {

            }) {
                HStack {
                    Image(uiImage: UIImage(named: "Login")!)
                        .renderingMode(.original)
                        .font(.title)
                        .foregroundColor(.blue)
                    Text("Login")
                        .font(.title)
                        .foregroundColor(.white)
                }


                .font(.headline)
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
                .cornerRadius(40)
                .padding(.horizontal, 20)

                .frame(width: 200, height: 50, alignment: .center)
            }
            Spacer()

        }.padding(10)

            .frame(minWidth: 0, idealWidth: .infinity, maxWidth: .infinity, minHeight: 0, idealHeight: .infinity, maxHeight: .infinity, alignment: .top)
            .background(Color.gray)

    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Srinivasan_iOS
  • 972
  • 10
  • 12
2

⚠️ Important Note!

All other solutions are just adding a frame around the content!

✅ but this solution changes the actual frame!


Simple and correct extension

You can use this modifier

.flexible(width: true, height: false)

Demo

Demo

Note how contents are aligned exactly as you assign in the original stack


The code behind this ( FlexibleViewModifier.swift )

extension View {
    func flexible(width: Bool, height: Bool) -> some View {
        self.modifier(MatchingParentModifier(width: width, height: height))
    }
}

struct MatchingParentModifier: ViewModifier {
    @State private var intrinsicSize: CGSize = UIScreen.main.bounds.size
    private let intrinsicWidth:  Bool
    private let intrinsicHeight:  Bool

    init(width: Bool, height: Bool) {
        intrinsicWidth = !width
        intrinsicHeight = !height
    }

    func body(content: Content) -> some View {
        GeometryReader { _ in
            content.modifier(intrinsicSizeModifier(intrinsicSize: $intrinsicSize))
        }
        .frame(
            maxWidth: intrinsicWidth ? intrinsicSize.width : nil,
            maxHeight: intrinsicHeight ? intrinsicSize.height : nil
        )
    }
}

struct intrinsicSizeModifier: ViewModifier {
    @Binding var intrinsicSize: CGSize

    func body(content: Content) -> some View {
        content.readIntrinsicContentSize(to: $intrinsicSize)
    }
}

struct IntrinsicContentSizePreferenceKey: PreferenceKey {
    static let defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

extension View {
    func readIntrinsicContentSize(to size: Binding<CGSize>) -> some View {
        background(
            GeometryReader {
                Color.clear.preference(
                    key: IntrinsicContentSizePreferenceKey.self,
                    value: $0.size
                )
            }
        )
        .onPreferenceChange(IntrinsicContentSizePreferenceKey.self) {
            size.wrappedValue = $0
        }
    }
}
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
1

Here another way which would save time in your projects:

Much less code and reusable in compare to other answers which they are not reusable!


extension View {
    
    var maxedOut: some View {
        
        return Color.clear
            .overlay(self, alignment: .center)

    }
    
    func maxedOut(color: Color = Color.clear, alignment: Alignment = Alignment.center) -> some View {
        
        return color
            .overlay(self, alignment: alignment)
        
    }
    
}

use case:

struct ContentView: View {

    var body: some View {

        Text("Hello, World!")
            .maxedOut
            .background(Color.blue)
        
        Text("Hello, World!")
            .maxedOut(color: Color.red)
  
    }
}

enter image description here

ios coder
  • 1
  • 4
  • 31
  • 91
-1

Just add Color.clear to the bottom of the VStack, simple as that :)

struct ContentView: View {
   var body: some View {
     VStack(alignment: .leading) {
       Text("Title")

       Color.clear
     }
     .background(Color.red)
   }
 }
fhucho
  • 34,062
  • 40
  • 136
  • 186