The @State Macro: What Xcode 27 Stops Compiling

Apple’s iOS 27 release notes open the @State entry by describing a bug SwiftUI has carried since iOS 13:3 “A @State declared with an expression as its initial value used to evaluate the expression each time the view struct re-instantiates. In the case of @State private var model = Model(), this means Model.init() gets called many times throughout the view’s lifetime.”1

The fix is a rewrite. “Xcode 27 introduces a new @State implementation that avoids this repeated evaluation. This new behavior back-deploys to iOS 17 aligned OSes. The new @State is implemented with a Swift macro. It is largely source compatible with the property wrapper version, with a few exceptions.”1

Read the trigger in that sentence. Apple names Xcode, not a deployment target.

TL;DR

  • Xcode 27 reimplements @State as a Swift macro, and Apple’s symbol pages state the switch in both directions: the State structure page says “When you build with Xcode 27 or later, the system uses the State() macro instead,” and the State() macro page says “When you build with Xcode 26 or earlier, the system uses the State property wrapper instead.”23
  • Your deployment target cannot opt out of the macro. It carries the same iOS 13.0 availability as the property wrapper it replaces, and the switch keys off the Xcode version rather than the target. The runtime half back-deploys only to “iOS 17 aligned OSes,” so projects supporting iOS 15 or 16 get the compile-time change without it.
  • The silent-discard behavior is old and unchanged. Apple writes that it “has not changed because of the macro, but some such cases no longer compile.”1 The macro converts a bug that ate your initializer’s value into a build failure.
  • Two patterns are where compilation breaks: an initializer assigning to a @State property that also carries a declaration-site initial value, and an extension calling the memberwise initializer the compiler synthesizes for an all-private struct.1 Apple writes “some such cases,” not all, and never enumerates which.
  • I audited four shipping apps before writing: 267 @State declarations, 201 of them carrying a declaration-site initial value, zero instances of either breaking pattern, one near miss, and a grep idiom that manufactures false clean results on macOS.6

The entry lives in the iOS and iPadOS 27 release notes under SwiftUI rather than in the Xcode 27 release notes, which carry no matching entry.7 An odd home for a change Apple attributes to Xcode, and a good reason the news will reach people late.

The performance bug Apple fixed

Apple’s description of the old behavior is unusually blunt for a release note. Model.init() “gets called many times throughout the view’s lifetime.”1 SwiftUI re-instantiates view structs constantly, and every re-instantiation re-evaluated the expression to the right of the equals sign. SwiftUI discarded the result, because state that already exists wins, but the work happened anyway.

The macro page states the new contract in one sentence: “A State() property instantiates its default value the first time SwiftUI instantiates the view.”2

Apple’s SwiftUI updates page adds a qualifier the release note omits, and the qualifier is the part worth planning around: “Build your project in Xcode 27 or later so that the @State attribute uses the State() macro to create a state value in an App, Scene, or View. This change only initializes and stores your property once when it’s a class.”4

“When it’s a class” narrows the win considerably, and it points straight at the pattern modern SwiftUI codebases are full of. Storing an @Observable object in @State is Apple’s documented approach, and the macro page’s own example holds an @Observable class Library exactly that way.2 A struct initializer is usually cheap. A class initializer that opens a store, starts a query, or registers an observer is not, and Apple describes it running “many times.”1

What actually changed, and what did not

Apple states the semantics and the compile behavior in a single sentence, and the two halves point in opposite directions: “If you provide an initial value at @State declaration, and also try to assign a value to it in an initializer, the initializer value is discarded. This behavior has not changed because of the macro, but some such cases no longer compile.”1

Nothing about what the code means changed. Under the property wrapper, an initializer that assigned to a @State property carrying a declaration-site value did nothing, silently, and compiled clean. The macro leaves the rule alone and takes away the silence.

The retired failure mode is the expensive one. A developer writes an initializer, threads a title through it, watches the wrong title render, and goes hunting in the view body. The compiler knew all along and had no way to say so.

Apple’s own example carries the two comments that make the point:

struct StickerPageView: View {
    @State private var page = StickerPage()
    let title: String

