I have a VStack with multiple child views (the one with blue background). The VStack has horizontal padding. I want to have this padding set for each child, but sometimes I have exception where I want that child to reach edges of the display completely (Two grey lines above "Checkout" button). Is there any way how to allow this to happen? I don't wanna set padding for every single child separately.
You can apply a negative padding on the view that you applied on the VStack, that means if you applied a padding of 16 points to the VStack like this for example .padding(16) for all directions which is the default. then you can apply a .padding(.horizontal,-16) to the lines and they will stretch to the end of the screen
here is a sample code and a screenshot for the behavior you want.
struct VStackPadding: View {
var body: some View {
VStack{
RoundedRectangle(cornerRadius: 4)
.frame(width: .infinity,height: 3)
.padding(.horizontal, -16)
.padding(.bottom,16)
RoundedRectangle(cornerRadius: 4)
.frame(width: .infinity,height: 3)
}.padding(16)
}
}
I'm confused by frame(maxHeight: ...) modifier. I would expect that the resulting view would have dynamic height capped at maxHeight. However, the height of the view is always maxHeight.
In the example below, I wanted the green rectangle to have 30px and the red one should fill the rest space. However, there's additional padding between the rectangles caused by maxHeight modifier.
Is there any other way to achieve what I want?
maxHeight seems useless to me know. It's pretty much the same as using height, isn't it?
Code
var body: some View {
VStack(spacing: 0) {
Color.green
.frame(height: 30)
.frame(maxHeight: 60, alignment: .top)
Color.red
}
.frame(height: 100)
.background(Color.black)
}
Preview:
Every modifier can (in general!) create a new view. So, you create at first green view unlimited, then limit it by 30px (so green filled that space), and then create another view with 60px (and because there is no stopper it filled that available space)... and then everything with red view below(!) considered above views. To get what you expected just remove .frame(maxHeight - it is not needed here.
... but, if you still want (for any reason) to keep it and fulfil expectation, then it is a matter for layout order - give a preference to second view, like
VStack(spacing: 0) {
Color.green
.frame(height: 30)
.frame(maxHeight: 60, alignment: .top)
Color.red
.layoutPriority(1) // << here !!
}
and you get
If the .cornerRadius modifier comes after the .frame modifier, the image becomes much slower. What is the reason behind this ?
struct ContentView: View {
var body: some View {
VStack(spacing: 100) {
Image("image1")
.resizable()
.scaledToFill()
.frame(width: 343, height: 184)
.cornerRadius(8)
Image("image1")
.resizable()
.scaledToFill()
.cornerRadius(8)
.frame(width: 343, height: 184)
}
}
}
In SwiftUI the order of modifiers is important and can lead to a different output.
A rule of thumb is:
read them from bottom to top
understand that every modifier produce a new view
the modifier "modifies" only what's below:
-> .frame(width: 343, height: 184)
-> .cornerRadius(8)
-> .scaledToFill()
-> .resizable()
Since you used the cornerRadius the documentation states that:
Clips this view to its bounding frame, with the specified corner
radius.
Behind the scene, you can imagine the engine applying two modifiers, the clipped and the corner radius.
Because every modifier produces a new view when the clipping is calculated it's like it doesn't know what are the bounds in which constrain the new view and that explains why the image goes out.
If we go a step further where we try to apply two frame and cornerRadius hopefully should be clearer how SwiftUI interprets the modifiers:
If you want to know more, this discussion goes deeper into the details of how SwiftUI sees the modifier: Order of modifiers in SwiftUI view impacts view appearance
I'm trying to create a widget that only allow VStack instead of List. I'm struggling with aligning the VStack to the top and letting the bottom truncate. It centres vertically and truncates both sides:
How can I make the VStack to the top so it starts from 1 and let it truncate from the bottom?
I found how to do this:
GeometryReader { geometry in
VStack {...}
.frame(height: geometry.size.height, alignment: .top)
}
I can't figure out how to align Image view on top of ZStack, by default views in SwiftUI are placed at the center of their parent, and we then use stacks to align them, I have the following piece of code:
struct ContentView: View {
var body: some View {
ZStack {
Image("bgImage")
Text("Hello, World!")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red) //this is for debugging purposes, to show the area of the ZStack
}
}
How can I position the image to the top ?
To tell the ZStack to align things a particular way within it, configure it with the alignment parameter:
ZStack(alignment: .top) {
Color.clear
Image(...)
Text("Hello, World!")
}
(Color.clear expands to fill all available space, so this forces your ZStack to be as large as the enclosing view without needing to add a .frame().)
That will align everything at the top of course, which might not be what you want. You could fix that by making a nesting your ZStacks to align as you want them to:
ZStack{
ZStack(alignment: .top) {
Color.clear
Image(...) // This will be at the top
}
Text("Hello, World!") // This will be centered
}
That said, I'd probably use a .background for this example.
ZStack {
Color.clear
Text("Hello, World!")
}
.background(Image(...), alignment: .top)
And if you only have one view, you can get rid of the ZStack and use a frame instead:
Text("Hello, World!")
.frame(maxHeight: .infinity)
.background(Image(uiImage:#imageLiteral(resourceName: "image.jpg")),
alignment: .top)
Keep in mind that in this case the image will draw outside its frame. In many cases that's fine (and it's completely legal), but it can matter sometimes (for example, if you put this inside a stack). You can add .border(Color.green) to the end to see how that works.
This example really gets to the heart of SwiftUI layout, so it's worth understanding what's going on. This isn't a workaround or a trick, so you should get to the place where this feels very normal.
The top-level content view (the one that contains the ZStack) offers its entire space to the ZStack. A ZStack is always exactly the size that contains its contents, so first the ZStack needs to layout its children. It lays them out according to its alignment, and then sizes itself exactly to fit around them. So with top-alignment (but without Color.clear), the Image is at the top of the ZStack. The ZStack is just exactly the same size as the Image.
The top-level content view then places the ZStack in its center.
The way the ZStack lays out its children is similar to how the content view did. It offers all the space it was offered, and then the child-views decide their sizes. Views always decide their own sizes. The Image and Text are fixed-sized views, so they are just the size of their contents. But Color is a flexible-sized view. It accepts the entire space that the ZStack offered (which is the same space that the top-level content view offered) and returns that as its size. Since a ZStack must exactly contain its children, the ZStack is now the size of the top-level content view, and things behave as you expect, aligning at the top of the screen.
Let's compare to using .frame() as you originally did:
ZStack {
Image("bgImage")
Text("Hello, World!")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red) //this is for debugging purposes, to show the area of the ZStack
First, I want to focus on your comment, because it's not correct. This is not the area of the ZStack. The ZStack is exactly the size of its contents (the Image and the Text). You put the background on the frame view, and the frame view is larger.
A key confusion people have is that they think .frame(...) changes the size of the View it's attached to. That's not correct at all. As before, a ZStack is always the size of its contents. .frame() creates a completely new view of the requested size. It then positions the wrapped view inside itself according to the frame's alignment. So in this example it works like this:
Top-level - Background - Frame - ZStack - { Image Text }
The top-level view offers all its space to the Background. Backgrounds are the size of what they contain, so it offers all of that space to the Frame. The Frame is flexible in both directions (due to the max fields), and so it ignores its child's size and chooses to be the size it was offered.
The Frame then offers all that space to the ZStack. The ZStack lays out its children, and returns its size as exactly the size that contains them.
The Frame then places the ZStack according to the Frame's alignment (.center, since that's the default). If you'd set the Frame's alignment to .top, then the ZStack would have been placed at the top of the frame (but the text would be centered in the ZStack not in the Frame).
It then reports to the Background that it is as large as the top-level view (since its flexible).
The Background then claims that same size to the top-level content view.
And finally, the top-level content view places the Background in its center.
You could always just put the things you want to be at the top in a VStack and use a Spacer.
ZStack(){
Image(...)
Spacer()
}
The complete code should look something like this:
struct ContentView: View {
var body: some View {
ZStack(){
Text("Hello, World!")
VStack {
Image(...)
Spacer()
}
}
}
}
You could do this with HStacks as well. Important to notice that if the image has no limits to its size, it will always take up as much space as possible. That would remove the purpose of the Spacer. Hope this helps :-)
So one thing working against you is the infinity maxHeight modifier, assuming that you do not want some space between the image and the bottom of the view.
.edgesIgnoringSafeArea(.all)
You may just need to tell your ZStack to ignore safe area insets.
struct ContactsView: View {
var body: some View {
ZStack {
Image("bgImage")
Text("Hello, World!")
}.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.red).edgesIgnoringSafeArea(.all)
}
}
If you need space between the bottom and the image, wrap the ZStack in a VStack and throw a Spacer in the bottom of the VStack.
struct ContactsView: View {
var body: some View {
VStack {
ZStack {
Image(systemName: "bgImage")
Text("Hello, World!")
}.frame(maxWidth: .infinity, maxHeight: 300).background(Color.red)
Spacer()
}.edgesIgnoringSafeArea(.all)
}
}
ZStack {
Image("background")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
}
Add .edgesIgnoringSafeArea(.all)