@jacobtechtavern It's for SwiftUI async rendering. openswiftuiproject.github.io/OpenSwiftUI/do...
— Kyle Ye (@KyleSwifter) April 23, 2026
SwiftUI async rendering is not just a generic "move drawing to a background thread" switch. In OpenSwiftUI's UIKit hosting path, async rendering is only available when the view graph can update outputs and render the display list without touching main-thread-only state.
The rough shape is:
- A display link drives the render loop.
- The hosting view decides whether the current pass can use the async renderer.
- The
ViewGraph.updateOutputsAsyncmethod attempts to update outputs on the async thread. - If the async pass succeeds, the display list is rendered there.
- If a requirement is not met, rendering falls back to the main thread.
One surprising requirement is hostPreferenceValues. It is set up when the hierarchy contains dynamic containers such as ForEach, conditional branches, or optional view unwrapping. Without those structures, the graph may have no host preferences to anchor an async update, so allowsAsyncUpdate() returns false.
That means two views that look equally simple in source can behave differently from the renderer's perspective:
struct CanUseAsyncRender: View {
@State private var items = [6]
var body: some View {
VStack {
ForEach(items, id: \.self) { value in
Color.blue.opacity(Double(value) / 6.0)
.frame(height: 50)
.transition(.slide)
}
}
.animation(.easeInOut(duration: 2), value: items)
}
}
ForEach
creates dynamic structure in the view graph, which gives the renderer the bookkeeping it needs for this async update path.
struct StaysOnMainRenderPath: View {
@State private var showRed = false
var body: some View {
Color(showRed ? .red : .blue)
}
}
This second example changes a value inside one view. There is no dynamic container boundary, so it is less likely to satisfy the async rendering path's requirements.
Debugging Angle
When a SwiftUI animation unexpectedly stays expensive on the main thread, I do not stop at the animation curve or drawing cost. The view graph shape matters too. Dynamic containers, preference propagation, pending transactions, and main-thread-only attributes can all decide whether async rendering is available.
In OpenSwiftUI, OPENSWIFTUI_PRINT_TREE=1 is a useful way to inspect the display list structure while debugging this class of issue.