    init(title: String) {
        // `title` won't have any effect
        // this also won't compile with @State macro
        self.page = StickerPage(title: title)
        self.title = title
    }
}

“Won’t have any effect” and “won’t compile” sit on adjacent lines. The first describes Xcode 26. The second describes Xcode 27. Same code, same meaning, different verdict.

The fix removes the declaration-site value:

struct StickerPageView: View {
    @State private var page: StickerPage // no initial value expression
    let title: String

    init(title: String) {
        self.page = StickerPage(title: title) // works!
        self.title = title
    }
}

Apple reduces the rule to one instruction: “When assigning initial value via an initializer, do not provide an initial value at the @State declaration.”1

So the accurate summary is not that the macro broke initializer assignment. Initializer assignment was already broken, and the macro is the first thing to say so out loud. Anyone framing the change as a regression has the direction backwards, though the practical consequence for a release branch is identical: builds that passed yesterday fail today.

One hedge is worth carrying. Apple wrote “some such cases,” not all, and never enumerates which. A clean build under Xcode 27 is evidence about your code rather than proof about the rule.

The synthesized initializer disappears

The second exception has nothing to do with initial values, and it catches code that never mentions @State at the call site.

“When all stored members of a struct are private, the compiler synthesizes a private init that can be used in an extension of the same type:”1

struct StickerPageView: View {
    @State private var page: StickerPage
    private let title: String
    ...
}

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.init(page: page, title: title) // using the synthesized init
    }
}

“The state macro disables this synthesized initializer. So the code above no longer compiles. To mitigate, assign value to members explicitly:”1

extension StickerPageView {
    init(title: String, _ page: StickerPage) {
        self.title = title
        self.page = page
    }
}

The pattern is nastier to find than the first, because the breaking call site sits in a different declaration from the @State that causes it. Grepping for @State will not surface it.

Apple states the effect and stops there. The macro’s published declaration lines up with the symptom:

@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`_`), prefixed(`__`), prefixed(`$`))
macro State()

An accessor macro supplying get and set changes what the property is from the compiler’s point of view, and memberwise initializer synthesis draws on stored properties.2 Reading the declaration as the cause is my inference rather than Apple’s claim, and the mitigation does not depend on the mechanism: write the assignments out by hand.

Generic inference and property-wrapper composition

Apple gives the remaining exceptions a sentence each, and both deserve a note in a migration checklist even though neither will hit many projects.

Generic inference gets vaguer treatment than anything else in the entry: “In rare situations, the automatic inference of generic arguments of @State is less flexible with the macro implementation. Write the type with more specificity.”1 Apple names no case and offers no diagnostic to look for. The mitigation is an explicit type annotation on the declaration, applied wherever the compiler complains.

Apple states composition as a limit rather than a break: “Composing @State with other property wrappers or macros is not supported.”1 Worth checking if you have ever wrapped @State inside a property wrapper of your own, and worth remembering that Apple frames it as unsupported territory rather than as something that used to work.

No deployment target opts out

Three sentences drawn from three Apple pages close every escape route.

The State() macro symbol carries availability from iOS 13.0, iPadOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, and visionOS 1.0, matching the property wrapper it replaces, so lowering your minimum does not dodge the macro.23 And the switch keys off the compiler, not the target: “When you build with Xcode 27 or later, the system uses the State() macro instead.”3

The runtime half of the change has a floor the compile-time half does not. Apple writes that the new behavior “back-deploys to iOS 17 aligned OSes,” which leaves a gap underneath: a project deploying to iOS 15 or 16 gets the macro at compile time, and with it the source-compatibility exceptions, without the back-deployed runtime behavior.1 Apple does not say what those targets get instead. If you support an OS older than iOS 17, treat the build break as certain and the repeated-initialization fix as unconfirmed on your oldest devices.

Open the project in Xcode 27, build, and you have the new @State. No Info.plist key, no build setting, and no availability check enters the decision.

The 27 cycle carries three breaking changes that developers keep filing under one heading, and they fire at three different moments. The launch screen requirement binds “apps built with the 27.0 SDK or later” and costs you an App Store rejection. The scene life cycle mandate binds apps “built with the latest SDK” and costs you an app that fails to launch. The @State macro binds the Xcode version and costs you a build. Apple phrases the first two against the SDK and the third against the toolchain, which puts @State first in line: you meet it on the first build, before you have touched a plist key or a deployment target.

