SwiftUI - How to align bottom edge to center of sibling - ios

Please have a look at this image before reading the question
I'm currently trying to align the red view's vertical center (/centerY) to the bottom edge of the green view in a SwiftUI View.
I'm coming from UIKit where I would solve this with something like viewA.centerYAnchor.constraint(toEqual: viewB.bottomAnchor)
But how would you solve this the SwiftUI way? I have kind of the following hierachy:
VStack {
ZStack {
Image("someImage")
Text("Awesome Title") // <- align center to the Image's bottom edge
.frame(width: 200, height: 130)
}
Spacer()
}

I found the solution:
Set the ZStacks alignment to .bottom. Now the red view will be aligned to the green views bottom edge. Thanks to #Andrew. But this is not enough:
Set the red views .alignmentGuide to the following:
-> .alignmentGuide(.bottom) { d in d[.bottom] / 2 }
Explanation: Now the green view's bottom edge will be aligned to 50% of the red view's height! Awesome!

If you remove the frame from the text and add a bottom alignment to the ZStack, it will give you the desired effect.
VStack {
ZStack (alignment: .bottom) {
Image("someImage")
Text("Awesome Title") // <- align center to the Image's bottom edge
}
Spacer()
}
result:

Related

How to ignore parent view's padding SwiftUI

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

How do I vertically center an icon with a multiline text label's first line of text?

Problem
We want an icon that is centered vertically with the first line of text of an adjacent view, and we want it to be aligned regardless of text length, icon size or font size. In other words, we want the following result:
The desired result is above: the red icon is aligned with the center of the first line of textIn SwiftUI, the default vertical alignment options are:
.top
.center
.bottom
.firstTextBaseline
.lastTextBaseline
.center is what we want for a single line of text, but if we use it for multiline text we get:
And if we use .firstTextBaseline, we are close but it doesn't quite center the image (this is more obvious as the text size changes):
Solution
We need to use a combination of alignment guides and arithmetic. The solution in code is below:
struct ContentView: View {
var body: some View {
HStack(alignment: .firstTextBaseline) {
Rectangle()
.fill(.red)
.frame(width: 16, height: 16)
.alignmentGuide(.firstTextBaseline) { context in
context[VerticalAlignment.center]
}
Text(greeting)
.font(.title3)
.background(.blue)
.alignmentGuide(.firstTextBaseline) { context in
let remainingLine = (context.height - context[.lastTextBaseline])
let lineHeight = context[.firstTextBaseline] + remainingLine
let lineCenter = lineHeight / 2
return lineCenter
}
}
}
}
In the above example, the rectangle center is aligned with the center of the first line:
How it works
The Rectangle is a stand-in for an icon.
The .firstTextBaseline of alignmentGuide is actually only used as a key to match the against the HStack alignment. So it doesn't make a difference except that the HStack, Rectangle and Text all are using the same vertical alignment scheme.
The context[VerticalAlignment.center] of the Rectangle is returned to indicate that the center of the Rectangle will be be aligned with the other alignment guides of its view siblings (thus centering the Rectangle)
We need to do additional math for the Text to find where the center of the first line of text lies. The arithmetic uses the lastTextBaseline and the height of the entire Text to calculate the region below the text baseline. By adding this to the firstTextBaseline and dividing by 2, we determine where the center of the first line of text lies. The result of this calculation is returned as the alignment guide, for alignment with its siblings (in this case, the center of the Rectangle that we returned already)

Create a border that follows the screen edge

I'm trying to create a background view that looks like a blackjack table. I want a green background with brown borders that follow the edge of the screen like the edges of a card table. I've tried to implement the view like this:
var body: some View {
// Green background with a brown wood border
ZStack {
Color.green
.ignoresSafeArea()
}
.overlay(
RoundedRectangle(cornerRadius: 16)
.strokeBorder(.brown, lineWidth: 10)
.ignoresSafeArea()
)
}
The code produces this preview:
However, this produces a border that is cut off on the outline of the phone and the inside of the corners are not rounded so the table looks like it's overflowing the view.
How could I refactor the view to be a green background with a brown border that follows around the edge of the screen?
You can get your desired result by adding RoundedRectangle with cornerRadius on top of Color.brown in ZStack like this.
ZStack {
Color.brown
.ignoresSafeArea()
RoundedRectangle(cornerRadius: 40)
.fill(Color.green)
.padding()
.ignoresSafeArea()
}
Preview

Using the .shadow modifier on a SwiftUI View that sits above a scrollview, a visible shadow line can be seen when other views overlap

On the bottom of my app I have a rectangle that contains a button (both in the foreground). I then have another rectangle below it that's half the height so I can add a shadow modifier on it. Underneath that is a scrollview, which contains a set of views that are generated using ForEach.
The shadow works great when there are no ForEach views underneath it, and the shadow code isn't anything fancy.
ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)) {
Rectangle()
.frame(height: 60)
.foregroundColor(.white)
.shadow(color: Color("LightDarkModeShadow"), radius: 20, x: 0, y: -5)
.opacity(0.33)
Rectangle()
.frame(height: 110)
.foregroundColor(Color("LightDarkModeBackground"))
However, when the scrollview contains entries and they're underneath the shadow, a line is visible overtop the view. It seems like it doesn't blend properly. Is there any way to fix this, or is this just by design?
Thanks to #RajaKishan in the comments for pointing me in the right direction. The scrollview had .padding(.bottom) applied to it. I think it was butted up against the rectangle shadow and created a line. I added a negative padding value to the scrollview:
.padding(.bottom, -10)
The issue is now gone!

How do I pin a view to the edges of a screen?

How do I pin a view (in this case, a label/text) to an edge of a screen with SwiftUI? With Storyboards I would just use AutoLayout but that isn't available with SwiftUI.
You can do something like this
VStack {
HStack {
Text("Label")
Spacer()
}
Spacer()
}
Spacer in VStack will make sure HStack is at the top, Spacer in HStack will make sure Text is all they way to the left. You can also solve this with alignments.
You can wrap your main content in a special container called GeometryReader. Its default size is the same as its parent so if it is the root view it will pin the contents to the screen edges like AutoLayout.
GeometryReader { in geometry
YourContentView().frame(width: geometry.size.width, height: geometry.size.height)
}
In case anyone is here to find a way to pin a view x points from the top, you can do this:
VStack {
Spacer()
.frame(height: 40)
Text("Space me")
Spacer()
}
You'll need both spacers. This can be a bit counter-intuitive if you're coming from Auto Layout but it's actually quite convenient. The spacer at the bottom of your VStack will "push" your VStack views from the default (i.e. centered) y position toward the top until it meets resistance – by default the top edge of the safe area. You can then push the resting point down by x points with the top spacer, giving a similar effect as Auto Layout's top constraint.
Use this modifier on your view
.frame(minWidth: 0, maxWidth: .infinity,
minHeight: 0, maxHeight: .infinity)
SwiftUI uses minimum needed space for any view, to increase it you may use different approaches depending on the situation.
Also it's possible to use relativeSize() modifier, but I didn't get yet when it works

Resources