This is my first question ever on StackOverflow so please bear with me!
How do I get a fullscreen ScrollView with LazyHStack while ignoring safe edges with the each image taking up 100% of the device screen?
I was able to get something like this that worked using TabView but since there is no "LazyTabView" it was not an efficient way to do things.. I have also tried multiple other methods utilizing Geometry Reader and frame but to no avail. Also note that I am strictly working in landscape orientation so how it looks in portrait does not matter to me.
Here is the code I'm currently using:
import SwiftUI
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack() {
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
Image("test")
.resizable()
.scaledToFill()
}
}
.ignoresSafeArea()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have also attached an image to show the results I am getting. I am hoping I can have the image take up 100% of the screen real estate without showing the edge of the next image. As you can see the image does not fill the entire screen and instead shows the edge of the next photo. I hope this makes sense!
screenshot of code with preview
This code worked for me:
struct ContentView: View {
var body: some View {
GeometryReader { g in
ScrollView(.horizontal, showsIndicators: false) {
HStack() {
Image("test")
.resizable()
.ignoresSafeArea()
.frame(width: g.size.width + CGFloat(g.safeAreaInsets.leading) + CGFloat(g.safeAreaInsets.trailing), height: g.size.height + CGFloat(g.safeAreaInsets.bottom))
.offset(x: -CGFloat(g.safeAreaInsets.leading))
}
}
}
}
}
It will make the image take up the whole screen, ignoring the safe area. You may have to take the code out of the LazyHStack and into a HStack instead, however. Unless you have a large number of images, you do not need to use LazyHStack.
Related
When I click my SwiftUI text field and the keyboard opens, the app zooms out (shown in video).
I have two questions about this behaviour:
Why does this happen?
How do I avoid this happening?
Here is my code:
struct BestillView: View { // This view is put inside a tab view with .ignoresSafeArea
#State var navn = ""
#State var varsling = true
var body: some View {
NavigationView {
ZStack {
Color("BackgroundColor")
.ignoresSafeArea()
VStack {
Image("Liquid") // This is my image overlayed on the background, i suspect this may be the only element that actually gets zoomed out
.resizable()
.aspectRatio(contentMode: .fit)
.ignoresSafeArea()
Spacer()
}
VStack {
ZStack(alignment: .leading) { // This is where the text field i'm having trouble with is
Color("UnselectedColor")
.frame(height: 50)
.cornerRadius(20.0)
if navn.isEmpty { // I have a separate text element as the placeholder text so i can give it a custom color
Text("Navn")
.foregroundColor(Color("AccentColor"))
.padding()
}
TextField("", text: $navn)
.padding()
}
.frame(width: 300)
Spacer()
.frame(height: 20.0)
// I removed the rest of my code, I don't think it should be necessary in this question - it's only a NavigationLink and a Toggle
}
}
}
}
}
You have .ignoresSafeArea() on your Image, but you actually need it on the VStack that contains the Image. The VStack is shrinking to fit the keyboard’s safe area, which squeezes the image too.
The view is actually not shrinking; the image is shrinking - because as the view moves up, it has less height to fit.
You can update your code as:
Image("Liquid")
.aspectRatio(contentMode: .fill)
and it will keep the size same - as the width will remain same.
When there is a NavigationLink in a container with an Image (that is resizable, scaled to fill, and clipped to a smaller frame), the NavigationLink cannot be pressed. I'm assuming that this has to do with the parts of the Image that have been "clipped off" still actually present and blocking the NavigationLink.
Here is a short example to replicate the behavior:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
VStack {
NavigationLink(destination: Text("Hello, world!")) {
Text("Press me")
}
}
Image("background")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
}
}
}
}
"background" can be any sort of picture from the assets folder.
I have tried to mess around with the zIndexes; that didn't work.
There was one hack that worked: I used a UIImage, cropping it to the aspect ratio of Image I wanted by converting it to a CGImage and back into a UIImage. After doing that, I could press on the NavigationLink again but it was obvious from my phone lagging that it was too expensive. I tried to work around this by saving the cropped image to the documents directory and then whenever the aspect ratio wasn't similar enough I would recrop, save, and reload the image, but this still took a toll on the performance of my project.
Please offer some advice on how I should handle this situation. Thanks in advance for any help.
Here is alternate to zIndex (if other active elements are present in view as well) - disable user interaction with background image
Image("background")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
.allowsHitTesting(false) // << here !!
//.zIndex(-1) // << also force put below siblings
Set .zIndex(1.0) to VStack of NavigationLink.
Tested : XCode 12.2, iOS 14.1
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
VStack {
NavigationLink(destination: Text("Hello, world!")) {
Text("Press me")
}
}
.zIndex(1.0) //<--- here
Image("ivana-cajina-_7LbC5J-jw4-unsplash")
.resizable()
.scaledToFill()
.frame(height: 60)
.clipped()
}
}
}
}
Here is another alternative.
public var body: some View {
ZStack {
image
.resizable()
.scaledToFill()
// > etc..
.allowsHitTesting(false)
}
.clipped()
}
I have a Zstack like this:
ZStack {
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
VStack {
// with a lot of stuff
}
}
I want the image to ignore the safe area, but the Vstack must respect the safe area.
The image is a background image that should cover all the area.
This code I have is making the VStack full screen too and I don't want that.
Why everything is not respecting the safe area is a mystery, because the respective modifier is applied to the image only.
Put your image in the .background:
struct ContentView: View {
var body: some View {
VStack {
// lots of stuff
Color.red
Color.blue
}
.background(
Image("beach")
.resizable()
.edgesIgnoringSafeArea(.all)
.scaledToFill()
)
}
}
The reason why it doesn't work with your example is that you are using scaledToFill() modifier on image, and here it matters in this particular case.
First you declared ZStack which is itself doesn't ignore safe area. After you put Image and resizable. What resizable does, is it stretches the image to fit its view, in your case it is the ZStack.
Let's see what we have until now.We have an image which stretches till the safe area.
ZStack {
Image("Breakfast").resizable()
}
So from now on you put edgesIgnoringSafeArea on Image, which lets the image cover all the area(ignore safe area).
Now you have Zstack which respects safe area, and Image which ignores safe area. This let you put VStack in ZStack and add staff inside it. VStack will fill its parent view, which is ZStack, so it too will respect safe area(See code and image below).
ZStack {
Image("Breakfast").resizable().edgesIgnoringSafeArea(.all)
VStack {
Text("hello")
Spacer()
}
}
And here at last you add .scaledToFill() modifier, which stretches the image to contain all the area, and by doing this it makes ZStack view to become the hole area, as fitting view's (ZStack, HStack, VStack) calculates its size based on its content.
Useful link:
Fitting and filling views in SwiftUI
How to resize a SwiftUI Image and keep its aspect ratio
Another way that worked for me is:
struct ContentView: View {
var body: some View {
ZStack {
Color.clear
.background(
Image("beach")
.resizable()
.ignoresSafeArea()
.scaledToFill()
)
VStack {
// lots of stuff
}
}
}
}
Note: In my case, when trying the other solution of putting .background on VStack the image did not fill the entire screen, instead it shrank to fit the size of what was in the VStack.
What I'm trying to achieve
I'm trying to create a SwiftUI view where an image should expand the entire screen (edgesIgnoringSafeArea(.all)), and then overlay a view on top of that, that also fills the entire screen, but respects the safe area.
What I've tried
This is my code, which comes close:
struct Overlay: View {
var body: some View {
VStack {
HStack {
EmptyView()
Spacer()
Text("My top/right aligned view.")
.padding()
.background(Color.red)
}
Spacer()
HStack {
Text("My bottom view")
.padding()
.background(Color.pink)
}
}
}
}
struct Overlay_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Image(uiImage: UIImage(named: "background")!)
.resizable()
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fill)
Overlay()
}
}
}
The issue and tested solutions
The issue is that the image is not clipped it looks like, so it expands the parent view to a width larger than the screen width, which then makes the top right aligned red text box float off screen (see image).
I tried using .clipped() in various places, with no luck. I would preferably avoid using GeometryReader if possible.
Q: How can I make the image view only fill the screen?
You have to limit the frame size of the out-of-bounds Image before it is being picked up by the ZStack to avoid the ZStack to grow and so the Overlay to go out of position.
edit: aheze shows with his answer a way around using GeometryReader by putting the Image into the background of Overlay() with .background(Image()..). This avoids the usage of ZStack and GeometryReader completely and is possibly a cleaner solution.
Based on parent view size
struct IgnoringEdgeInsetsView2: View {
var body: some View {
ZStack {
GeometryReader { geometry in
Image("smile")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: geometry.size.width,
maxHeight: geometry.size.height)
}
Overlay()
}
}
}
Based on screen size
struct IgnoringEdgeInsetsView: View {
var body: some View {
ZStack {
Image("smile-photo")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
Overlay()
}
}
}
No need to mess with GeometryReader. Instead, you can prevent the image from overflowing by using the .background() modifier.
struct ContentView: View {
var body: some View {
Overlay()
.background( /// here!
Image("City")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
)
}
}
Result:
I added an image to my body in a SwiftUI application and want to have that image cover the full width of the device, but not go over it.
In body, I return the image object:
var body: some View {
Image("page-under-construction")
}
and the image shows up, however, it's too big:
I tried setting the frame: that affects the highlighted boundaries, but the image does not resize.
In combination, I played around with .aspectRatio(contentMode:), which did not seem to have any effect on the layout.
How can I have the image be effectively 100% of the screen width?
The reason .aspectRatio(contentMode:) had no effect on the layout is because you did not make the image resizable with resizeable().
Doing
var body: some View {
Image("page-under-construction")
.resizable()
.aspectRatio(contentMode: .fill)
}
will cause the image to be the width of the screen, but the image's aspect ratio will not be maintained. To maintain the aspect ratio, do
var body: some View {
Image("page-under-construction")
.resizable()
.aspectRatio(UIImage(named: "page-under-construction")!.size, contentMode: .fill)
}
This utilizes the .aspectRatio(aspectRatio: CGSize, contentMode: ContentMode) version of the method your original question discussed with a dummy UIImage to access the Image's original aspect ratio.
Note: The explicitly unwrapped optional (!) should not be a problem unless you are unsure if the image name is a valid one from your Assets folder. See this post for a comprehensive overview on Swift optionals.
Here is how you add a background image and make it fill the full screen in SwiftUI
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.top)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Did you try resizable modifier?
struct ImageView: View {
var body: some View {
Image("turtlerock")
.resizable()
.aspectRatio(contentMode: .fit)
}}
Note - There are 2 content modes: .fit and .fill