How do I avoid app zoom when keyboard opens? - swift

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.

Related

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

SwiftUI truncate content of ForEach

In the following code snippet, the red circles obviously don't fit onto the screen and therefore only the trailing part of the HStack is shown. Instead of that behavior, I want the leading text of the HStack to always be visible and truncate the trailing red circles that don't fit into the available space (replaced with ...).
How can I do this?
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.layoutPriority(1)
Spacer()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
}
}
So I want this:
instead of this:
Can get everything except the ellipses with:
struct ContentView: View {
var body: some View {
HStack {
Text("This text should always be visible")
.fixedSize()
ForEach(0..<20) { index in
Image(systemName: "circle.fill")
.font(.body)
.foregroundColor(.red)
}
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
}
In order to get the ellipses you'll probably have to calculate frame sizes or try to get your images into an attributed string which could get pretty complicated. You might also consider a scroll view or grid.

Clipped and Scaled To Fill SwiftUI Image Blocks NavigationLink

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

Text goes off screen after using .aspectRatio(contentMode: .fill)

I am trying to make an app with a background image using SwiftUI. However, the image is not the same aspect ratio as the screen, making me use .aspectRatio(contentMode: .fill) to fill the entire screen with it. This works completely fine until I start adding text. When adding text, it now goes off the screen instead of wrapping like it normally should do.
Here's my code:
struct FeaturesView: View
{
var body: some View
{
ZStack
{
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
}
}
And this is the preview:
As you can see, the text goes off the screen. I have tried using ´.frame()´ and specifying a width and height to fix it, but that causes issues when using the view inside other views. I am using the Xcode 12 beta.
I'm new to Swift and SwiftUI, so all help is appreciated :)
Of course it goes, because image expands frame of container, ie ZStack, wider than screen width.
Here is a solution - make image to live own life in real background and do not affect others. Tested with Xcode 12 / iOS 14.
var body: some View
{
ZStack
{
// ... other content
VStack(alignment: .leading)
{
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}
.foregroundColor(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
)
}
this happen because your ZStack using width that equal to your image, try to wrap your image using VStack and geometryReader .
GeometryReader { geometry in
ZStack {
Image() // your image
VStack(alignment: .leading) {
Text("Hello this is some sample text that i am writing to show that this text goes off the screen.")
}.foregroundColor(.white)
}.frame(maxWidth: geometry.size.width)
}
I'm write this on my windows it might some spelling error

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: