TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder
Update existing code for two foundational changes in SwiftUI built with Xcode 27.
Overview
SwiftUI in Xcode 27 introduces two changes to the language-level shape of the framework:
State is a Swift macro rather than a property wrapper. The macro enables lazy evaluation of a property’s initial value, and the compiler treats the property like any other stored property of the view.
Result builders are unified under ContentBuilder. In order to drastically improve typechecking performance, previously distinct builders such as ViewBuilder are unified under
@ContentBuilder, and builder blocks no longer carry specific protocol constraints on their contents, drastically improving typechecking performance.
The vast majority of SwiftUI code in your app is unaffected and continues to compile and behave the same way without modification. A small number of patterns that depended on the previous implementations now produce build errors. The following sections describe each known source incompatibility, the recommended remediation, and the underlying cause.
To locate a specific build error, see the Diagnostic reference at the end of this document.
Understanding the changes
Before diving into specific errors, it helps to understand what changed and why. Both changes were made to target specific developer pain-points, but share a common theme that causes source incompatibility: the compiler now has different information about your code, meaning some code that compiled before is now unsupported.
How @State evolved to support laziness
Previously, when you wrote a state variable, that state value would be recreated every time its containing view was initialized. In SwiftUI, views are intended to be lightweight values that are created frequently, and so views and their contained state values are recreated frequently.
This behavior is fine when the state contains value types, but reference types must heap allocate every time they’re instantiated, meaning this recreation causes performance degradations, or unintended work to initialize these objects.
By making @State a macro, SwiftUI can ensure that objects held in state are only ever initialized one time, when the view is first created, avoiding this performance issue.
How result builders changed to be unified under @ContentBuilder
SwiftUI previously defined a separate result builder for each kind of content you compose. Every primitive those builders share, such as Group, ForEach, and Section, provided a separate initializer for each builder. Because these initializers differ only in the type of content the builder produces, and that type doesn’t appear at the call site, the compiler infers which initializer to use.
Inferring the initializer requires typechecking the entire closure body. When that body contains more overloaded primitives, the compiler repeats this work for each candidate while keeping its choices consistent across the closure. The cost grows quickly as builders nest, and deeply nested uses of Group, Section, or ForEach can exceed the compiler’s limits, producing the error:
the compiler is unable to type-check this expression in reasonable timeContentBuilder replaces these builders with a single builder that imposes no constraints on its contents. It assembles arbitrary content, and that content conforms to the protocol its context requires only when its elements do. Each primitive provides one builder-based initializer, so the compiler no longer searches indistinguishable overloads or typechecks closure bodies to choose one. Your code keeps the same syntax and type safety, and typechecks substantially faster.
Migrating code that uses @State
@State now participates in initialization the same way any stored property does, meaning the compiler now diagnoses several patterns that were previously accepted, but never well-defined.
The content within this section covers these patterns that no longer compile.
Initialize stored properties before assigning to @State in init
What happens: A view whose init assigns to a @State property before all other stored properties have been initialized produces one of the following diagnostics:
error: 'self' used in property access '_counter' before 'super.init' call
error: variable 'self.counter' used before being initializedThe following code reproduces the error:
import SwiftUI
struct ContentView: View {
var name: String
@State private var counter: Int
init(name: String) {
self.counter = 42
self.name = name
}
var body: some View { Text("\(name): \(counter)") }
}How to fix it: Initialize all non-@State stored properties before assigning to any @State property.
Why this occurs: The @State macro synthesizes a backing storage property, _counter in the example above. Assigning to this property before the remainder of self is initialized is a use of self prior to full initialization, which the compiler now reports. This pattern arises when a view’s initial state must be derived from a value passed to its initializer.
Avoid composing additional property wrappers with @State
What happens: Applying another property wrapper to a property declared with @State produces:
error: invalid redeclaration of synthesized property '_counter'How to fix it: Remove the additional property wrapper.
Why this occurs: The composed property wrapper and the @State macro each attempt to synthesize a backing storage property using the same underscore-prefixed name.
Define memberwise initializers explicitly when a view has private @State
What happens: In Swift, a type whose stored properties are all private and that does not declare an explicit initializer receives a synthesized memberwise initializer with private access, callable from initializers defined in extensions of the type. Views that declare a @State property no longer receive this synthesized initializer, and call sites in extensions fail to resolve:
struct Foo: View {
@State private var bar = 0
private let baz: Int
}
extension Foo {
init(_ bar: Int, baz: Int) {
self.init(bar: bar, baz: baz) // error: no exact matches in call to initializer
}
}How to fix it: Define the memberwise initializer explicitly rather than relying on synthesis.
Why this occurs: The expansion of the @State macro causes the compiler to skip synthesis of the memberwise initializer, per Init Accessors.
Migrating code that uses result builders
Many of SwiftUI’s result builders are unified under @ContentBuilder, improving typechecking performance. To accomplish this, the constraint that previously required builder block contents to conform to View has been removed. This change causes a few source incompatibilities, most of which relate to no longer being able to use that view constraint to choose between otherwise ambiguous overloads.
Use the closure-based forms of background and overlay for ShapeStyle expressions
What happens: A call to the deprecated, non-builder forms of background(alignment:content:) or overlay(alignment:content:) that passes a ShapeStyle expression composed with modifiers such as opacity(_:) or blendMode(_:) produces:
error: ambiguous use of 'opacity'
error: ambiguous use of 'blendMode'The following code reproduces the error:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello")
.overlay(Color.blue.opacity(0.70).blendMode(.overlay))
}
}How to fix it: Use the closure-based form of background(_:) or overlay(_:):
import SwiftUI
struct ContentView: View {
var body: some View {
Rectangle()
.overlay { Color.blue.opacity(0.3).blendMode(.overlay) }
}
}Why this occurs: Both background and overlay provide overloads that accept either a View or a ShapeStyle, and modifiers such as opacity(_:) on a ShapeStyle return either a ShapeStyle or a View. The View constraint that ViewBuilder previously imposed allowed the compiler to select a single resolution for these expressions. Without that constraint, the competing overloads are equally viable and the expression becomes ambiguous. The closure-based form selects the builder overload directly and resolves the ambiguity.
Disambiguate types and members that collide across modules
What happens: If a project imports a module that declares a type whose name matches a SwiftUI type, or a type with members that match those of a SwiftUI type, the compiler may report an ambiguity at the use site. This applies to any duplicated type, such as a framework’s own Text declaration whose overloads match those of SwiftUI’s Text, and to any duplicated static member of such a type, such as red, green, blue, or clear.
For example, when a project imports a module that declares its own Color type with a clear member, the compiler produces:
error: ambiguous use of 'clear'The following code reproduces the error:
// In MyPackage:
public struct Color {
public static let clear = Color()
}
// In the app:
import SwiftUI
import MyPackage
struct ContentView: View {
var body: some View {
Color.clear
}
}How to fix it: Fully qualify the type with its module name, or rename the conflicting type or member in the other module.
import SwiftUI
import MyPackage
struct ContentView: View {
var body: some View {
SwiftUI.Color.clear
}
}Why this occurs: The View constraint on @ViewBuilder previously allowed the compiler to exclude non-View-conforming candidates during overload resolution. With the constraint removed, both candidates are equally valid and the compiler reports the ambiguity.
Update generic constraints that reference TupleView
What happens: Code that names TupleView as a nested generic type argument may produce one of the following diagnostics:
error: cannot convert value of type 'VStack<TupleContent<Text, Text>>' to expected argument type 'VStack<TupleView<(Text, Text)>>'
error: cannot convert value of type 'Label<TupleContent<Text, Text?>, Image?>' to expected argument type 'Label<TupleView<(Text, Optional<Text>)>, Optional<Image>>'The following code reproduces the error:
import SwiftUI
struct CardView<Content: View>: View {
var content: Content
var body: some View { content }
init(@ContentBuilder content: () -> Content) {
self.content = content()
}
}
extension CardView where Content == VStack<TupleView<(Text, Text)>> {
init(title: String, subtitle: String) {
self = CardView {
VStack {
Text(title)
Text(subtitle)
}
}
}
}How to fix it: Prefer opaque types such as some View rather than spelling the concrete builder return type. When a concrete spelling is required, replace TupleView with TupleContent in the constraint:
extension CardView where Content == VStack<TupleContent<Text, Text>> {
init(title: String, subtitle: String) {
self = CardView {
VStack {
Text(title)
Text(subtitle)
}
}
}
}For projects with a minimum deployment target earlier than iOS 27, iPadOS 27, macOS 27, or visionOS 27, where TupleContent is unavailable, retain the TupleView constraint and construct the TupleView explicitly inside the builder:
extension CardView where Content == VStack<TupleView<(Text, Text)>> {
init(title: String, subtitle: String) {
self = CardView {
VStack {
TupleView((
Text(title),
Text(subtitle)
))
}
}
}
}Why this occurs: The unified @ContentBuilder produces TupleContent rather than TupleView as the concrete return type for multi-expression builder blocks. When TupleView appears as a nested generic argument, the contextual type does not propagate deeply enough to redirect the inner builder’s return type, and the resulting type does not match the declared constraint.
Provide an explicit empty content value in nested builders when MapKit is in scope
What happens: When SwiftUI and MapKit are both in scope in a source file, an empty result builder block — including a conditional compilation block whose only branch is excluded — within a nested builder produces:
error: return type of property 'body' requires that 'EmptyMapContent' conform to 'View'The following code reproduces the error:
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
Group { }
}
}The same error occurs when a conditional compilation block resolves to an empty body — for example, when the active configuration causes the only branch to be excluded:
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
Group {
#if MY_CONDITION
MyView()
#endif
}
}
}How to fix it: Supply an explicit EmptyContent (or EmptyView) value:
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
Group {
EmptyContent()
}
}
}For the conditional compilation case, add an #else branch that produces an explicit EmptyContent() (or EmptyView()):
import SwiftUI
import MapKit
struct ContentView: View {
var body: some View {
Group {
#if MY_CONDITION
MyView()
#else
EmptyContent()
#endif
}
}
}Why this occurs: Without the View constraint on the builder, an empty body is ambiguous when MapKit is also in scope: MapKit declares its own result builder that produces EmptyMapContent, and the compiler has no basis to choose between the two. Supplying an explicit EmptyContent() (or EmptyView()) anchors the resolution to a concrete View-conforming expression.
Extract deeply branching content from Chart closures when back-deploying
What happens: For projects whose minimum deployment target is earlier than iOS 27, iPadOS 27, macOS 27, or visionOS 27, a Chart that contains deeply branching if / else if or switch statements may produce:
error: the compiler is unable to type-check this expression in reasonable timeThis diagnostic does not occur in projects whose minimum deployment target is iOS 27, iPadOS 27, macOS 27, or visionOS 27. The threshold for the diagnostic is approximately ten or more branches.
The following code reproduces the error:
import SwiftUI
import Charts
struct DataPoint {
var index: Int
var rate: Double
var signal: Double
var noise: Double
var errors: Double
var throughput: Double
var txRate: Double
var rxRate: Double
var txFrames: Double
var rxFrames: Double
var channel: Double
var bandwidth: Double
var defaultValue: Double
}
struct MetricChartView: View {
var selectedMetric: String
var dataPoints: [DataPoint]
var body: some View {
Chart(dataPoints, id: \.index) { dataPoint in
if selectedMetric == "Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rate))
.foregroundStyle(.blue)
} else if selectedMetric == "Signal" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.signal))
.foregroundStyle(.green)
} else if selectedMetric == "Noise" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.noise))
.foregroundStyle(.red)
} else if selectedMetric == "Errors" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.errors))
.foregroundStyle(.orange)
} else if selectedMetric == "Throughput" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.throughput))
.foregroundStyle(.purple)
} else if selectedMetric == "TX Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.txRate))
.foregroundStyle(.cyan)
} else if selectedMetric == "RX Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rxRate))
.foregroundStyle(.mint)
} else if selectedMetric == "TX Frames" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.txFrames))
.foregroundStyle(.indigo)
} else if selectedMetric == "RX Frames" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rxFrames))
.foregroundStyle(.brown)
} else if selectedMetric == "Channel" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.channel))
.foregroundStyle(.teal)
} else if selectedMetric == "Bandwidth" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.bandwidth))
.foregroundStyle(.pink)
} else {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.defaultValue))
.foregroundStyle(.gray)
}
}
}
}How to fix it: Extract the branching logic into a separate function annotated with ChartContentBuilder:
import SwiftUI
import Charts
struct MetricChartView: View {
var selectedMetric: String
var dataPoints: [DataPoint]
var body: some View {
Chart(dataPoints, id: \.index) { dataPoint in
marks(for: dataPoint)
}
}
@ChartContentBuilder
private func marks(for dataPoint: DataPoint) -> some ChartContent {
if selectedMetric == "Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rate))
.foregroundStyle(.blue)
} else if selectedMetric == "Signal" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.signal))
.foregroundStyle(.green)
} else if selectedMetric == "Noise" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.noise))
.foregroundStyle(.red)
} else if selectedMetric == "Errors" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.errors))
.foregroundStyle(.orange)
} else if selectedMetric == "Throughput" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.throughput))
.foregroundStyle(.purple)
} else if selectedMetric == "TX Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.txRate))
.foregroundStyle(.cyan)
} else if selectedMetric == "RX Rate" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rxRate))
.foregroundStyle(.mint)
} else if selectedMetric == "TX Frames" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.txFrames))
.foregroundStyle(.indigo)
} else if selectedMetric == "RX Frames" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.rxFrames))
.foregroundStyle(.brown)
} else if selectedMetric == "Channel" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.channel))
.foregroundStyle(.teal)
} else if selectedMetric == "Bandwidth" {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.bandwidth))
.foregroundStyle(.pink)
} else {
LineMark(x: .value("X", dataPoint.index), y: .value("Y", dataPoint.defaultValue))
.foregroundStyle(.gray)
}
}
}Why this occurs: Supporting back-deployment of @ContentBuilder in Swift Charts requires a compatibility overload of buildEither that returns a Charts-specific BuilderConditional type. The presence of this additional overload increases the size of the candidate set the compiler must consider when resolving branching expressions inside chart builders. As the number of branches grows, the candidate set grows superlinearly, and beyond a certain point the expression exceeds the compiler’s complexity budget. Projects whose minimum deployment target is iOS 27, iPadOS 27, macOS 27, or visionOS 27 are unaffected because the compatibility overload is not compiled in. Extracting the branches into a dedicated @ChartContentBuilder function isolates the type-checking work and keeps each expression within budget. In aggregate, this trade-off improves type-checking performance for chart content outside this specific case, and for all SwiftUI content in projects that import Swift Charts.
Diagnostic reference
To locate the section that addresses a specific build error, match the diagnostic text to the entry below.
Type | Error | Learn More |
|---|---|---|
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
| Memberwise initializer not callable from extension | Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
|
| Tn3211 Resolving Swiftui Source Incompatibilities For State And Contentbuilder |
Revision History
2026-06-08 First published.
See Also
Latest
TN3210: Optimizing your app for iPhone MirroringTN3212: Adopting gesture recognizers for Sidecar touch supportTN3208: Preparing your app’s launch screen to meet App Store requirementsTN3205: Low-latency communication with RDMA over ThunderboltTN3206: Updating Apple Pay certificatesTN3179: Understanding local network privacyTN3190: USB audio device design considerationsTN3194: Handling account deletions and revoking tokens for Sign in with AppleTN3193: Managing the on-device foundation model’s context windowTN3115: Bluetooth State Restoration app relaunch rulesTN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen keyTN3151: Choosing the right networking APITN3111: iOS Wi-Fi API overviewTN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOSTN3134: Network Extension provider deployment