Clipped and Scaled To Fill SwiftUI Image Blocks NavigationLink - swift

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

Related

Fullscreen LazyHStack for Images (Landscape)

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.

SwiftUI Image with high height layout

I'm trying to get a basic thing, but I can't make it work!
I would like to have a VStack that contains a text an image and a second text.
Everything should be visible on the screen. So the Image should resize (crop top and bottom) to give spaces for the two texts... but not.
Without .scaledToFill() it's working well, but the image is stretched
The problem (I think) is because the image has a high height!
(I tried with GeometryReader, fixedSize, layoutPriority, but nothing that I tried works)
The image is from Wikipedia: Image link
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}.background(Color.blue)
}
}
Simulator screen
What is needed:
Image of what is needed
Thank you :)
Ok, I found a way
Color.clear
.background(Image("image")
.resizable()
.scaledToFill())
.clipped()
Embedding the image as background of a clear Color, the image is limited to the parent size!
Result: https://imgur.com/a/ceZRqSw
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}
.background(Color.blue)
.padding(.top)
}
}
You could also use the maxHeight frame parameter to frame the view if you know how big you'd want it to be. You can also do this on the image itself.
struct TestView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}
.background(Color.blue)
.frame(maxHeight: 500) //500 can be any value of your choice
}
}
You can also use the AsyncImage with placeholder to load the image
struct ContentView: View {
var body: some View {
VStack(spacing: 8) {
Text("Text 1")
AsyncImage(url: URL(string: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ee/Isabella_of_France_by_Froissart.png/1024px-Isabella_of_France_by_Froissart.png")){ image in
image.resizable()
}placeholder: {
ProgressView()
}
.scaledToFill()
.frame(width: 200, height: 400, alignment: .center)
Text("Text 2")
}
.background(Color.blue)
}
}
It seems like you are looking for a way to consider Safe Area. because at the second image all contents respect to safe area while the first image don't. However the code seems to do so by default unless we indicate to ignore safe area by using .ignoresSafeArea(). Look at the view where you called TextView, You might use ZStack with a blue background that affects its children.
If not you should used scaledToFit instead of scaledToFill because as I see both images, the second one is cropped.
Have you tried
struct TestView: View {
var body: some View {
VStack(spacing: 8) { // You can alternatively increase the spacing and remove the padding for the image
Text("Text 1")
Image("image")
.resizable()
.scaledToFill()
.padding()
Text("Text 2")
}.background(Color.blue)
.padding() // Note padding here
}
}

How do I avoid app zoom when keyboard opens?

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.

SwiftUI Nested GeometryReader - Breaking UI

So I am trying to understand why my subview (TopView) is having weird resizing issues.
Here is the sample
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
TopView()
.background(Color.yellow)
}
Rectangle()
.foregroundColor(Color.red)
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
if !isInterfaceHidden {
Rectangle()
.foregroundColor(Color.yellow)
.frame(height: 80)
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
struct TopView: View {var body: some View {
HStack(content: {
VStack(spacing: 0, content: {
Text("Text to show, it is a title.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
GeometryReader(content: { geometry in
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
})
.background(Color.purple)
})
})
.padding([.leading, .trailing], 20)
}
}
I tried to set a .fixedSize() like this:
GeometryReader(content: { geometry in
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
})
.background(Color.purple)
But it is not fitting the text exactly, so I am not sure if this is the right solution. Do you guys have any idea?
Be aware that GeometryReader has had what appears to be a regression as of 14.0 (26/Sep/20) - or perhaps a wonderfully undocumented change of behaviour - with weighting layouts towards the topleft corner - rather than the center.
This has only appeared with apps I developed and built with XCode 12 - an XCode-11-compiled-app running on iOS 14 did not exhibit the issue. Most tutorials on the net will be assuming this worked the way it did in iOS 13/XCode 11 and your code may function differently
iOS 14 has Changed (or broken?) SwiftUI GeometryReader for a more involved question with the same issues
As far as I know, GeometryReader passes back its parent a size that is given by the parent unless you set frame() to GeometryReader explicitly. Even so, If you want to fit the area of GeometryReader to the Text view (exactly your custom view), you will have to calculate a height of the custom view by using preference or anchorPreference and then set it as a height of GeometryReader in order to let the parent know what size it needs to assign.
I hope the following link will be helpful.
https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
GeometryReader fit to View
If you're looking for the GeometryReader to not affect the size of your view, you should make an inversion. The view that you return inside the GeometryReader should be out, and the GeometryReader itself should be put in a background or in a overlay of that View.
Text("Text to show, it is a subline.")
.tracking(0.2)
.foregroundColor(.white)
.lineLimit(1)
.overlay(
GeometryReader(content: { geometry -> Color in
print(geometry.frame(in: .global))
return Color.clear
})
)
.background(Color.purple)
Either way (background or overlay), would solve your problem. Try changing overlay to background to see.
Just remember to return a Color.clear, this way, the GeometryReader becomes invisible and it doesn't change the View.

SwiftUI: Prevent Image() from expanding view rect outside of screen bounds

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: