The aspectRatio Modifier In SwiftUI

Posted in SwiftUI

March 7th, 2025

⏱ Reading Time: 7 mins

Presenting proportionally scaled views in SwiftUI is something we often do, with the Image view being the most significant example. The main tool for scaling is the aspectRatio modifier, which helps us avoid distorted views, while also letting us choose whether a view fits within its bounds or fills the available space given to it. The aspectRatio modifier is more than what it's known for, or what we use it for. In this post we'll explore it in greater depth and find out how it works, so we can make the most of it in SwiftUI.


Defining the content mode

We'll get started with the most common use case, displaying an image in an Image view. By default, any image is presented in its original size, occupying the entire available space. If the Image view is a child view in a container or a specific frame is set to it, it disregards any bounds in order to present the image in full size. Take a look at the following:

☝️Note:
Image Credits: Luis Felipe Alburquerque Briganti: https://www.pexels.com/el-gr/photo/galaxy-5191961/

Even though we set a frame, the Image view overrides it, with large portions of the image being off-screen. That's normal to happen, because images are not resizable by default; we have to explicitly dictate that with the resizable modifier:

The image is now resized to the frame we set, but it’s distorted because its proportions do not match those of the frame. Similar undesired results appear even if we omit frame:


We can fix this using the aspectRatio view modifier, which scales the Image view based on the content mode we'll provide. Let's start with fit, the most common content mode:

The above modifier resizes the Image view, making its width equal to the width of the container view —the main view of the window in this case. But at the same time, it respects the original width-to-height ratio of the image, and calculates the final height accordingly. As a result, the image is not distorted any more.

Notice that the aspectRatio modifier with the fit content mode is similar to applying the scaledToFit modifier:

☝️Note:
The combination of resizable, aspectRatio (or scaledToFit) and usually frame modifier is a common recipe to scale down and show images in correct proportion.

The "fill" content mode

Besides fit, there is also the fill content mode which we can provide as an argument to aspectRatio modifier. Unlike the former, fill occupies the entire available space, while maintaining the original aspect ratio. If the container view and the Image view (or any other view that's scaled) have the same aspect ratio, then scaling works as expected. Otherwise, the fill mode makes the scaled view match the container’s size on one axis and calculates the other dimension, which is always larger:

In the above screenshot the height of the image is equal to the height of the view. However, the width is much larger so the image is not distorted, resulting to a large area of it being out of the visible area.

Generally, the fill mode scales views in a way that usually escape the bounds of the parent view. Before we get to see how to prevent that from happening, it's important to underline that similar to the .aspectRatio(contentMode: .fill) is the scaledToFill modifier:

Comparing content modes side by side

To get a better taste of the two content modes, let's consider the following example, where we have the same image presented twice in a VStack. The first time is scaled down using the fit content mode, the second using fill. A frame is applied to both Image views, while a border color indicates the bounds of each view visually:

With the fit mode, the image is resized and remains constrained in the specified frame. The width becomes equal to the available width, while the height is calculated based on the aspect ratio of the image.

With the fill mode, the height of the Image view becomes equal to the height we set in the frame modifier. However, the width disregards the width value in frame and it's expanded, so the original aspect ratio is preserved.

The above screenshot clearly demonstrates the behavior of the aspectRatio modifier when using either content mode. There's no better or worse choice, it's all about applying the right mode for the needs of each app.

It's possible to prevent the image from exceeding the specified bounds in the second Image view. We do that with the clipped modifier, which we can apply right after the frame modifier:

Beyond images – More ways to use aspect ratio

So far we have talked only about images and the Image view, and that's fine because aspectRatio is used to help resize images while maintaining the original proportions in the majority of cases. But the truth is that it can be useful in other views too, where a width-to-height ratio is needed.

Unlike Image views where the content itself implies an aspect ratio (i.e. images), in other views we have to explicitly specify it. And for that, the aspectRatio modifier gives us an additional initializer where, besides the content mode, we also have to provide the desired aspect ratio as a CGFloat value.

Take a look at the following example. The aspectRatio is applied to a SwiftUI Color, while the desired aspect ratio is given as the first argument. For easier understanding, the border visually indicates the frame of the Color view:

In this particular example, we set the width-to-height ratio to 4/3. Given that we use the fit content mode and the width is equal to the width of the color view, the height is equal to: width * 3/4 = 300 * 3/4 = 225 pts. As a result, the color view is scaled as shown in the above screenshot, always staying within the specified frame.

In the previous code we passed the aspect ratio value as a CGFloat value (even though it was expressed as a fraction for better visibility). In addition to that, there's one last initializer that accepts a CGSize value as the first argument. There, we can set the ratio by providing distinct values to width and height.

For instance, let's see the previous example again but using CGSize as first argument this time:

Next, you can see color views scaled using various aspect ratios, providing a better visual understanding of how aspectRatio affects them:

Animating aspect ratio

When using the aspectRatio modifier we mainly focus on the content mode and, when necessary, the aspect ratio value we explicitly set. So it might not be immediately obvious, but we can animate changes to the aspect ratio value, creating smooth visual transitions in the target view.

The first thing we need to do in order to achieve this is to declare a state property in the view that will keep the aspect ratio value. That's what we'll animate later:

Next, let's use similar examples to those in the previous parts, where we apply aspectRatio to a Color with a specific frame:

Notice that instead of a hardcoded aspect ratio, we just provide the aspectRatioValue we set earlier.

To keep things simple, let's animate changes to aspectRatioValue when the Color appears. We'll use a withAnimation block as shown next:

We set the animation to run forever, switching between two different aspect ratio values. When the view appears, the following animation starts:

Conclusion

Even though we mostly use aspectRatio to proportionally scale images in combination with the resizable modifier, this post has made it clear that it's more than that. Image view is not the only type of view that can use aspectRatio; we can use it with other views too, explicitly setting the desired width-to-height ratio. Even more, we can animate aspect ratio values, managing to create attractive visual transitions when needed. I hope this has been valuable to you, thanks for reading!


Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.