Positioning inside ZStack - swiftui

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)
}
}
}

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)

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)
}
}
}

Related

Why does the blur modifier affect views differently?

I spent a lot of time trying to figure out whether this was a bug, but I couldn't for the life of me figure out why a shape wasn't blurring correctly.
VStack {
//Doesn't blur right
Capsule()
.fill(LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing))
.frame(width: 80, height: 40)
.blur(radius: 10)
//Does blur correctly
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
}
When you apply blur to an empty HStack, it blurs correctly, while using a blur to a shape doesn't. Can someone explain why it differs?
Firstly, two of them looks like the same but it's not the same.
From Apple docs, Capsule() is subclass of Shape which is subclass of View.
The first one, when you using Capsule() directly then you .fill means just like you add subview LinearGradient with colors into Capsule(). That's the reason I think .blur not working correctly when you call directly from Capsule().
The second one, you .blur correctly because the view was directly be drawn on view ( just like drawing in layer of view in normal UIKit)
The view hierarchy show the different between two of them.
The solution:
You can do like your second view
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
Or you can make linear gradient with mask
HStack {
LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Capsule()
.blur(radius: 10)
.frame(width: 80, height: 40)
)
}.frame(width: 200, height: 60)
Both will act the same like you draw on the layer of view
UPDATE
If you need it works directly, just use only one color then call .foregroundColor then blur will continue work - But only one color at the time only.
Capsule()
.foregroundColor(Color.red)
.blur(radius: 10)
.frame(width: 80, height: 40)

How can I edit alignment and spacing in SwiftUI for images and text?

I would like the Image "Astronaut Meditation (Traced)" and the text "Levitate" to be aligned as in the prototype image, which is in the dimensions of an iPhone 13 pro max. How I want it to look
Here is my ContentView code:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack
{
//Background
Image("Background")
.edgesIgnoringSafeArea([.top])
**//Meditating Astronaut
Image("Astronaut Meditaton (Traced)")
.position(x: 102, y: 106)
//Levitate
Text("Levitate")
.font(.system(size: 34, weight: .semibold, design: .rounded))
.foregroundColor(Color.white)
.position(x: 110, y: 386)**
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have included an image of how it is previewed on Xcode:
How it looks in XCode
Do not use static value alignment like position or offset because it’s not stable for all devices, instead just wrap your content inside VStack{} then you will achieve your desired design.
ZStack {
//Background
Image("Background")
.edgesIgnoringSafeArea([.top])
VStack { //start wrapping after background color image
//Meditating Astronaut
Image("Astronaut Meditaton (Traced)")
//Levitate
Text("Levitate")
.font(.system(size: 34, weight: .semibold, design: .rounded))
.foregroundColor(Color.white)
}
}
Like #tail said, you should avoid absolute position & offset, since it will vary from device to device. But if you do want to use them for some reason, you can add a alignment parameter to the ZStack and then use offset to get the desired outcome.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack(alignment: .center) {
//Background
Image("Background")
.ignoresSafeArea(edges: [.top])
//Meditating Astronaut
Image(systemName: "gear")
.resizable()
.frame(width: 150, height: 150)
.offset(y: -150)
//Levitate
Text("Levitate")
.font(.system(size: 34, weight: .semibold, design: .rounded))
.offset(y: -50)
}
}
}
Even better way would be to use GeometryReader and work with relative values to the screen size:
import SwiftUI
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .center) {
//Background
Image("Background")
.ignoresSafeArea(edges: [.top])
//Meditating Astronaut
Image(systemName: "gear")
.resizable()
.frame(width: 150, height: 150)
.position(x: geometry.size.width / 2, y: geometry.size.height / 5)
//Levitate
Text("Levitate")
.font(.system(size: 34, weight: .semibold, design: .rounded))
// Position with offsetting by the size of image.
// Image frame 150 -> half is 75 + 25 for padding.
.position(x: geometry.size.width / 2, y: geometry.size.height / 5 + 100)
}
.frame(width: geometry.size.width, height: geometry.size.height)
}
}
}
With GeometryReader I would use absolute position and calculate the position of image with the relative values and then use the image size in addition to position the text
Use below code to achieve your actual desire design. Wrap your content in VStack and also add frame to background image for bind in your whole device with device width and height.
And also add Spacer() in VStack to occupy remains bottom space(For move view to up side).
ZStack {
//Background
Image("Background")
.resizable()
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) //Here add frame to background image
.edgesIgnoringSafeArea(.all)
VStack { //Wrap your view in VStack
//Meditating Astronaut
Image("Astronaut Meditaton (Traced)")
.resizable()
.frame(width: 100, height: 100)
.padding(.top,20)
//Levitate
Text("Levitate")
.font(.system(size: 34, weight: .semibold, design: .rounded))
.foregroundColor(Color.white)
Spacer() //Add Spacer to move view to upper side.
}.padding(.top,UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0 + 150) //Add padding with safe area to top side for upper space(suitable for all devices).
}

Getting rid of SwiftUI ScrollView top padding

I just can't seem to get rid of the ~6 height padding(?) at the top of ScrollView. I've tried setting padding to 0 on both the ScrollView itself and on it's content. I've tried using SwiftUI-Introspect to get at the content insets, but they appear to already be 0 for the top. I could just use offset(y: -6), but I'd like to understand what is causing the padding. Does anyone know?
var body: some View {
VStack(spacing: 0) {
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.blue)
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.red)
ScrollView {
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.black)
}
}
}

Aligning image to navigationBarTitle in SwiftUI

I'm trying to add a User Image (button) next to the .navigationBarTitle, but with the code bellow, the image appears on top of the title alignment. (picture attached). Many thanks for your help!
.navigationBarTitle(Text("Watch"))
.navigationBarItems(trailing:
Image("User")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 36, height: 36)
.clipShape(Circle())
)
Image should be bottom - aligned to the text
This code produces this view:
struct ContentView: View {
var body: some View {
NavigationView {
List {
Text("Chocolate")
Text("Vanilla")
Text("Strawberry")
}.navigationBarTitle(Text("Watch"))
.navigationBarItems(trailing:
Image(systemName: "person.circle")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 36, height: 36)
.clipShape(Circle())
.padding(.top, 90)
)
}
}
}
There is a default space for the Items and a default space for the Text. Imagine it like two HStacks in a VStack. Where the Title is in the lower HStack and the items are in the upper one. There is no "real" way on getting in the lower one.
I'd recommend to create an own NavigationBar for your purposes.
Thanks #Simon, the best option for what I was looking for is to Add the User Icon to the Title (not NavBar) and apply an Offset of y: -55. When scrolling up the icon disappears under the NavBar. The same effect on the Apple TV app (mobile).` VStack(alignment: .leading) {
HStack {
Text("Children")
.font(.title)
.fontWeight(.bold)
.padding(.leading, 24)
.padding(.top, 20)
Spacer ()
Image("User")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 36, height: 36)
.clipShape(Circle())
.offset(y: -55)
.padding(.trailing, 24)[final result][1]`

What is the purpose of .compositingGroup() in SwiftUI?

I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}
This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle
Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.
It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.