2

At the end of the day, I'm trying to scale to fill an image into a height of X (439px in my case) and then clip it using a RoundedRectangle.

post.picture
    .resizable()
    .aspectRatio(contentMode: .fill)
    .frame(height: 439)
    .clipped()
    .clipShape(Circle())

This view is inside a VStack. If that is necessary to resolve this, I'll post it too, but I don't think it is.

post.picture is an Image property within the Post struct. It looks like this

struct Post: Hashable, Codable {
    var id: Int
    var userId: Int
    var location: String
    var activityId: Int
    var cheerCount: Int
    
    private var pictureLocation: String
    var picture: Image {
        let mainPath = "/hardcoded/path/"
        if let image = UIImage(contentsOfFile: "\(mainPath)/\(pictureLocation)") {
            return Image(uiImage: image)
        } else {
            return Image(systemName: "photo")
        }
    }
}

The code produces this

enter image description here

The issue is that the circle is cut off at the edges. I want the circle mask to be scaled down but the image to retain it's aspect ratio and size.

I am not sure how to do this. I'm using a clipShape(Circle()) to make the issue more clear but I really want to clip the image with RoundedRectangle.

The code works properly when the image height is larger than width, and cuts off the mask shape when the width larger than the height.

It's suppose to look like this

enter image description here

5
  • So basically, the issue is that the image in the first one isn't actually resizing. Is this correct? Commented May 1, 2023 at 19:39
  • 1
    Also, have you tried using .scaledToFit instead of aspectRatio? Commented May 1, 2023 at 19:40
  • I think your issue is the dimensions of your image. I presume they are not standardized, and are probably wider than high. In that case, a clip shape won't work. What you need is to make a cover with a hole in it. See this answer. Commented May 1, 2023 at 20:08
  • @MrDeveloper no, the issue is the mask is scaled with the fit/aspect ratio so it gets cut off. Commented May 2, 2023 at 12:20
  • @Yrb I'm having trouble using that solution on my own. How would your linked solution look in my case? Commented May 2, 2023 at 15:28

3 Answers 3

3

Best avoid using UIScreen.main.bounds.size.width for setting view dimensions due to two reasons:

  1. The use of main will be deprecated in future iOS versions.
  2. It represents a static value that does not adapt to device orientation changes, such as rotating.

In SwiftUI, parent views do not inherently dictate the size and position of child views. This becomes particularly relevant when dealing with images set to scaledToFill. This setting causes an image to expand until one of its dimensions aligns with the corresponding dimension of its parent view. However, without explicit constraints, this can lead to the image exceeding the bounds of the parent view if the other dimension is smaller. I think this is the biggest difference from image behaviours in UIKit.

So, we'd better explicitly define the dimensions of images. This ensures that they do not unexpectedly exceed the parent view's bounds. Here, the GeometryReader plays a role by providing access to the parent view's size, which reflects the available screen area for the image. This allows for dynamic sizing of the image based on the device's screen dimensions or the parent view's size.

Consider the following code snippet:

GeometryReader { geometry in
    post.picture
        .resizable()
        .scaledToFill()
        .frame(width: geometry.size.width, height: 439)
        .clipped()
        .cornerRadius(10)
}
// Apply padding or other modifiers below this line. As GeometryReader acts as a container view and may affect layout.
Sign up to request clarification or add additional context in comments.

Comments

1

Okay, I figured it out.

The frame(height: 439) and .fill caused the internal width value of the frame to be extended past the screen limits in order to keep the aspect ratio when the width > height.

This made it so the left and right edges of the frame were cut off when width > height. This seems like silly behaviour to me since there is nothing to indicate that the frame is larger than the screen width. Visually, the image looks clipped to the correct size, and documentation wise would imply this should work properly without explicitly setting the frame size (given that the max width of the VStack should be the width of the screen).

To fix it, I needed to place an explicit width size on the frame

post.picture
    .resizable()
    .scaledToFill()
    .frame(width: UIScreen.main.bounds.size.width, height: 439)
    .clipped()
    .cornerRadius(10)

.infinity doesn't work on the width like @Fuad suggested because that would only scale the frame to it's already large width. The frame needed to be cropped essentially.

1 Comment

That is essentially it, though for the .frame() I would consider using maxWidth:, maxHeight:
0

Please try this solution:

post.picture
    .resizable()
    .scaledToFit()
    .frame(
        minWidth: 0,
        maxWidth: .infinity,
        minHeight: 439,
        maxHeight: 439
    )
    .clipped()
    .clipShape(Circle())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.