- Published on
- 4 min read Beginner
> Why Your Image Isn't Showing in a Live Activity
// What_You_Will_Learn
- Understand Live Activity image size limitations
- Resize and compress images for ActivityKit content
- Debug grey box rendering issues in Live Activities
You've got a Live Activity running and everything looks great with SF Symbols. Then you swap in a custom image from your asset catalog and instead of your icon, you get a grey box. No error, no crash, just silence and a placeholder where your image should be.
This is one of those problems that's frustrating because there's nothing obviously wrong. The image exists in your asset catalog, the code compiles, and SF Symbols work fine in the same spot. The issue comes down to how ActivityKit handles image data, and there are two constraints that trip people up.
The 4KB Data Limit
ActivityKit imposes a strict size limit on the data you send with updates. From Apple's documentation:
The updated dynamic data for both ActivityKit updates and ActivityKit push notifications can't exceed 4KB in size.
This means everything in your ContentState needs to fit within 4KB when encoded. If you're trying to pass image data as part of your activity update, you'll blow through that limit quickly. Images should live in the widget extension's asset catalog, not get passed through the content state.
Here's a common pattern that works. Define your content state with only lightweight data:
import ActivityKit
struct DeliveryAttributes: ActivityAttributes {
var orderNumber: String
struct ContentState: Codable, Hashable {
var status: String
var estimatedArrival: Date
}
}
Then reference images directly in your widget view by name, pulling them from the widget extension's asset catalog:
import SwiftUI
import WidgetKit
struct DeliveryActivityView: View {
let context: ActivityViewContext<DeliveryAttributes>
var body: some View {
HStack {
Image("delivery-icon")
.resizable()
.frame(width: 36, height: 36)
VStack(alignment: .leading) {
Text(context.state.status)
.font(.headline)
Text(context.state.estimatedArrival, style: .timer)
.font(.caption)
}
}
.padding()
}
}
One thing to watch out for: the image needs to be in the widget extension's asset catalog, not the main app's. Widget extensions are separate targets and don't automatically inherit the main app's assets.
Image Resolution Matters
Even with the image in the right place, you can still end up with a grey box if the image is too large. The system requires that image assets for a Live Activity use a resolution smaller than or equal to the size of the presentation on the device. If the image exceeds those dimensions, the system may fail to render the Live Activity entirely.
For example, an image in the minimal Dynamic Island presentation shouldn't exceed about 45 x 36.67 points. The Lock Screen presentation gives you more room, but you still need to be mindful of the size. Check Apple's Human Interface Guidelines for exact dimensions for each presentation type.
The fix is to provide properly sized image assets in your widget extension's asset catalog. Use the 1x, 2x, and 3x slots to supply appropriately scaled versions, and keep the point size within the bounds of your layout. If you're using resizable() to scale an image down in your view, the original asset still needs to be within the system's size limits.
Quick Checklist
If your Live Activity image is showing a grey box, check these things:
- Is the image in the widget extension's asset catalog, not the main app's?
- Is your
ContentStateunder the 4KB limit? - Is the image resolution within the size limits for the presentation type you're targeting?
- Are you providing the correct scale variants (1x, 2x, 3x)?
Getting any one of these wrong produces the same symptom: a silent grey box with no error to guide you. Once you've got the asset in the right catalog at the right size, the image should appear without any fuss.
Sample Project
Want to see this code in action? Check out the complete sample project on GitHub:
The repository includes a working Xcode project with all the examples from this article, plus unit tests you can run to verify the behavior.
// Continue_Learning
How to Preview Live Activities in SwiftUI
Use the #Preview macro to preview your Live Activity views directly in Xcode without running the full app.
ControlWidgetButton for Control Center Widgets
Create interactive buttons for the Control Center using ControlWidgetButton and App Intents in iOS 18.
Building Custom Container Views in SwiftUI with Subviews
iOS 18 introduced public APIs for building custom container views that can decompose content into individual subviews. Learn how to use ForEach(subviews:), Group(subviews:), and ContainerValues.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.