The pressure to open that toolchain arrives on a published schedule, though not yet for iOS 27. Apple’s requirements page currently reads: “Since April 28, 2026 Apps uploaded to App Store Connect must be built with Xcode 26 or later using an SDK for iOS 26, iPadOS 26, tvOS 26, visionOS 26, or watchOS 26.”5 Apple has published no equivalent date for the iOS 27 SDK. Apple has raised the SDK minimum each recent spring, so a 27 deadline is a reasonable expectation rather than a fact, and any specific date you read elsewhere is inference.

What four shipping apps actually contain

I ran the audit against my own code before writing about anyone else’s: four App Store apps, all SwiftUI, all currently built with Xcode 26.6.6

App Swift files @State declarations With declaration-site value Types declaring @State
Reps 77 120 83 31
Return 57 57 42 14
Ace Citizenship 26 63 54 11
Banana List 55 27 22 8
Total 215 267 201 64

Two commands narrow the work. The first finds @State declarations carrying an initial value, and the character class after @State earns its place: [^A-Za-z0-9_] keeps @StateObject out of the results, which a plain search for @State does not.

grep -rn --include="*.swift" \
  --exclude-dir=.build --exclude-dir=DerivedData --exclude-dir=build \
  -E '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' .

The command assumes the attribute and the declaration share a line. A property written with @State on its own line above private var page = StickerPage() produces no match, which is the same false-clean failure described below in a different costume. Read the misses as “not yet checked” rather than “clean.”

The second narrows to files that also declare an initializer, the only place the first exception can bite:

find . -name "*.swift" -not -path "*/build/*" -not -path "*/DerivedData/*" -print0 |
while IFS= read -r -d '' f; do
  grep -qE '@State[^A-Za-z0-9_][^=]*[^!<>=]=[^=]' "$f" || continue
  grep -qE '^[[:space:]]*(private |public |internal |fileprivate )?init[[:space:]]*[(<]' "$f" || continue
  echo "$f"
done

The loop looks heavier than piping into xargs, and it earns the weight on macOS. BSD grep does not emit NUL separators for -Z the way GNU grep does, so grep -rlZ ... | xargs -0 grep -l hands the second grep one newline-joined blob. On a project whose path contains a space, Banana List for instance, you get a screen of “No such file or directory” on standard error and nothing at all on standard output, which reads exactly like a clean audit.6 Reading empty output as “no matches” gets the answer backwards on the projects most likely to need it.

Across 215 Swift files the shortlist came to 20: nine in Reps, six in Return, three in Ace Citizenship, and two in Banana List. Reading all 20 by hand produced the same result in all four projects.

  • Apple’s exact first shape, a declaration-site initial value plus an assignment to that same property inside an initializer of the same type: zero.
  • The second exception, an extension calling the synthesized memberwise initializer of a struct that declares @State: zero.
  • The composition exception, @State alongside another property wrapper or macro on one declaration: zero.

One near miss deserves description, because it sits one line away from the shape Apple tells you to remove. A profile setup view in Reps declares @State private var healthWriteStatus: HealthAuthorizationState = .notRequested and then, inside its initializer, assigns self._healthWriteStatus = State(initialValue: ...). Both places carry a value, so Apple’s mitigation sentence applies word for word: do not provide an initial value at the @State declaration.1 The assignment uses the underscore form rather than the wrapped-value form in Apple’s example, and Apple’s release note never mentions the underscore form.

Which leaves the question my audit could not answer. The _x = State(initialValue:) idiom appears 15 times across three of the four apps, and it remains the standard way to seed state from an initializer parameter. Apple’s entry does not address it. The macro declaration does generate a peer prefixed with an underscore,2 which is suggestive and not a guarantee. I build on Xcode 26.6 (build 17F113), so I could verify neither the idiom nor the text of the resulting compile errors.6 Anyone holding an Xcode 27 beta can settle both in an afternoon. Until someone does, search your code for the declaration shape rather than for an error string.

