Starting with iOS 26, SwiftUI's UIHostingView may use CALayer-based rendering (caLayerSystem) instead of the traditional UIKit-based rendering (uikitSystem) when certain conditions are met. This can cause unexpected changes in the view hierarchy — for example, _UIGraphicsView and CGDrawingView subviews may be replaced by plain CALayer and CGDrawingLayer sublayers.
Here are two approaches to temporarily disable this behavior in your local debug environment.
Set the environment variable SWIFTUI_DISABLE_MIXED_VIEW_HIERARCHY=1 in your scheme's Run configuration (Edit Scheme → Run → Arguments → Environment Variables).
This forces platform.mayInsertCALayers to return false for uikitSystem, preventing the switch to CALayer-based rendering.
Note: This is a debug/development-only workaround. Do not ship with this.
SwiftUI includes an SPI modifier mayNotInsertCALayersEffect() that injects the .mayNotInsertCALayers property into the display list, preventing CALayer insertion for the modified subtree.
The modifier is hidden behind @_spi(Private). To use it, add the following declarations to your SDK's swiftinterface file:
File: <Xcode.app>/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/SwiftUICore.framework/Modules/SwiftUICore.swiftmodule/arm64-apple-ios-simulator.swiftinterface
These declarations exist under @_spi(Private) in Apple's internal SwiftUI interface but are stripped from the public SDK. Add them to the swiftinterface file:
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
public struct MayNotInsertCALayersEffect : SwiftUICore.RendererEffect {
@_Concurrency.MainActor @preconcurrency public init()
@available(iOS 18.0, tvOS 18.0, watchOS 11.0, macOS 15.0, visionOS 2.0, *)
public typealias AnimatableData = SwiftUICore.EmptyAnimatableData
@available(iOS 18.0, tvOS 18.0, watchOS 11.0, macOS 15.0, visionOS 2.0, *)
public typealias Body = Swift.Never
}
@available(*, unavailable)
extension SwiftUICore.MayNotInsertCALayersEffect : Swift.Sendable {
}
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
extension SwiftUICore.View {
@_alwaysEmitIntoClient nonisolated public func mayNotInsertCALayersEffect() -> some SwiftUICore.View {
return modifier(SwiftUICore.MayNotInsertCALayersEffect())
}
}struct ContentView: View {
var body: some View {
VStack {
Color.red
Text("123")
}
.mayNotInsertCALayersEffect()
}
}Without mayNotInsertCALayersEffect() (iOS 26.2 Simulator — CALayer rendering):
<_UIHostingView: 0x101d12ed0; frame = (0 0; 390 844); ...>
| <CALayer: 0x600000c13990> (layer)
| <CGDrawingLayer: 0x600002c34080> (layer)
With mayNotInsertCALayersEffect() (UIKit rendering restored):
<_UIHostingView: 0x101c0d680; frame = (0 0; 390 844); ...>
| <SwiftUI._UIGraphicsView: 0x101d25ea0; frame = (0 47; 390 736.333); ...>
| <SwiftUI.CGDrawingView: 0x101d26560; frame = (181.333 789.667; 27.6667 20.3333); ...>
In iOS 26, UIHostingView evaluates whether to use CALayer-based rendering via two conditions:
platform.mayInsertCALayers— controlled by theSWIFTUI_DISABLE_MIXED_VIEW_HIERARCHYenv var or theSemantics.forcedversion!state.pointee.properties.contains(.mayNotInsertCALayers)— controlled byMayNotInsertCALayersEffect
If either condition is false, the hosting view stays on the traditional UIKit rendering path.
Under the hood, MayNotInsertCALayersEffect.effectValue(size:) returns Effect.properties(.mayNotInsertCALayers):
_MayNotInsertCALayersEffect.effectValue(size:):
mov w9, #0x200
str x9, [x8]
str wzr, [x8, #0x8]
mov w9, #0x2
strb w9, [x8, #0xc]
ret