Adding images to a SwiftUI application | SwiftUI Bootcamp #7
Displaying images correctly — without stretching, pixelating, or overflowing — is trickier than it first appears in SwiftUI. After this lesson you'll know the exact sequence of modifiers that makes any image resize, scale, and clip properly inside a fixed frame.
What You'll Learn
- Why
Imagerequires.resizable()before any sizing modifier will work - The difference between
.scaledToFit()and.scaledToFill()— and when to use each - How to clip an image into custom shapes like circles, rounded rectangles, or ellipses
Mental Model
Think of Image in SwiftUI like a physical photograph. By default, it displays at its natural size — if you drop a 3000×2000px photo into your view without any modifiers, it renders at full size and overflows the screen. Calling .resizable() is like making the photo out of elastic material: now it can stretch or shrink to fit whatever space you give it.
Once resizable, you choose a content mode: .scaledToFit() is like placing the photo in a frame and matting the edges — the whole photo is visible, letterboxed if necessary. .scaledToFill() is like filling the frame with the photo and cutting off what doesn't fit — no gaps, but some content is cropped.
Detailed Explanation
Image("name") loads a bitmap image from your app's asset catalog by name. Without any modifiers, it renders at exactly one point per pixel — a 300×200 asset renders as a 300×200pt view. This is almost never what you want.
.resizable() makes the image flexible so it responds to .frame() and scaling modifiers. It must come before scaling modifiers — if you call .frame() before .resizable(), the frame constrains the non-resizable image and the result is unpredictable.
.scaledToFit() and .scaledToFill() are the two aspect-ratio-preserving content modes:
- Fit: scales the image to fit entirely within the available space. The image's full content is visible, but there may be empty space on two sides (letterboxing).
- Fill: scales the image so that no empty space remains. The image always covers its entire container, but content outside the container is clipped. Without
.clipped(), the overflow is still visible (and can overlap neighboring views), so always pair.scaledToFill()with.clipped()or a clip shape.
.clipShape() masks the image with any Shape — Circle(), RoundedRectangle(cornerRadius:), Ellipse() — and produces clean-edged image crops. .cornerRadius() is a shortcut for rectangular clipping with rounded corners. .clipped() simply clips to the rectangular frame bounds.
Code Structure
The sample is in ImageBootcamp.swift and loads an image named "google" from the asset catalog. The active code chain makes it resizable, scales it to fit, constrains it to a 300×200 frame, and tints it green (via .renderingMode(.template), commented out). The many commented alternatives cover the most common image manipulation patterns you'll need in a real app.
Complete Code
ImageBootcamp.swift
import SwiftUI
struct ImageBootcamp: View {
var body: some View {
Image("google") // loads "google" image from the Assets.xcassets catalog
//.renderingMode(.template) // treats the image as a mask — lets foregroundColor tint it
.resizable() // makes the image flexible so it responds to frame and scale modifiers
//.aspectRatio(contentMode: .fit) // preserves aspect ratio, may leave empty space (letterbox)
//.scaledToFit() // same as aspectRatio(.fit) — the whole image is visible
.scaledToFit() // scales down/up to fit within the frame while preserving aspect ratio
.frame(width: 300, height: 200) // constrains the image to 300×200 points
.foregroundColor(.green) // only has visible effect when .renderingMode(.template) is active
//.clipped() // removes overflow pixels outside the frame boundary
//.cornerRadius(150) // rounds corners (150pt on a 300pt wide view makes a pill shape)
//.clipShape(
//Circle() // crops image to a circle
//RoundedRectangle(cornerRadius: 25.0) // crops to rounded rect
//Ellipse() // crops to an ellipse
// Circle()
//)
}
}
struct ImageBootcamp_Previews: PreviewProvider {
static var previews: some View {
ImageBootcamp()
}
}Code Walkthrough
Image("google")— Loads the image asset named "google" fromAssets.xcassets. The name is case-sensitive and must exactly match the asset name in the catalog. For image sets with @2x and @3x variants, Xcode automatically picks the right resolution — you always reference the base name..renderingMode(.template)— Converts the image into a silhouette and fills it withforegroundColor. This is useful for logo icons where you want the color to follow your app's accent. In.originalmode (the default), the image's own colors are used..resizable()— This modifier is mandatory for any image that needs to resize. Place it immediately afterImage(...), before any scaling or frame modifiers..scaledToFit()— Scales the image uniformly until it fits within the300×200frame. If the image's aspect ratio doesn't match the frame, empty space appears on two sides. Ideal for logos, illustrations, and content where the full image must be visible..frame(width: 300, height: 200)— Sets the display size. Without this,.scaledToFit()would fit the image to the entire screen..foregroundColor(.green)— Only visible when.renderingMode(.template)is active. In template mode, every pixel of the image becomes the foreground color, so the original image colors are lost..clipShape(Circle())— A powerful pattern for profile pictures and avatars. The image is first scaled (.scaledToFill()is more common for circular crops, to avoid letterboxing), then clipped to the circle shape. No overflow occurs becauseclipShapemasks everything outside the shape path.
Common Mistakes
Mistake: Forgetting .resizable() and wondering why .frame() has no effect on the image size Without .resizable(), an Image view has a fixed intrinsic size equal to its natural pixel dimensions. Applying .frame() creates a container of the right size but the image inside stays its natural size and either gets clipped or letterboxed in an unexpected way. Always call .resizable() first.
Mistake: Using .scaledToFill() without .clipped() or a clip shape.scaledToFill() scales the image until no empty space remains, which means the image often extends beyond its frame. The overflow is invisible in most cases, but it can cause the image to overlap sibling views in a stack. Always follow .scaledToFill() with .clipped() or .clipShape().
Mistake: Using .aspectRatio(contentMode: .fit) and .scaledToFit() on the same image They do the same thing — both set the content mode to .fit. Having both in the chain is redundant. Pick one. .scaledToFit() and .scaledToFill() are the more concise, modern form.
Key Takeaways
- Call
.resizable()before any frame or scale modifier — without it, images display at their natural pixel size .scaledToFit()keeps the full image visible (with possible letterboxing);.scaledToFill()fills the space entirely (with overflow that needs clipping)- Always pair
.scaledToFill()with.clipped()or.clipShape()to prevent overflow from overlapping neighboring views
Last updated: June 27, 2026