The honest headline from 267 declarations is that most SwiftUI code passes untouched, which matches Apple’s “largely source compatible” framing.1 The views at risk are the ones with hand-written initializers, and they cluster hard: fewer than one Swift file in 10 across four codebases held both a hand-written initializer and a @State carrying its own initial value.

FAQ

Do I need to change _page = State(initialValue:) calls?

Apple’s entry does not say. The underscore form assigns the projected storage directly instead of the wrapped value, and it appears nowhere in the release note: no initialValue, no underscore example, no mention either way.1 The macro’s published declaration does emit a _-prefixed peer, which is suggestive and not a guarantee.2 Three of the four apps I audited use the idiom, 15 times in total, so the answer matters to more codebases than the two documented exceptions do.6 Until Apple addresses it, build the project under Xcode 27 and let the compiler answer rather than refactoring on speculation.

Did the macro change what happens to a value assigned in an initializer?

No. Apple is explicit that the discard behavior “has not changed because of the macro, but some such cases no longer compile.”1 Under Xcode 26 an initializer assigning to a @State property that already carried a declaration-site value did nothing and compiled. Under Xcode 27 some of those cases fail to build. The semantics stayed put and the diagnostics arrived, so a build breaking here is the compiler reporting a bug you already had.

How do I find the at-risk code in my project?

Search for @State declarations carrying an initial value, restrict the results to files that also declare an initializer, then read that shortlist by hand. Exclude @StateObject with a character class (@State[^A-Za-z0-9_]), and prefer a find -print0 loop over grep -rlZ | xargs -0 on macOS, where BSD grep emits no NUL separators and any path containing a space produces empty output that looks like a pass.6 Across four apps and 215 Swift files the shortlist came to 20 files. Search separately for extensions that call self.init(...) on a view struct, since the second exception leaves no trace near the @State that causes it.

Will my app actually get faster?

Only in specific circumstances, and Apple narrows them further than the release note suggests. The release note describes avoiding repeated evaluation in general,1 while the SwiftUI updates entry says the change “only initializes and stores your property once when it’s a class.”4 The win lands on @State private var model = SomeObservableClass(), where the old implementation ran the class initializer on every view re-instantiation and threw the result away. A @State private var isPresented = false was never the problem.

Key Takeaways

For iOS developers: - Audit for two shapes rather than one. The first lives on a @State declaration next to an initializer; the second lives in an extension calling self.init(...) on a view struct whose stored members are all private.1 - Fix the first by deleting the declaration-site value, not by deleting the initializer assignment. Apple’s instruction is to keep the initializer as the single source and leave the declaration bare.1

For teams maintaining older SwiftUI code: - Budget review time against the shortlist, not the codebase. Files holding both a @State with an initial value and a hand-written initializer were 20 of 215 in my four apps, and every instance of the first pattern has to live in one of them. Search for the second pattern separately, since it hides in extensions.6 - Treat a broken build as a found bug. SwiftUI was already discarding the initializer value your compiler now rejects, so anywhere the change bites, some behavior was already wrong.1

For release managers: - Schedule the @State audit ahead of the SDK-triggered work in the same cycle. The launch screen key and the scene life cycle both bind the SDK you build against; @State binds the Xcode you build with, so it lands first.13 - Do not plan against an iOS 27 SDK deadline. Apple’s published minimum is still the iOS 26 SDK, required since April 28, 2026, and Apple has announced nothing for 27.5


Three enforcement points ship in one cycle and fail in three different places: the launch screen key stops a submission, the scene mandate stops a launch, and the @State macro stops a build. For the rest of what lands in the same SDK, see What’s New in SwiftUI for iOS 27. The full series hub is the Apple Ecosystem Series.

