Remove SwiftUI Form padding - ios

When you use a SwiftUI Form it creates padding on the leading edge, which is fine for Form input but I would like to add additional content to the Form e.g. an image that takes the entire width of the screen, but because the image is in the Form, padding gets applied and the image gets pushed off screen slightly. How can I remove all Form padding?
struct MyForm: View {
var body: some View {
Form {
Image(uiImage: someImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width)
TextField("Name", text: $name)
// Other fields
}
}
}
I know that the underlying view for Form is a UITableView where I can do things like UITableView.appearance().backgroundColor = .clear to change the Form appearance, but I can't figure out how to remove the leading padding.
I also know I can move the Image view outside the Form and put everything in a stack, but that creates other issues with scrolling that I'd like to avoid.

Here is a solution. Tested with Xcode 11.4 / iOS 13.4
Image(uiImage: someImage)
.resizable()
.aspectRatio(contentMode: .fill)
.listRowInsets(EdgeInsets()) // << this one !!
Note: hardcode to UIScreen.main.bounds.width is not needed

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

Image at top of view extending under navigationbar

I am trying to make a "reusable" template for views in my app. As part of this I started prototyping this:
var body: some View {
NavigationView {
ZStack {
VStack {
// Spacer()
Image("progress_bar")
.resizable()
.scaledToFit()
.foregroundColor(Color.gray)
.background(Color.green)
HStack{
}
Spacer()
}
VStack{
}
}
}
.navigationBarHidden(true)
}
}
The ZStack contains 2 VStack. First one is my template and will be part of multiple of my screens later on. Second Stack is destined to be replaced by #ViewBuilder parameter so that I can reuse that in multiple screens easily.
The progress_bar image is a SVG file imported into assets, preserving Vector Data and rendered as template (So I can change colour).
My issue, as shown on the following screenshot, is that the image somehow extends toward the top of the screen. The green area correspond to the green coloured background added to the image. The progress bar is the grey line across the screen.
progress bar extending toward top of the screen
If I change my code to (commented out the spacer):
// Spacer()
Image("progress_bar")
.resizable()
.scaledToFit()
.foregroundColor(Color.gray)
.background(Color.green)
HStack{
}
Spacer()
}
I get this, progress bar shifts down in the screen (not wanted but expected) but the green area that was added on top of the image disappears:
updated screen with progress_bar shifted down and not over extending
I did try setting up a maxHeight to my Image view but this did not work out.
What am I missing? Is there a way I can stop this from happening?
Edit:
After more looking around, my issue is coming from the fact that the whole thing is embedded in a NavigationView. Apparently space is saved for the navigation bar even though it is hidden.

Background Img not staying on Screen Preview (SwiftUI w/ screen shots)

Xcode Version - 13.1
I'm having some issues with a Background Img & the grouping Form{}. What's happening is when placing a background image in a ZStack and include the grouping option, Form{}, the Background Img disappears.
Below my code showing my Background Image inside a ZStack. Also including a link to a screen shot of the Preview -> https://i.stack.imgur.com/u12Sw.png
var body: some View {
ZStack {
Image("login")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
}
}
However, when I add a Form{} inside the ZStack, the background image completely disappears and only the Form{} (w/ a TextField &SecureTextField) appears on the Preview Sans the Background Img. Below is the code w/ and a link to the Screen shot of the Preview -> https://i.stack.imgur.com/BI3U2.png
var body: some View {
ZStack {
Image("login")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
Form {
TextField(
"Username (email)",
text: self.$username)
.autocapitalization(.none)
.disableAutocorrection(true)
SecureTextField(text: $password)
}
}
}
My assumption is that as long as the Form is inside the ZStack, the form should overlay the Background Img.
I like how the Form looks rather than two separate TextFields. Are there anyways to do this w/ the Form or something similar to a Form?
This is what I do at the moment:
I either add this modifier to the form.
.onAppear {
UITableView.appearance().backgroundColor = .clear
}
Or in an init() of the View
this has some side effects on the rest of the view if you have List or Forms but it works for me.

SwiftUI - corner radius changes the height of an image

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

Update SwiftUI View size to match image

I'm trying to make a view which holds an image loaded asynchronously from a network request. Before the image loads, I want to show a placeholder view which has a fixed size. When the image loads, I want to replace this placeholder view with the image, scaled to fit inside the frame of the placeholder view, but then I want the parent view to shrink to match the size of this image. I can't figure out how to do this last part.
Right now, it looks like this:
struct ItemCell: View {
var body: some View {
Group {
CustomImageView(from: imageURL, placeholder: PlaceholderView(), config: { $0.resizable() })
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: 150, minHeight: 0, maxHeight: 190, alignment: .bottomLeading)
}.background(Color.red) // To show that the view isn't resizing properly
}
}
struct PlaceholderView: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 3, style: .continuous)
.frame(width: 150, height: 190)
.foregroundColor(Color(.secondarySystemBackground))
Image(systemName: "globe")
.resizable()
.scaledToFit()
.frame(width: 50)
.foregroundColor(.secondary)
}
}
}
The CustomImageView is adapted from this article on loading images asynchronously. The ItemCells are placed in a horizontal ScrollView. When I test this, it:
correctly displays the placeholder view before the image is loaded;
resizes the image so it maintains its aspect ratio and fits inside the 150x190 frame, but has a weird animation where some of the images shrink and then expand back; also, some of the images seem to shrink too much;
does not resize the parent view to match the size of the image properly, but instead retains the full original height and some (?) extra width on some cells.
These two problems are shown in the gif below, with blue images and a red background. Notice the extra height on the first and third cells, and the extra width on the second. Also, note that the first image ends up smaller than when it first loads, even though it fit inside the original 150x190 frame at first.
How can I fix these problems?
Figured out how to do it. There were several problems with my original code. First, the ItemCells used in the ScrollView should be modified with the .fixedSize() view modifier, like so:
ScrollView(...) {
HStack(...) {
ForEach(...) { ...
ItemCell()
.fixedSize()
}
}
}
Then, changing the frame of the CustomImageCell to be use idealHeight instead of maxHeight and making the Group a VStack with a Spacer() to push everything to the bottom, as #Paulw11 had suggested in comments:
struct ItemCell: View {
var body: some View {
VStack {
Spacer()
CustomImageView(from: imageURL, placeholder: PlaceholderView(), config: { $0.resizable() })
.aspectRatio(contentMode: .fit)
.frame(maxWidth: 150, idealHeight: 190)
}
}
}
These changes fix both the image resizing animation issue and the extra space issue.

Resources