When an existing SwiftUI view is built with Xcode 27, an assignment to @State inside init can fail with variable 'self.x' used before being initialized. This is a source-compatibility consequence of SwiftUI changing State from a property wrapper to a Swift macro. The macro participates in definite initialization like other stored properties. This guide applies Apple’s TN3211 recommendations and explains how to fix the code without hiding a runtime initialization bug.
The error message
error: variable 'self.counter' used before being initialized
Depending on the shape of the type, Xcode may report the related diagnostic:
error: 'self' used in property access '_counter' before 'super.init' call
Reproduction
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)")
}
}
In Xcode 27, assigning to counter reaches backing storage synthesized by the macro. At that point, name has not been initialized, so the compiler diagnoses access to self before the value is fully initialized.
Why it happens
SwiftUI in Xcode 27 implements @State as a Swift macro instead of a property wrapper. This enables lazy initialization, particularly avoiding repeated allocation of reference-type state whenever a lightweight view value is recreated. The source-level consequence is that @State now participates explicitly in the view’s initialization rules. Patterns that previously compiled despite an ambiguous initialization order can now fail.
Key point: This diagnostic does not mean the state value’s type is invalid. It normally means that assignments are in the wrong order, or that an inline default is competing with an assignment in init.
Affected versions and conditions
- The SwiftUI source is compiled with Xcode 27.
- The view contains both
@Stateand other stored properties. - An initializer assigns to
@Statebefore all non-@Statestored properties are initialized. - Alternatively, the
@Statedeclaration has an inline default whileinitassigns a different value.
Apple first published TN3211 on June 8, 2026 to document @State and @ContentBuilder source incompatibilities in Xcode 27. The lazy-state runtime behavior is back-deployed to iOS 17, macOS 14, and aligned releases where @Observable first appeared, but this compiler diagnostic requires a source update when building with Xcode 27.
Recommended fix
1. Initialize non-@State stored properties first
import SwiftUI
struct ContentView: View {
var name: String
@State private var counter: Int
init(name: String) {
self.name = name
self.counter = 42
}
var body: some View {
Text("\(name): \(counter)")
}
}
Follow Apple’s recommended ordering: initialize every non-@State stored property before assigning any @State property. If the view has multiple initializers, apply the same ordering to each one.
2. Remove the inline default when init supplies the state value
Avoid combining two sources of the initial value:
struct CounterView: View {
@State private var counter = 0
init(initialCount: Int) {
self.counter = initialCount
}
var body: some View {
Text("\(counter)")
}
}
Make the initializer the only source instead:
struct CounterView: View {
@State private var counter: Int
init(initialCount: Int) {
self.counter = initialCount
}
var body: some View {
Text("\(counter)")
}
}
If the inline default remains, it takes precedence and an assignment made in init can be discarded at runtime. A successful build is not enough; the state must have one unambiguous source of its initial value.
Alternatives
- If the state always starts with a constant, keep an inline initialization such as
@State private var counter = 0and remove the assignment frominit. - If the value should remain controlled by the parent, reconsider copying it into local state. A regular stored property or
@Bindingmay better represent the data flow. - For reference-type state owned by the view, choose either declaration-site initialization or initializer-based initialization so that Xcode 27’s lazy-state semantics remain predictable.
Note: Mechanically rewriting the code to _counter = State(initialValue: ...) is not the primary Xcode 27 migration guidance now that @State is a macro. Start with Apple’s documented rules: initialize non-state properties first, and do not combine an inline default with an initializer assignment.
How to verify the fix
- Run Product > Clean Build Folder, then rebuild the target.
- Confirm that the diagnostic is gone from every initializer that creates the view.
- Pass a non-default initializer value and check the preview, simulator, or device to confirm that the expected state appears.
- Trigger a parent update that reevaluates the view and verify that the state value or state object is not unexpectedly recreated or overwritten.
- If the project uses multiple configurations, build both Debug and Release.
Cautions
@Staterepresents state owned by the view. It is not a general-purpose mechanism for continuously mirroring a value owned by a parent.- The property name in the diagnostic varies, such as
counterorpage. TN3211 usesself.xas a generic form. - Treating this only as an Xcode bug and staying on an older toolchain leaves the source incompatible with Xcode 27 and later. Updating the initialization pattern is the durable fix documented by Apple.
Summary
variable 'self.x' used before being initialized appears because Xcode 27’s macro-based @State is subject to definite-initialization ordering. Initialize all non-@State stored properties first. If init provides the state value, remove the declaration’s inline default. Finally, verify both the clean build and the state’s runtime value after the view is reevaluated.
Official references
- Apple TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder (first published June 8, 2026)
- Apple WWDC26: What’s new in SwiftUI (the
@Statemacro and initialization example beginning at 23:08)
