SwiftUI - corner radius changes the height of an image - ios

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

Related

`maxHeight` behaves exactly as `height` frame modifier

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

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!

SwiftUI: How to know correct order of modifiers?

I'm pretty new to SwiftUI, learning it for the first time, and couldn’t understand why the below snippet doesn’t work. Ideally, the VStack should stretch in all directions and the Image should have a width of 200px without losing its aspect ratio.
Code
struct ContentView: View {
var body: some View {
VStack() {
Image("Image Name")
.resizable()
.frame(width: 200)
.aspectRatio(contentMode: .fit)
}
.background(Color.red)
.frame(maxWidth: .infinity,maxHeight: .infinity)
}
}
After I accidentally reordered the modifiers, it worked. So, how am I supposed to know the correct order of modifiers without a hit and trial method each time?
// new VStack modifier order
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
// new Image modifier order
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 200)
The best way to think about it for now is to imagine that SwiftUI renders your view after every single modifier. So, as soon as you say .background(Color.red) it colors the background in red, regardless of what frame you give it. If you then later expand the frame, it won’t magically redraw the background – that was already applied.
Of course, this isn’t actually how SwiftUI works, because if it did it would be a performance nightmare, but it’s a neat mental shortcut to use while you’re learning.
Please refer to this link for more details https://www.hackingwithswift.com/books/ios-swiftui/why-modifier-order-matters#:~:text=Every%20time%20we%20modify%20a,up%3A%20ModifiedContent%3CModifiedContent%3C%E2%80%A6

How do I keep clipped content of one subview from blocking gestures in underlying subviews with SwiftUI?

I have a main view consisting of a zStack with a background image at the bottom, a user-loaded image above that, and two toolbars at the top. The toolbars are at the vertical top and bottom of the view. I want those toolbars to appear semi-transparent with background images matching the main view's background image in size and position. If the user drags or scales their image to overlap with the toolbars, it should be obscured by them. To accomplish that, I've built those toolbar views as zStacks with the same background image aligned to the top or bottom, respectively, and clipped the content to match the height of the toolbars.
My problem is that the clipped content of those toolbar views blocks gestures on the underlying, user-loaded image. To help visualize the problem, I've linked a screenshot of the debug view hierarchy for the main view (MockScoringView).
Here's the code for the main view:
GeometryReader { geometry in
ZStack {
Rectangle()
.foregroundColor(.blue)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.baseOffset.width + self.newOffset.width, y: self.baseOffset.height + self.newOffset.height)
.scaleEffect(self.scale)
.gesture(DragGesture()
.onChanged { value in
self.newOffset.width = value.translation.width / self.scale
self.newOffset.height = value.translation.height / self.scale
}
.onEnded { value in
self.baseOffset.width += self.newOffset.width
self.baseOffset.height += self.newOffset.height
self.newOffset = CGSize.zero
}
)
VStack(spacing: 0) {
MockTopToolbarView()
Spacer()
MockBottomToolbarView()
}
}
}
And the code for the top toolbar view, which is very similar to the one on bottom:
ZStack {
Image("Background")
.resizable()
.scaledToFill()
.frame(height: 56, alignment: .top)
.clipped()
Rectangle()
.foregroundColor(.toolbarGray)
.frame(height: 56)
}
I'd like to modify the content of the toolbar views' backgrounds so they do not block gestures from reaching the user-loaded image.
Debug View Hierarchy
You could set allowsHitTesting(false) on the background images.
Alternatively, since you do not want the user image to be visible behind the toolbars you could also just add the Rectangle and toolbars to a single VStack.
ZStack {
Image("Background")
.resizable()
.scaledToFill()
VStack {
TopToolBar()
Rectangle()
.foregroundColor(.blue)
...
BottomToolBar()
}
}

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