SwiftUI Image scaledToFill layout behaviour - swift

In my iOS 14 Widget I want to display several circular images in a row.
When using scaledToFit() the image stretches weirdly to fit the circle. scaledToFill() gives me the desired output like so:
Image("Person")
.resizable()
.scaledToFill()
.clipShape(Circle())
But this changes the behaviour of the view to ignore it's parent and expand beyond it. Setting a fixed frame prevents this, but I need these Images to resize dynamically. When I place this View inside an HStack in my Widget the Images are way too large.
How can I get the image to scale like scaledToFill() and still respect the parent view.

You can try using geometry reader. This is an example code of what I have used in my Widget so you have to adjust it for your project, however, you will get the idea.
GeometryReader { geo in
ZStack(alignment: .bottom) {
HStack {
Image("Person")
.resizable()
.frame(width: geo.size.width, height: geo.size.height)
.aspectRatio(contentMode: .fit)
}
}
}

I found a solution in this post, the first answer works great in the Widget:
stackoverflow.com/questions/58290963/clip-image-to-square-in-swiftui
However I am reusing the view within my app and it caused some weird behaviour there. Instead I am now clipping the Image to a Circle before the View is created. This allows me to use .scaledToFit() and still maintain the original aspect ratio of the image.

I found it works best when wrapping the GeometryReader directly into the Image
HStack {
GeometryReader { geo in
Image("Person")
.resizable()
.scaledToFill()
.frame(width: geo.size.width, height: geo.size.height)
}
}

Related

SwiftUI Image from UIImage is still stretched after setting ascpectMode: .fit

I'm struggling to show a SwiftUI Image correctly when creating it from UIImage with the initialiser Image(uiImage: UIImage).
The code I'm using is
private var rightViewImage: Image
public var body: some View {
....
rightViewImage
.resizable()
.renderingMode(.template)
.scaledToFit()
.foregroundColor(.blue)
.frame(width: 20, height: 20)
}
and setting the variable in a method with
if let image = UIImage(systemName: "mic") {
rightViewImage = Image(uiImage: image)
}
I get a stretched version of the image
instead of the correctly scaled one, which is this
It seems like the .scaledToFit() is not working, it renders it like a .fill.
Is this a bug?
UIImage(systemName:) provides a completely different image dimension and resolution from Image(systemName:).
For this case, you should this instead:
Image(systemName: "mic")
.resizable()
.renderingMode(.template)
.scaledToFit()
.foregroundColor(.blue)
.frame(width: 20, height: 20)
This is what rendered by Image():
This is what rendered by UIImage():
The other answer will get the job done.
But what if you can't use an SF Symbol and want to preserve the aspect ratio? In that case, use the .aspectRatio() modifier:
Image("my-custom-image")
.resizable()
.aspectRatio(contentMode: .fit)
That will give an image that is as large as can fit in the parent view without stretching.
It looks like the question might be about some text paired with an image though. The best way to handle that is not to use resizable() at all. SF Symbols have a bunch of variations that match the system font. And Image(systemName:) will give a size and weight based on the current font. So you can do something like this and the image will match the text:
HStack {
Text("Here's a label")
Image(systemName: "mic")
.foregroundColor(.blue)
}
.font(.title.bold())
That's a better bet than resizing by hand because it will match the iOS system design and look 'right.'

GeometryReader's child view overflowing width

I'm currently using GeometryReader and Image to have images in a 3-column grid. In this grid, I want when user taps the image, they will see the bigger version of it.
However, when I implemented this, GeometryReader is not functioning properly. The image within the GeometryReader is overflowing the frame size passed to the GeometryReader.
I have checked that the size of GeometryReader is 1:1 ratio, but when you see the screenshot of view hierarchy, the size of image is different.
Below is the code for my custom Image view.
GeometryReader { geo in
if isZoomable {
Image("Image_gallery_default")
.centerCropped(width: geo.size.width, height: geo.size.height)
.aspectRatio(1.0, contentMode: .fill)
.onTapGesture {
showImage = true
}
} else {
Image("Image_gallery_default")
.centerCropped(width: geo.size.width, height: geo.size.height)
}
}
And centerCropped modifier is just a set of resizable/scaletofill/frame/clipped to make image center cropped.
self
.resizable()
.scaledToFill()
.frame(width: width, height: height)
.clipped()
The image on screen is just fine picture.
But if you tap on the right side of first picture (turlip one), it will instead show the picture on right side.
Why is the Image view overflowing instead of following the width of GeometryReader?
I searched to find out the solution, but nothing similar came up.

SwiftUI content gets cut off on smaller iPhone screens

