---
title: "TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder"
framework: technotes
role: article
role_heading: Article
path: technotes/tn3211-resolving-swiftui-source-incompatibilities-for-state-and-contentbuilder
---

# TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder

Update existing code for two foundational changes in SwiftUI built with Xcode 27.

## Overview

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 time ContentBuilder 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 initialized The 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. important: Assigning to a @State property that already has an inline initial value is not supported and does not produce the expected behavior at runtime. The inline initial value takes precedence, and any value assigned in init is discarded. For example, the following view observes counter as 0 in its body: struct ContentView: View {     @State private var counter: Int = 0

init() {         self.counter = 42     } } When the initial value of a @State property must be supplied by the initializer, omit the inline default: struct ContentView: View {     @State private var counter: Int

init() {         self.counter = 42     } } 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' note: This diagnostic can occur in source files that do not explicitly import MapKit when the project does not enable member import visibility. Do not rule out this diagnostic based solely on the imports declared in the file. 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 time This 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.  |  |   |  |   |  |   |  |   |  |   |  |   |  |   |  |   |  |   |  |  Revision History 2026-06-08 First published.

## See Also

### Latest

- [TN3210: Optimizing your app for iPhone Mirroring](technotes/tn3210-optimizing-your-app-for-iphone-mirroring.md)
- [TN3212: Adopting gesture recognizers for Sidecar touch support](technotes/tn3212-adopting-gesture-recognizers-for-sidecar-touch-support.md)
- [TN3208: Preparing your app’s launch screen to meet App Store requirements](technotes/tn3208-preparing-your-apps-launch-screen-to-meet-app-store-requirements.md)
- [TN3205: Low-latency communication with RDMA over Thunderbolt](technotes/tn3205-low-latency-communication-with-rdma-over-thunderbolt.md)
- [TN3206: Updating Apple Pay certificates](technotes/tn3206-updating-apple-pay-certificates.md)
- [TN3179: Understanding local network privacy](technotes/tn3179-understanding-local-network-privacy.md)
- [TN3190: USB audio device design considerations](technotes/tn3190-usb-audio-device-design-considerations.md)
- [TN3194: Handling account deletions and revoking tokens for Sign in with Apple](technotes/tn3194-handling-account-deletions-and-revoking-tokens-for-sign-in-with-apple.md)
- [TN3193: Managing the on-device foundation model’s context window](technotes/tn3193-managing-the-on-device-foundation-model-s-context-window.md)
- [TN3115: Bluetooth State Restoration app relaunch rules](technotes/tn3115-bluetooth-state-restoration-app-relaunch-rules.md)
- [TN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen key](technotes/tn3192-migrating-your-app-from-the-deprecated-uirequiresfullscreen-key.md)
- [TN3151: Choosing the right networking API](technotes/tn3151-choosing-the-right-networking-api.md)
- [TN3111: iOS Wi-Fi API overview](technotes/tn3111-ios-wifi-api-overview.md)
- [TN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOS](technotes/tn3191-imap-extensions-supported-by-mail.md)
- [TN3134: Network Extension provider deployment](technotes/tn3134-network-extension-provider-deployment.md)
