The short guide on how to render custom view in swipe action.
Task
Use SwiftUI views inside swipeActions by prerendering them into Image objects, overcoming UIKit limitations.

Solution
In SwiftUI, swipeActions only accept Image/UIImage as button labels. This restricts the ability to use rich SwiftUI layouts like Circle, RoundedRectangle, or even combinations of views with SF Symbols and colors. To overcome this, we can render SwiftUI views into images.
Meet ImageRenderer
ImageRenderer is a powerful API available from iOS 16+, allowing you to convert any SwiftUI view into a UIImage. It’s especially useful when you’re working with UIKit-bound APIs or places like swipeActions that only accept image-based content.
Here’s how it works:
- You wrap your SwiftUI content in a fixed-size container.
- You pass it to
ImageRenderer. - You retrieve a
UIImage(orImagefor SwiftUI) that visually matches your SwiftUI view.
This allows full creative control with shapes, text, gradients, SF Symbols, and more — and then reuse that output anywhere UIImage is expected.
Implementation
Here’s a reusable function that does the rendering:
func render(size: CGSize, @ViewBuilder content: () -> some View) -> Image? {
let renderer = ImageRenderer(
content: content()
.frame(width: size.width, height: size.height)
)
renderer.scale = UIScreen.main.scale
renderer.proposedSize = .init(size)
return renderer.uiImage.map { Image(uiImage: $0) }
}
sizedefines the exact output size of the image..proposedSizeensures the renderer respects layout.- The SwiftUI view is framed and passed into the renderer.
Benefits
- Use rich SwiftUI layout (shapes, overlays, icons) where only
Imageis allowed- Retain visual consistency with the rest of your UI
- Customize light/dark mode rendering via SwiftUI environment if needed
- Combine this with caching for performance
Notes
- Works on iOS 16 and later .
- You can wrap this rendering in a cache layer for efficiency.
.tint(.clear)is important to avoid system tint from affecting your rendered image.
Demo Project
import SwiftUI
struct ContentView: View {
@Environment(\.displayScale) var displayScale
@State private var items = Array(repeating: "0", count: 10)
var body: some View {
List {
ForEach(items.indices, id: \.self) { index in
Text("Row \(index)")
.swipeActions(edge: .trailing) {
Button {
print("Tapped action on row \(index)")
} label: {
render(size: CGSize(width: 36, height: 36)) {
Circle()
.fill(Color.blue)
.overlay {
Image(systemName: "star.fill")
.foregroundColor(.white)
}
}
}
.tint(.clear)
Button {
print("Tapped action on row \(index)")
} label: {
render(size: CGSize(width: 36, height: 36)) {
RoundedRectangle(cornerRadius: 6)
.fill(Color.blue)
.overlay {
Image(systemName: "document.fill")
.foregroundColor(.white)
}
}
}
.tint(.clear)
}
}
}
}
// MARK: - Renderer
func render(size: CGSize, @ViewBuilder content: () -> some View) -> Image? {
let renderer = ImageRenderer(
content: content()
.frame(width: size.width, height: size.height)
)
renderer.scale = UIScreen.main.scale
renderer.proposedSize = .init(size)
return renderer.uiImage.map { Image(uiImage: $0) }
}
}