I'm new to SwiftUI, so I'm following a tutorial to get familiar with it. However, my app's content is getting cut off on smaller screens (both vertically and horizontally). How can I prevent this from happening?
Here's my code:
EDIT: I have added borders around my images and resized the images as suggested in your comments and answers, but as you can see, the images don't appear to be taking up any more space than they're supposed to.
struct ContentView: View {
var body: some View {
ZStack {
Image("background").ignoresSafeArea(.all)
VStack {
Spacer()
Image("logo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.border(Color.black)
Spacer()
HStack {
Spacer()
Image("card3").border(Color.black, width: 3)
Spacer()
Image("card4").border(Color.black, width: 3)
Spacer()
}
Spacer()
Image("dealbutton")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
.border(Color.black)
Spacer()
HStack {
Spacer()
VStack {
Text("Player").padding(.bottom, 10)
Text("0").font(.largeTitle)
}
Spacer()
VStack {
Text("CPU").padding(.bottom, 10)
Text("0").font(.largeTitle)
}
Spacer()
}
.foregroundColor(.white)
Spacer()
}
}
}
}
And here's what the preview looks like on an iPod touch:
Try making Image("background") resizable or set it as the .background(:) of your ZStack. Currently the background image isn’t resizable and is larger than the screen, so it shows at its native size and stretches its parent ZStack beyond the bounds of the screen. Since your content is in that same ZStack, it also extends beyond the bounds of the screen
Your issue is related to your images that you have present on the view structure itself. Images are rendered at 100% their size, irrespective of their constraints. This will cause other views to be pushed away. The solution for that is to set a set size on the view itself that matches within the confines of your available space. Also you're resizing your ZStack which also resizes the content inside of the ZStack. For example.
Image("logo")
.resizeable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
Handling your image like this will ensure that it is set to the appropriate size when it is rendered. Then you can have the remaining views fall into place in a way that's expected. If you need it scaled on a % for the screen size you can use a GeometryReader to scale the view for different screen sizes.
GeometryReader { reader in
Image("logo")
.resizeable()
.aspectRatio(contentMode: .fit)
.frame(width: reader.size.width * 0.2)
}
Finally, remove your ZStack and set it up like this.
VStack {
//Your Content
}.background(
//Make sure to set the edgesIgnoring.. on this NOT the VStack
Image("background").edgesIgnoringSafeArea(.all)
)
Tip When Working with Stacks
Stacks, wether VStack, HStack, or ZStack's all ALWAYS have their frame set to the content that is held inside of them. If you had an object with a width of 100 and a height of 10,000 then the Stack would also have those dimensions, unless otherwise specified with a Modifier such as .frame(width...) or even, in your case, an Image that is resized.
Suppose your same stack then has a width: 10, height: 10 view added to it, it would still retain the same size as the largest content held within. This is of course handled differently with HStack and VStacks as they actually stack things in a 2D plane, whereas the ZStack works on the 3D plane.

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: Increase tap/drag area for user interaction

I've built a custom slider in SwiftUI with a thumb dragger that is 20x20. Everything is working, except the tap target is quite small since it's just the 20x20 view. I'm looking for a way to increase this to make it easier for users to interact with my slider. Is there a way to do this in SwiftUI?
I tried wrapping my thumb in Color.clear.overlay and setting the frame to 60x60. This works if the color is solid, like red, but with clear the tap target seems to revert back to visible pixels of 20x20.
You can see on this gif I'm able to drag the slider even when clicking outside of the thumb.
However, as soon as I change the color to clear, this area no longer receives interactions.
Add a .contentShape(Rectangle()) after the frame.
I'm sure there are several ways to accomplish this, but this is how i made a big button with the whole area tappable:
Button(action: { someAction() }, label: {
Text("OK")
.frame(minWidth: 200)
.contentShape(Rectangle())
.padding()
})
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(5.0)
I had the same problem. Except I didn't want the expanded overlay to impact the rest of the layout. If you embed everything in a ZStack and put a rectangle before your interactive object, you can control the size of the gesture. Kind of like this:
Rectangle().frame(width: screen.width, height: 300).opacity(0.001)
.layoutPriority(-1)
Just needed to make sure to set the opacity to next to nothing so you can't see it, and the layout priority to -1 so it doesn't impact the view.
As a follow up to the answer above, You can enlarge the clickable rect all day using this as example.
ZStack {
Image(systemName: secured ? "eye.slash" : "eye")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
Rectangle()
.frame(width: 22, height: 20)
.opacity(0.001)
.onTapGesture {
self.secured.toggle()
}
}