References


  1. Apple, iOS & iPadOS 27 Release Notes, SwiftUI section, New Features (radar 105893279). Source for the description of the old behavior (“A @State declared with an expression as its initial value used to evaluate the expression each time the view struct re-instantiates. In the case of @State private var model = Model(), this means Model.init() gets called many times throughout the view’s lifetime”), the new implementation (“Xcode 27 introduces a new @State implementation that avoids this repeated evaluation. This new behavior back-deploys to iOS 17 aligned OSes. The new @State is implemented with a Swift macro. It is largely source compatible with the property wrapper version, with a few exceptions”), the first exception and its instruction (“If you provide an initial value at @State declaration, and also try to assign a value to it in an initializer, the initializer value is discarded. This behavior has not changed because of the macro, but some such cases no longer compile” and “When assigning initial value via an initializer, do not provide an initial value at the @State declaration”), both StickerPageView code listings for the first exception, the second exception (“When all stored members of a struct are private, the compiler synthesizes a private init that can be used in an extension of the same type” and “The state macro disables this synthesized initializer. So the code above no longer compiles. To mitigate, assign value to members explicitly”) with its two code listings, the generic inference note (“In rare situations, the automatic inference of generic arguments of @State is less flexible with the macro implementation. Write the type with more specificity”), and the composition note (“Composing @State with other property wrappers or macros is not supported”). All code listings reproduced verbatim. Verified against Apple’s documentation JSON on July 25, 2026, since the HTML page renders its content through JavaScript. 

  2. Apple, State() macro, SwiftUI macro reference. Source of the published declaration (@attached(accessor, names: named(init), named(get), named(set)), @attached(peer, names: prefixed(_), prefixed(__), prefixed($)), macro State()), the availability list (iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, macOS 10.15, tvOS 13.0, visionOS 1.0, watchOS 6.0), the toolchain aside (“When you build with Xcode 26 or earlier, the system uses the State property wrapper instead”), the new initialization contract (“A State() property instantiates its default value the first time SwiftUI instantiates the view”), and the “Store observable objects” example holding an @Observable class Library in @State

  3. Apple, State, SwiftUI structure reference. Still declared @frozen @propertyWrapper struct State<Value> with availability from iOS 13.0, iPadOS 13.0, Mac Catalyst 13.0, macOS 10.15, tvOS 13.0, visionOS 1.0, and watchOS 6.0. Source of the toolchain aside pointing the other way: “When you build with Xcode 27 or later, the system uses the State() macro instead.” 

  4. Apple, SwiftUI Updates, June 2026, General. Source of the class qualifier: “Build your project in Xcode 27 or later so that the @State attribute uses the State() macro to create a state value in an App, Scene, or View. This change only initializes and stores your property once when it’s a class.” 

  5. Apple, Upcoming requirements, Apple Developer News. Source of the current SDK minimum: “Since April 28, 2026 Apps uploaded to App Store Connect must be built with Xcode 26 or later using an SDK for iOS 26, iPadOS 26, tvOS 26, visionOS 26, or watchOS 26.” Checked July 25, 2026; the page lists no requirement referencing the iOS 27 SDK. 

  6. Author’s audit of four shipping SwiftUI apps (Reps, Return, Ace Citizenship, and Banana List) on macOS 26.5.2 with Xcode 26.6 (build 17F113), July 25, 2026. Counts come from a script that parses each type declaration by brace depth and scopes initializer bodies within it, cross-checked against the grep command published above, which reproduced the per-project declaration-site counts exactly in all four projects (83, 42, 54, and 22). The BSD grep behavior was confirmed directly: on macOS, grep -rlZ emits newline-separated output rather than NUL-separated output, so xargs -0 receives a single joined argument and the pipeline fails with “No such file or directory” on any path containing a space. The _x = State(initialValue:) idiom count (15 occurrences across Reps, Return, and Banana List) comes from the same pass. Behavior under Xcode 27 was not tested, and no compile-error text is reported here, because Xcode 27 was not installed on the machine used. 

  7. Apple, Xcode 27 Release Notes. Searched for radar 105893279 and for any entry describing the @State macro on July 25, 2026; neither appears. The only @State mention is an unrelated MusicKit fix (radar 176947544). 

Verwandte Beiträge

The iOS 27 Launch Screen Rule: Four Keys or Rejection

Apps built with the iOS 27 SDK must declare a launch screen or the App Store rejects them. The four keys, and how to aud…

16 Min. Lesezeit

UIKit's Scene Mandate: What Fails to Launch on iOS 27

Apps built with the iOS 27 SDK must adopt the UIKit scene-based life cycle or they fail to launch. The timeline, the mig…

10 Min. Lesezeit