Research snapshot: July 22, 2026
Locally verified with: Xcode 27 beta build 27A5194q, Apple Swift 6.4 (swiftlang-6.4.0.20.104), iOS 27.0 SDK
Scope: the 12 unique X posts supplied for this research, the related Xcode 27 @State migration discussed immediately before them, a July 22 inventory pass across all 87 posts then listed in Blake Crosley's Apple Ecosystem series (published April 28 through June 15, 2026), and authenticated Grok searches for follow-up X discussion across the resulting topic clusters. The repeated X link 2076630236222124076 is counted once. X and the series were used as discovery material; migration claims below are promoted to Confirmed only when Apple material or a reproducible SDK/compiler result supports them.
This guide distinguishes three evidence levels:
- Confirmed: Apple documentation, Apple WWDC material, the exported Apple-authored Xcode 27 coding skills, or a reproducible SDK/compiler result.
- Beta discrepancy: Apple has documented the intended behavior, but the current beta SDK, diagnostics, or reference page does not fully agree yet.
- Reverse-engineered / private: an observation about private framework implementation. It is useful for testing and understanding behavior, but is not an API contract and must not become a shipping dependency.
- Secondary / not independently confirmed: useful analysis or a paraphrased lab finding for which no public Apple transcript or stable contract was found. Treat it as a hypothesis to measure, not a migration rule.
- Build every target with Xcode 27 and treat new warnings as migration work, even if the deployment target remains older.
- For UIKit targets, verify scene-lifecycle adoption before anything else. An app built with the 27 SDK that remains on the app-only lifecycle does not launch.
- Verify every distributable iOS/iPadOS target has a launch-screen key; App Store Connect rejects 27-SDK uploads without one.
- Audit custom initializers for
@Stateproperties that also have declaration defaults. Remove the redundant declaration default when the initializer supplies the initial value. - Audit class-valued
@Statefor initializer side effects. Xcode 27 makes class construction lazy and once-per-view-lifetime. - Ensure every bound
TabViewselection identifies a visible, available tab before changing tab visibility. - Replace
canOpenURL(_:)preflight logic with an attempted open and completion handling; reduceLSApplicationQueriesSchemesto no more than 25 entries for apps linked on or after iOS 27. - Handle errors inside discarded throwing
Taskclosures, or retain and observe the task result. - Audit lazy stacks for unstable absolute-offset logic, dynamic leaf subview counts, layout-changing
onAppear, and row state that must outlive scrolling. - In UIKit/AppKit views, evaluate Observation tracking before retaining manual redraw plumbing; back-deployment requires the documented Info.plist opt-in.
- Add
.swipeActionsContainer()around custom scrollable row layouts that use.swipeActionsoutsideList. - Test iPad custom chrome and sidebar content in inactive windows; use
@Environment(\.appearsActive)only for custom elements the system cannot style automatically. - If a UIKit tab architecture should prefer a sidebar on supported iPhone layouts, gate
tabBarController.sidebar.preferredPlacement = .sidebarto iOS 27. - Test continuous resizing and all orientation configurations on the current seed; Beta 4 still documents several iPad/iPhone Mirroring defects.
- Add a small XCUITest VoiceOver suite for critical reading order and spoken output using Xcode 27's
XCUIVoiceOverService; keep per-screen accessibility audits as a separate layer. - If the app uses
ImageCreator, migrate before release: the iOS 27 SDK deprecates it withImagePlaygroundViewControllerorimagePlaygroundSheetas the user-driven replacements. - If the app uses On Demand Resources, plan a move from
NSBundleResourceRequestto Background Assets. - Evaluate the new SwiftUI reorder, document, toolbar-overflow, item-presentation, and
AsyncImage(request:)APIs where they delete custom infrastructure; availability-gate them for older deployment targets. - Apply the six Apple-authored SwiftUI performance rules covered below: stable environment defaults, unary rows, Equatable observable property types where practical, prepared
ForEachdata, narrow value inputs, and real subview boundaries. - Remove any dependency on
_UIScrollEdgeEffectViewInteraction,updatePocket(...), or other underscored scroll-pocket implementation details.
| Supplied source | Claim | Disposition |
|---|---|---|
Vistar: canOpenURL and scheme limit |
canOpenURL(_:) deprecated; query-scheme cap reduced to 25 |
Confirmed in Apple docs/release notes, with a beta SDK annotation discrepancy |
| Julien Sagot: scroll-pocket hook | Private interaction continues receiving pocket geometry callbacks | Reverse-engineered/private; not a supported migration API |
Vincent: class default in @Entry |
Avoid an inline fresh class default in the environment | Confirmed by the Apple-authored Xcode 27 SwiftUI Specialist skill |
Vincent: top-level row if/else |
Avoid branch-dependent top-level row shapes | Confirmed by the Apple-authored skill |
| Vincent: non-Equatable observable property | Prefer Equatable property types in @Observable models |
Confirmed by the Apple-authored skill and Swift 6.4 macro expansion |
| Vincent: iPhone tab-to-sidebar placement | UIKit can prefer a sidebar where only tab bar or sidebar can fit | Confirmed beta API |
Vincent: expensive work in ForEach |
Avoid scalable transforms in the ForEach data expression |
Confirmed by the Apple-authored skill |
| Vincent: unnecessary value-type view inputs | Pass only the value fields a view uses | Confirmed by the Apple-authored skill, with reference-type nuance |
| Vincent: computed properties used to split views | Extract real View types to create invalidation boundaries |
Confirmed by the Apple-authored skill, with a small-fragment exception |
| Vincent: inactive iPad appearance | iPad apps gain inactive-window appearance | Confirmed by WWDC26; public reference-page text currently lags |
Vincent: discarded throwing Task warning |
Swift warns when an unobserved task can discard thrown errors | Confirmed by Swift 6.4 compiler behavior |
Vincent: swipe actions outside List |
Swipe actions work in custom scrollable containers | Confirmed beta API |
Apple states that Xcode 27 includes a SwiftUI Specialist skill and a What's New in SwiftUI skill, exportable with xcrun agent skills export. The six best-practice findings in this guide were checked against a fresh export from Xcode 27, not inferred from the screenshots alone. See What's new in SwiftUI, WWDC26.
The follow-up searched the topics rather than individual account timelines so that adjacent reports could surface. It produced a small number of useful leads; each was then checked against a primary source.
| X discovery | What survived verification |
|---|---|
Arturo Rivas linking a SwiftPills @State explainer |
The article correctly demonstrates throwaway pre-27 class allocations and the new lazy behavior, but adds no contract beyond Apple's release notes and TN3211. |
Daniel Saidi on the reorder sample's missing apply |
Novel and confirmed: ReorderDifference.apply(to:) is absent from the locally installed beta SDK. Apple's WWDC sample defines the helper in app code and imports Apple's open-source swift-collections; see the correction in section 12. |
Mysk on Beta 4 canOpenURL |
Corroborates the supplied post; Apple's Beta 4 release notes now explicitly carry the deprecation and replacement guidance. |
| Leandro Rocha on mandatory scenes | Confirmed by TN3187 and Apple's SDK-linking clarification. |
| Jacob Bartlett on Instruments 27 launch-time analysis | A useful flame-graph workflow, not a source migration rule; it is included in the verification tooling discussion. |
| Julien Sagot on ScrollPocket v8 | Further private implementation evidence only. It does not change the public-API conclusion in section 8. |
Targeted searches for the reported @State premature-deinitialization case, variadic-generic metadata crash, @ContentBuilder regressions, and SwiftData observer APIs did not produce additional reproducible X evidence. They are therefore not promoted beyond Apple-documented behavior or explicit beta caveats.
Status: Confirmed. Apple documents the change in SwiftUI updates, explains it in TN3211, and demonstrates it in What's new in SwiftUI.
When code is built with Xcode 27, @State has a same-named attached macro layer. For a class-valued declaration such as:
@MainActor
@Observable
final class StickerStore {}
struct StickerStoreView: View {
@State private var store = StickerStore()
var body: some View {
StickerList(store: store)
}
}the initializer expression can be captured and evaluated lazily. Apple describes the user-visible result as one class construction for the lifetime of the view's state identity, instead of a throwaway allocation whenever Swift recreates the lightweight View value.
This behavior back-deploys to the releases where Observation was introduced, beginning with iOS 17 and macOS 14, when the app is built with Xcode 27. It is a compiler/framework change, not a reason to raise the deployment target by itself.
The local macro expansion for @State private var model = Model() generated storage shaped like:
private var __model = SwiftUICore.State._makeStorage {
Model()
}along with compatibility peers for _model and $model. LazyState is an implementation detail selected behind _makeStorage; do not spell or depend on it in app code.
This old pattern is now invalid:
struct StickerPageView: View {
@State private var page = StickerPage()
let title: String
init(title: String) {
page = StickerPage(title: title)
self.title = title
}
}It can diagnose that self.title is used before initialization because assigning the macro-generated state property uses self.
Do not merely reorder the assignments:
// Wrong migration: compiles, but the declaration initializer still wins.
init(title: String) {
self.title = title
page = StickerPage(title: title)
}In a local Xcode 27 runtime probe, the analogous reordered Int state returned the declaration value 0, not the initializer assignment 42.
Remove the declaration default when the initializer owns initialization:
struct StickerPageView: View {
@State private var page: StickerPage
let title: String
init(title: String) {
page = StickerPage(title: title)
self.title = title
}
}- Search for
@Statedeclarations with both=and assignment ininit. - If
initsupplies the value, remove the declaration default and initialize exactly once. - Keep view-owned
@Observablereference models in private@State. - Do not operate on a state-owned object from the surrounding
View.init. Start lifecycle work from.task, an explicit user action, or another rendered-view lifecycle boundary. - Do not put analytics, registration, detached tasks, file access, or other externally visible side effects in a state initializer. Laziness can change when and how often those effects occur.
- Remember that “once” means once for the retained SwiftUI state identity. Removing the view or changing an explicit
.id(...)can create a new lifetime and a new object.
Status: Confirmed intent and limit; beta SDK discrepancy. Sources: supplied X post, Apple canOpenURL(_:) documentation, and iOS & iPadOS 27 release notes.
Apple's current documentation says:
canOpenURL(_:)is deprecated in the 27 releases.- Attempt the open and handle failure instead of preflighting.
- Universal Links remove the need for custom-scheme validation in many cases.
- Apps linked on or after iOS 27 may list at most 25 schemes in
LSApplicationQueriesSchemes.
The iOS 27 beta SDK in Xcode build 27A5194q still declares Objective-C canOpenURL: as API_AVAILABLE(ios(3.0)), and a Swift source probe produced no deprecation warning. Treat this as beta annotation lag, not evidence that the deprecation was withdrawn: both the Apple reference page and the release notes declare the migration direction.
Before:
if UIApplication.shared.canOpenURL(destination) {
UIApplication.shared.open(destination)
} else {
showUnavailableMessage = true
}After:
UIApplication.shared.open(destination, options: [:]) { succeeded in
if !succeeded {
showUnavailableMessage = true
}
}In SwiftUI, prefer the openURL environment action where it fits the UI architecture:
@Environment(\.openURL) private var openURL
Button("Open destination") {
openURL(destination) { accepted in
if !accepted {
showUnavailableMessage = true
}
}
}- Count the final built app's
LSApplicationQueriesSchemes, not only one source plist; build settings and generated plists can merge values. - Remove probes used only to customize UI based on whether another app is installed.
- Prefer Universal Links, system share sheets, document pickers, and platform routing.
- If a custom scheme is still required, attempt it and provide a fallback destination or a clear failure state.
- Test apps linked with the iOS 27 SDK. The 25-entry rule is link-SDK dependent, not simply the device OS version.
Status: Confirmed. Sources: supplied X post and the compiler's linked diagnostic, NoUseUnstructuredThrowingTask.
This code now warns by default:
Task {
try await lander.fly(to: moon)
}The Xcode 27 compiler emitted:
unstructured throwing task created by 'init(name:priority:operation:)'
is not used, which may accidentally ignore errors thrown inside the task
Handle the error inside fire-and-forget work:
Task {
do {
try await lander.fly(to: moon)
} catch is CancellationError {
// Expected if the owning lifecycle cancels the work.
} catch {
logger.error("Flight failed: \(error)")
}
}Or retain and observe structured results:
let flight = Task {
try await lander.fly(to: moon)
}
try await flight.valueAn explicit discard such as _ = Task { ... } silences the diagnostic, but use it only when deliberately ignoring both the task handle and failure. In SwiftUI, .task is often a better lifecycle boundary because SwiftUI cancels it when the view disappears; thrown non-cancellation errors still need an intentional policy.
Status: Confirmed beta API. Sources: supplied X post, swipeActionsContainer() documentation, SwiftUI updates, and WWDC26.
The row keeps the existing .swipeActions modifier. The custom container gains .swipeActionsContainer():
struct StickerList: View {
@State private var stickers: [Sticker]
var body: some View {
ScrollView {
LazyVStack {
ForEach(stickers) { sticker in
StickerRow(sticker: sticker)
.swipeActions {
Button("Delete", role: .destructive) {
stickers.removeAll { $0.id == sticker.id }
}
}
}
}
}
.swipeActionsContainer()
}
}The container coordinates mutual exclusion and dismissal: one row open at a time, close on scrolling, and close when tapping elsewhere. Without it, .swipeActions outside List has no effect. Adding it to List is a no-op because List already supplies the coordination.
Availability:
| API | iOS | macOS | watchOS | visionOS | tvOS |
|---|---|---|---|---|---|
swipeActionsContainer() |
27 | 27 | 27 | 27 | unavailable |
Existing row .swipeActions |
15 | 12 | 8 | 1 | unavailable |
New onPresentationChanged overload |
27 | 27 | 27 | 27 | unavailable |
Gate the container modifier when supporting older OS versions, or isolate the new layout in an @available view. Test full-swipe behavior, VoiceOver actions, right-to-left layout, nested horizontal gestures, row identity, and grid layouts.
Status: Confirmed by Apple WWDC material; public reference text currently lags. Sources: supplied X post, WWDC26 inactive-appearance demonstration, and appearsActive.
iPadOS 27 automatically gives an app a distinct inactive appearance when another window becomes active. Standard SwiftUI controls, labels, selection, and materials generally adapt without app code.
Use appearsActive for custom content that does not inherit the correct system treatment:
struct SidebarFooterView: View {
@Environment(\.appearsActive) private var appearsActive
var body: some View {
MyAccountView()
.opacity(appearsActive ? 1 : 0.5)
}
}Do not manually dim the whole app. Prefer semantic colors and materials, then adjust only custom chrome that remains visually overemphasized.
The current appearsActive reference page still describes non-macOS platforms as always returning true, while Apple's WWDC26 transcript explicitly demonstrates and recommends it for inactive iPad windows. This appears to be stale beta documentation. Test behavior on the latest iPadOS 27 seed and re-check the page before final release.
Audit custom sidebars, selection indicators, destructive controls, Metal/canvas content, toolbars, and custom glass. Also keep scene lifecycle separate from appearance: .inactive scene phase and appearsActive == false are related concepts but are not interchangeable ownership or work-cancellation signals.
Status: Confirmed beta API. Sources: supplied X post, preferredPlacement, and Sidebar.Placement.
UIKit adds an iOS 27 UITabBarController.Sidebar.preferredPlacement property for contexts where the tab bar and sidebar are mutually exclusive. The default .automatic resolves to a tab bar on iOS. Apps can prefer .sidebar when the context supports it:
let controller = UITabBarController()
controller.tabs = makeTabs()
controller.mode = .tabSidebar
if #available(iOS 27, *) {
controller.sidebar.preferredPlacement = .sidebar
}Important boundaries:
- This is UIKit API, available in iOS 27 and visionOS 27 and unavailable on tvOS.
.sidebarmeans “display the sidebar when supported,” not “force a sidebar at every width.”- The property does not create a useful navigation hierarchy by itself. Configure tabs/groups and a tab-sidebar mode that can be represented as either placement.
- On platforms that can show multiple placements simultaneously, such as iPadOS, Apple says this preference has no effect.
- Preserve destination selection independently of whether the system currently renders tabs or a sidebar.
- Test size changes without destructively resetting navigation or user customization.
Do not infer a specific future device from the API. The supported conclusion is that UIKit navigation is becoming more adaptive to available presentation context.
These are not iOS 27 runtime APIs. They are current best practices shipped in Xcode 27's Apple-authored SwiftUI Specialist skill. Apple introduced the skill publicly in What's new in SwiftUI. They can improve apps that deploy to older OS releases as well.
Source: supplied X post.
Status: Confirmed.
@Entry wraps its default expression in a computed getter. Therefore a fresh class default is recreated on every fallback read:
// Avoid: a fresh instance on every fallback read.
extension EnvironmentValues {
@Entry var theme = Theme()
}Any environment update can cause readers to reread their keys. A different reference then looks like a changed environment value, invalidating readers even though the relevant dependency did not change. Different readers also receive different fallback objects.
Choose one stable shape:
// A. Keep @Entry, back it with one stable instance.
extension EnvironmentValues {
@Entry var theme = _defaultTheme
private static let _defaultTheme = Theme()
}// B. Use a manual key with a stored default.
private struct ThemeKey: EnvironmentKey {
static let defaultValue = Theme()
}
extension EnvironmentValues {
var theme: Theme {
get { self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}// C. Model the absence honestly.
extension EnvironmentValues {
@Entry var theme: Theme?
}Use the optional form when a missing injection is meaningful. Do not “fix” the problem by giving the class a permissive Equatable implementation; that can mask invalidation while still constructing new objects and running initializer side effects.
Nuance: not every inline value default is unstable. Literals, nil, fixed enum cases, and structs whose fields resolve to the same values or stable references are fine. The deciding question is whether evaluating the expression again can produce a different result. Date(), UUID(), random values, and fresh nested references are unstable.
Source: supplied X post.
Status: Confirmed.
A top-level if/else or switch can make a row's structural identity branch-dependent. A lazy container may then evaluate every row body just to establish the full identity set.
// Avoid: branch-dependent top-level shape.
List(items) { item in
if item.isHighlighted {
Label(item.title, systemImage: "star.fill")
} else {
Text(item.title)
}
}Wrap the branch in a real single-root container:
List(items) { item in
VStack {
if item.isHighlighted {
Label(item.title, systemImage: "star.fill")
} else {
Text(item.title)
}
}
}Group is a passthrough, not a layout container, and does not make the row unary. If the intent is to omit elements entirely, filter upstream rather than returning a zero-view row. Avoid AnyView for the same reason: it erases structural identity.
Source: supplied X post.
Status: Confirmed by the Xcode skill and Swift 6.4 macro expansion.
The current Observation macro generates overloads conceptually equivalent to:
func shouldNotifyObservers<T>(_ old: T, _ new: T) -> Bool { true }
func shouldNotifyObservers<T: Equatable>(_ old: T, _ new: T) -> Bool { old != new }
func shouldNotifyObservers<T: AnyObject>(_ old: T, _ new: T) -> Bool { old !== new }For a non-Equatable value type, every assignment notifies observers. An Equatable type can suppress redundant sets:
struct Coordinate: Equatable {
var latitude: Double
var longitude: Double
}
@MainActor
@Observable
final class LocationModel {
var coordinate = Coordinate(latitude: 0, longitude: 0)
}This matters most for frequently assigned polling, streaming, timer, or synchronization values. It is a preference, not a command to force dubious equality onto every type. Collections become Equatable only when their elements do. Reference-typed properties already have an identity comparison path; make a reference type Equatable only when it has a sound domain equality definition.
Source: supplied X post.
Status: Confirmed.
This repeats filtering and sorting on every body evaluation:
ForEach(
contacts
.filter { $0.name.localizedCaseInsensitiveContains(query) }
.sorted { $0.name < $1.name }
) { contact in
ContactRow(contact: contact)
}Expose prepared results from the model and recompute when actual inputs change:
@MainActor
@Observable
final class ContactsModel {
var contacts: [Contact] = [] { didSet { search() } }
var query = "" { didSet { search() } }
private(set) var results: [Contact] = []
private func search() {
results = contacts
.filter { $0.name.localizedCaseInsensitiveContains(query) }
.sorted { $0.name < $1.name }
}
}Cheap bounded transformations such as prefix(n) on a small collection are fine. The rule targets work that scales with collection size, allocates new elements, or performs I/O/formatting.
Source: supplied X post.
Status: Confirmed, with reference-type nuance.
For a value-type input, the stored parameter list is the view's invalidation and comparison surface:
// Avoid when only the name is used.
struct NameTag: View {
let account: Account
var body: some View { Text(account.name) }
}struct NameTag: View {
let name: String
var body: some View { Text(name) }
}This reduces unrelated invalidations and avoids deep comparisons of large decoded value graphs. Forwarding a field to a child counts as using it.
Do not apply the rule mechanically to @Observable class references. SwiftUI compares a class by identity, while Observation tracks individual property reads during body. Passing one stable observable model can be cheaper and more precise than copying a large value payload through the view tree.
Source: supplied X post.
Status: Confirmed.
Computed some View properties improve readability but remain inside the parent's invalidation boundary:
struct ProfileView: View {
var body: some View {
VStack {
header
details
}
}
private var header: some View { /* ... */ }
private var details: some View { /* ... */ }
}Use separate view types for meaningful sections, with narrow inputs:
struct ProfileView: View {
var body: some View {
VStack {
ProfileHeader(name: name)
ProfileDetails(isExpanded: isExpanded)
}
}
}A separate View gives SwiftUI a distinct unit it can skip when inputs have not changed. Tiny static fragments reused a few times, styling expressions, and conveniences with no independent invalidation story can remain computed properties. The rule is aimed at section-level decomposition and performance boundaries, not eliminating every helper.
Status: Reverse-engineered/private beta behavior. Source: supplied X post.
The post reports that private _UIScrollEdgeEffectViewInteraction remains installed even when no pocket is visible and receives a private callback shaped like:
updatePocket(_:contentRect:velocity:isTracking:shouldAnimateVisibility:)
That observation may explain how the system coordinates content geometry, velocity, tracking, and visibility. It does not establish a contract. The leading underscore, absence from public SDK headers, and method-hooking approach all make it unsuitable for App Store code. The alleged internal “ScrollPocket v8” rewrite is likewise a beta implementation finding, not an announced platform API.
UIKit publicly provides iOS 26-era scroll-edge APIs that remain available in the iOS 27 SDK:
UIScrollEdgeEffectthroughtopEdgeEffect,bottomEdgeEffect,leftEdgeEffect, andrightEdgeEffectonUIScrollView.- Public effect styles such as automatic, soft, and hard.
UIScrollEdgeElementContainerInteractionfor a container of controls or glass elements that overlays a scroll edge and should affect the effect's shape.
Example:
scrollView.topEdgeEffect.style = .soft
let interaction = UIScrollEdgeElementContainerInteraction()
interaction.scrollView = scrollView
interaction.edge = .bottom
buttonContainer.addInteraction(interaction)- Do not swizzle, hook, instantiate, cast to, or inspect underscored pocket classes in production.
- Do not use private callback signatures as selectors or reflection keys.
- If an iOS 26 visual recreation changed under iOS 27, rebuild it from public edge-effect APIs and observable scroll state, or accept the system's current rendering.
- Keep screenshot-diff investigations and hierarchy inspection in test/debug tooling only.
- Re-test each beta. Internal behavior can change without deprecation or release-note coverage.
Status: Confirmed by Apple. Secondary synthesis: SwiftUI Performance and Interop in iOS 27. Primary source: Dive into lazy stacks and scrolling with SwiftUI — WWDC26.
This is not an iOS 27-only API change. It is Apple's current explanation of behavior that can expose existing problems when an app is rebuilt, resized, or measured with newer tools.
A LazyVStack lays out only enough content to fill the visible rect. It estimates off-screen heights from the average of views already placed and the expected remaining count. Its enclosing scroll view therefore has an estimated content size and content offset that are corrected as more rows are measured. An orientation or size change can make those corrections especially visible.
Do not drive important UI from an exact absolute offset when the content is lazy:
// Fragile: the 100-point threshold can drift as estimates settle.
.onScrollGeometryChange(for: Bool.self) { geometry in
geometry.contentOffset.y <= 100
} action: { _, isNearTop in
showsJumpButton = isNearTop
}Prefer a relative signal tied to actual visible targets:
ScrollView {
LazyVStack {
ForEach(steps) { step in
StepRow(step: step)
.id(step.id)
}
}
.scrollTargetLayout()
}
.onScrollTargetVisibilityChange(
idType: Step.ID.self,
threshold: 0.8
) { visibleIDs in
showsJumpButton = steps.first.map { visibleIDs.contains($0.id) } ?? false
}Also audit scrollTransition: a transform that paints a row far outside its original layout frame can conflict with lazy culling. The lazy container decides visibility from the reported layout position, not every pixel the transformed view eventually paints.
The lazy container works with resolved subviews, not merely the number of View structs in source. A ForEach leaf that conditionally resolves to zero or one child forces the stack to retain earlier view values to preserve index meaning:
// Avoid filtering inside a repeated leaf.
ForEach(steps) { step in
if step.detailLevel <= selectedDetailLevel {
StepRow(step: step)
}
}Filter the data before construction, or express the filter in a SwiftData query predicate:
let visibleSteps = steps.filter {
$0.detailLevel <= selectedDetailLevel
}
ForEach(visibleSteps) { step in
StepRow(step: step)
}This is related to, but more specific than, the unary-row rule in section 7.2. It improves memory release, off-screen invalidation behavior, and programmatic scroll-to-ID performance. Optional unwrapping that returns no row has the same structural cost; handle missing prerequisites above the lazy collection when possible.
Lazy stacks may evaluate a row body and perform layout during spare frame time before the row appears. If the user reverses direction, body can run without onAppear ever running. A row that changes most of its content or size from onAppear discards that work and forces new layout at the frame deadline.
Use these boundaries:
- Put immutable, synchronous presentation setup in the row initializer or prepared row model.
- Keep row construction cheap and side-effect free. “Set up in the initializer” does not authorize network access, analytics, or touching
@Statefrom a transientView.init. - Let an owned model/cache start idempotent, cancellable loading when that model is created, or use
.task(id:)for lifecycle-bound asynchronous work. - Keep
onAppearfor genuinely appearance-bound signals such as requesting the next page at the end of an infinite list. - Do not use
onGeometryChangeto write a size into state and then change the same row's layout if a customLayoutcan express the dependency in one pass.
Lazy rows and their @State can be released after scrolling away. State that must survive that must live in an outer model or data source:
// Transient UI state is fine here.
@State private var isPressed = false
// Durable state belongs in the model, keyed by step ID.
@Bindable var progress: ReadingProgressSearch for LazyVStack, LazyHStack, onScrollGeometryChange, onAppear, onGeometryChange, scrollTransition, scrollTo, and conditional or optional content directly inside repeated leaves. Re-test with variable-height rows, rotation/resizing, fast reverse scrolling, programmatic jumps near the end, and enough rows to force real lazy behavior.
Status: Confirmed by Apple and present in the SDK. Secondary synthesis: SwiftUI Performance and Interop in iOS 27. Primary source: Use SwiftUI with AppKit and UIKit — WWDC26 and Apple's UIKit observation-tracking documentation.
UIKit and AppKit can automatically track properties read from @Observable models in supported drawing, layout, constraint, and controller update methods. When an accessed property changes, the framework schedules the relevant method again. This can replace manual needsDisplay/setNeedsLayout fan-out when one model property affects multiple views.
import Observation
import UIKit
@Observable
@MainActor
final class ColorModel {
var hue = 0.6
var saturation = 1.0
var brightness = 1.0
}
final class ColorSwatchView: UIView {
var model: ColorModel
init(model: ColorModel) {
self.model = model
super.init(frame: .zero)
}
required init?(coder: NSCoder) { fatalError("init(coder:) not implemented") }
override func draw(_ rect: CGRect) {
UIColor(
hue: model.hue,
saturation: model.saturation,
brightness: model.brightness,
alpha: 1
).setFill()
UIBezierPath(rect: rect).fill()
}
}Apple says UIKit's coverage extends beyond UIView/UIViewController to types such as UIButton and UICollectionViewCell. The behavior is enabled by default in the 2026 OS releases and later. To back-deploy the integration, add the Boolean UIObservationTrackingEnabled key for iOS 18+; AppKit uses NSObservationTrackingEnabled for macOS 15+.
Migration guidance:
- Add
@Observableto the shared model and read only the properties the drawing or layout method truly depends on. - Enable the back-deployment key only after testing the oldest supported runtime.
- Remove manual invalidation calls incrementally. Validate that every affected override is observation-tracked before deleting a fallback.
- Keep the model
@MainActorwhen it directly drives UI state. - Use one shared model at the seam. Do not duplicate UIKit state into a second SwiftUI-only model.
Incremental SwiftUI adoption does not require a rewrite:
- Embed SwiftUI in UIKit with
UIHostingControllerorUIHostingConfiguration. - Embed UIKit in SwiftUI with
UIViewRepresentable/UIViewControllerRepresentable. - Reuse an existing recognizer through
UIGestureRecognizerRepresentable(available from iOS 18) and attach it with.gesture(...). - On macOS, the corresponding APIs include
NSHostingView,NSViewRepresentable,NSGestureRecognizerRepresentable,NSHostingMenu, andNSHostingSceneRepresentation.
The installed iOS 27 SDK confirms UIGestureRecognizerRepresentable, UIHostingController, and UIHostingConfiguration; the macOS-specific menu and scene wrappers must not be presented as iOS APIs.
Status: Confirmed breaking requirement. Secondary source: UIKit's Scene Mandate. Primary sources: Modernize your UIKit app — WWDC26 and Transitioning to the UIKit scene-based life cycle.
Apple states that a UIKit app built with the latest 27 SDKs must use UIScene lifecycle or it will not launch. Older binaries are not retroactively broken; the requirement is triggered by rebuilding with the 27 SDK. Multiple-window support is still optional.
Check all three surfaces:
- The built Info.plist contains
UIApplicationSceneManifestwith a usableUISceneConfigurationsentry, or the app uses a dynamic scene configuration. - The app delegate implements
application(_:configurationForConnecting:options:)when configuration is dynamic. - A scene delegate conforms to
UIWindowSceneDelegate, and UI lifecycle/window ownership has actually moved there.
Treat a manifest with no configuration, a configuration method with no delegate, or a scene delegate that leaves all UI lifecycle logic in AppDelegate as a partial migration requiring review.
For a programmatic single-window app, the essential shape is:
final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = RootViewController()
window.makeKeyAndVisible()
self.window = window
}
}UIApplicationDelegate remains the home for process-wide work such as shared service setup and push registration. Move UI lifecycle events to their scene equivalents (sceneDidBecomeActive, sceneWillResignActive, sceneDidEnterBackground, and sceneWillEnterForeground) as one coherent set. Use UIWindow(windowScene:), not UIWindow(frame: UIScreen.main.bounds).
iOS 27 makes iPhone Mirroring windows freely resizable, and iPhone-only apps on iPad can be fully resizable while still reporting the phone idiom. Apple therefore says:
- Remove
UIScreen.mainfrom window-relative layout. Use the closest view/controller's bounds or trait collection; usewindow.windowScene?.screenonly when the physical screen itself is truly required. - Replace
UIScreen.main.scalein view/controller code withtraitCollection.displayScale. If the result is cached outside an automatically re-run layout/draw method, observeUITraitDisplayScaleand refresh the cached value. - Do not use
userInterfaceIdiomfor layout. Prefer size classes or the surrounding view's dimensions. - Do not use interface orientation for layout. In resizable environments it is a preference, and iPhone Mirroring reports portrait regardless of aspect ratio. Use size classes or bounds.
- Test continuously resized widths, not only named devices and portrait/landscape snapshots.
Status: Confirmed App Store requirement. Apple documents this in TN3208 and the iOS/iPadOS 27 release notes.
An iOS or iPadOS app uploaded with the 27 SDK must have at least one of these keys in its built Info.plist:
UILaunchStoryboardName
UILaunchStoryboards
UILaunchScreen
UILaunchScreens
Without one, App Store Connect rejects the upload. Newer Xcode templates normally generate UILaunchScreen when automatic Info.plist generation and the Launch Screen (Generation) setting are enabled, but existing targets, extensions packaged as apps, and hand-maintained Info.plists need an explicit audit. Verify the archived product's Info.plist, then delete the installed app before visually retesting the launch screen because the system caches launch snapshots.
Status: Beta known issues, not intended final contracts. The current iOS & iPadOS 27 Beta 4 release notes list several build-linked defects:
- An iPad app that does not list all four interface orientations can be treated as non-continuously resizable. Apple's current workaround is to include all four in
UISupportedInterfaceOrientations. - iPhone Mirroring can initially ignore the target's supported orientations, and
UIRequiresFullScreenscenes can receive the wrong continuous-resize behavior. - An iPhone-only app on iPad that declares only portrait or only landscape can lay out incorrectly when the iPad is in an unsupported orientation.
Do not architect around these defects. Keep an explicit beta test matrix, recheck each item on every seed, and remove workarounds once Apple marks the issue resolved. UIRequiresFullScreen is itself deprecated; TN3192 is the durable migration path.
Two additional 27 behaviors deserve targeted UIKit testing:
- Siri and Apple Intelligence may start a
UIDragInteractionsession without a user drag gesture. KeepdragInteraction(_:sessionWillBegin:)free of modal presentation and drag-start animation; perform visible feedback after the session moves. - Audit custom presentation controllers and code that assumes traits jump directly from a presenting view controller. In 27-linked apps, presentation intermediates can participate in trait propagation; read traits from the view or controller whose layout is being computed.
Search UIKit targets for UIApplicationSceneManifest, UIWindowSceneDelegate, var window, UIScreen.main, userInterfaceIdiom, interfaceOrientation, UIDevice.current.orientation, supportedInterfaceOrientations, and UIWindow(frame:).
Status: Confirmed in current Apple material; the locally inspected earlier beta SDK confirms the original subset, while explicitly noted Beta 4 additions are newer. Secondary overview: What's New in SwiftUI for iOS 27. Primary overview: What's new in SwiftUI — WWDC26.
These are adoption opportunities, not mandatory rewrites. Availability-gate them when the deployment target is below 27.
| Area | New surface | Migration value |
|---|---|---|
| Reordering | .reorderable() plus .reorderContainer(for:move:) and ReorderDifference |
Replaces hand-built drag gestures and index arithmetic in List, lazy stacks/grids, stacks, and custom layouts. |
| Documents | Document, ReadableDocument, WritableDocument, DocumentReader, DocumentWriter, URLDocumentConfiguration |
Separates reading and writing, uses observable reference documents, moves I/O off the main actor, and supports coordinated URL access. Beta 4 deprecates FileDocument and ReferenceFileDocument; prefer the new model for new code and plan deliberate migrations for existing document apps. |
| Toolbar overflow | ToolbarOverflowMenu, .topBarPinnedTrailing, .visibilityPriority(_:), .toolbarMinimizationBehavior |
Expresses what is essential, what may overflow, and how the bar minimizes as widths change. The final spelling changed during the beta cycle. |
| Item presentation | alert(_:item:actions:) and confirmationDialog(_:item:actions:) |
One optional value controls presentation and supplies the action payload; no parallel Boolean or wrapper type. |
| Errors | alert(error:actions:message:) |
Binds directly to Error?; LocalizedError supplies user-facing descriptions. |
| Images | AsyncImage(request:) and .asyncImageURLSession(_:) |
Adds headers, timeout/cache-policy control, and a custom session. On 27 runtimes, ordinary AsyncImage(url:) also gains standard HTTP caching according to response headers. |
ForEach(stickers) { sticker in
StickerView(sticker: sticker)
}
.reorderable().reorderContainer(for: Sticker.self) { difference in
// Apply difference.sources and difference.destination to the model.
}The callback reports IDs and a destination (before(id) or end); the model remains the source of truth. For multiple sections, use the collection-ID overload and route by difference.destination.collectionID. The installed SDK marks reorder containers available on iOS, macOS, watchOS, and visionOS 27, and unavailable on tvOS.
The WWDC demo may look as though this is sufficient:
.reorderContainer(for: Sticker.self) { difference in
difference.apply(to: &stickers)
}Grok surfaced an X report that Xcode could not find apply. Inspection of Xcode 27 beta build 27A5194q confirms that ReorderDifference exposes only sources and destination; it has no apply(to:) member. Apple also says in the WWDC session that its helper uses the open-source Swift Collections package, and the session's code listing imports OrderedCollections and defines the extension in sample code.
Choose one explicit migration path:
- Apply
sourcesanddestinationin your own collection model. - Copy the small algorithm into app-owned code with tests for multi-item moves and cross-section moves.
- If the project already benefits from it, add Apple's official
apple/swift-collectionspackage at an intentionally pinned or bounded version and keep the helper visibly app-defined.
Do not write production code assuming a public difference.apply(to:) exists until the SDK interface actually declares it. Apple's current reorderContainer reference text appears ahead of or inconsistent with this beta SDK.
@State private var itemToDelete: Item?
ContentView()
.alert("Delete item?", item: $itemToDelete) { item in
Button("Delete", role: .destructive) {
delete(item)
}
Button("Cancel", role: .cancel) {}
} message: { item in
Text(item.name)
}For older OS support, place the new modifier in an if #available(iOS 27, *) branch and keep the existing isPresented/presenting fallback.
In the new document model, snapshot(contentType:) and apply(snapshot:previous:) run on the main actor and must remain light. Serialization/deserialization belongs in the reader/writer. Autosave relies on registered undo actions; moving to the new protocols without an undo strategy can create a document that edits correctly but does not autosave as expected.
Beta 4 adds the umbrella Document protocol for common read/write documents and deprecates FileDocument and ReferenceFileDocument. This is newer than the locally inspected beta build: that build contains ReadableDocument and WritableDocument, but not Document or deprecation annotations on the old protocols. Treat the current release notes as the intended direction and verify the exact declarations in the Xcode seed used by CI before beginning a broad conversion.
These are not optional visual refinements; compiling against the 27 SDK changes assumptions in existing code:
TabViewselection must remain visible. A binding that points to a hidden or otherwise unavailable tab may crash. Before hiding/removing a selected tab, move selection to a visible fallback in the same state transition.- Selectable
Texthas system interaction gestures. ATextwith.textSelection(.enabled)now uses the system selection UI. If an existing custom gesture must win, attach it with.highPriorityGesture(...)and retest selection, accessibility, and scroll gesture arbitration. - Menu images are reduced by default. On iPadOS 27, most symbol images in menus are hidden unless semantics justify forcing
.labelStyle(.titleAndIcon); review the current Human Interface Guidelines rather than restoring every icon mechanically. - Text-field border styles changed.
.squareBorderand.roundedBorderare soft-deprecated. Prefer.borderedand usetextInputBorderShape(_:)when a nondefault shape matters. - Toolbar naming moved during beta. Current release notes use
toolbarMinimizationBehavior, replacing the earliertoolbarMinimizeBehavior. Expect source churn across seeds and verify against the SDK used by CI.
Status: Beta 3 known issue; reported fixed in Beta 4 on aligned platforms, so keep the regression test. A target whose minimum deployment version is below 27 could warn that conditional _ConditionalContent conformance to ChartContent is only available in 27 and then crash when loading that content. Apple's workaround is to extract the conditional marks into a function or property explicitly annotated with @ChartContentBuilder:
Chart(points) { point in
marks(for: point)
}
@ChartContentBuilder
private func marks(for point: Point) -> some ChartContent {
if showsRate {
LineMark(x: .value("Time", point.date),
y: .value("Rate", point.rate))
} else {
LineMark(x: .value("Time", point.date),
y: .value("Signal", point.signal))
}
}Even after the seed containing the fix, run this path on the oldest supported OS. It crosses the compiler, Swift Charts result builder, and back-deployed runtime.
Status: Confirmed new Xcode 27 API; beta runtime behavior still needs seed-by-seed validation. Primary sources: XCUIVoiceOverService, XCUIDevice.voiceOverService, and Apple's Accessibility Technologies Group Lab.
Xcode 27 adds programmatic VoiceOver control to XCUIAutomation. This closes an important gap: an end-to-end test can now validate the order in which VoiceOver visits elements and inspect what it actually speaks, rather than only asserting raw accessibility properties.
The entry point is:
let voiceOver = XCUIDevice.shared.voiceOverServiceThe locally inspected Xcode 27 beta headers and a compiler type-check confirm this Swift surface:
@MainActor
final class XCUIVoiceOverService {
var isEnabled: Bool { get }
func enable() throws
func disable() throws
func currentSpeech() throws -> Output
func moveForward() throws -> Output
func moveBackward() throws -> Output
// iOS and macOS only
func moveIn() throws -> Output
func moveOut() throws -> Output
final class Output {
var utterance: String { get }
}
}The service is available on 27 runtimes. Forward/backward navigation and speech retrieval are declared across iOS, macOS, tvOS, watchOS, and visionOS; entering and leaving a container are declared only on iOS and macOS. Native visionOS applications remain a practical exception because XCUIAutomation does not currently support UI testing for apps built with the visionOS SDK.
This is not a Swift Testing replacement for UI tests. XCUIAutomation is still driven from an XCTest UI-testing target:
import XCTest
@MainActor
final class CheckoutVoiceOverTests: XCTestCase {
func testCheckoutSummaryReadingOrder() throws {
let app = XCUIApplication()
app.launchArguments = ["-uiTesting", "-checkoutFixture"]
app.launch()
XCTAssertTrue(
app.otherElements["checkout.summary"].waitForExistence(timeout: 5)
)
let voiceOver = XCUIDevice.shared.voiceOverService
try voiceOver.enable()
defer { try? voiceOver.disable() }
let title = try seek(
"Order summary",
using: voiceOver,
maximumMoves: 20
)
XCTAssertTrue(title.utterance.contains("Order summary"))
let total = try voiceOver.moveForward()
XCTAssertTrue(total.utterance.contains("Total"))
let button = try voiceOver.moveForward()
XCTAssertEqual(button.utterance, "Place Order, button")
}
private func seek(
_ phrase: String,
using voiceOver: XCUIVoiceOverService,
maximumMoves: Int
) throws -> XCUIVoiceOverService.Output {
var output = try voiceOver.currentSpeech()
for move in 0...maximumMoves {
if output.utterance.localizedCaseInsensitiveContains(phrase) {
return output
}
if move < maximumMoves {
output = try voiceOver.moveForward()
}
}
XCTFail("VoiceOver never reached: \(phrase)")
return output
}
}The bounded search makes startup focus less fragile while still failing if a critical element is unreachable. Once the test reaches a stable landmark, assert the next few utterances directly to validate reading order.
- A visible control that is absent from VoiceOver navigation.
- An unintended element inserted into the reading order.
- A combined element whose spoken label/value/trait is wrong.
- Heading, button, selected, adjustable, or other semantics that change the final utterance.
- A custom container that VoiceOver cannot enter or leave correctly.
- Localization or dynamic state that produces misleading speech even though the element remains queryable by identifier.
The important distinction is:
accessibilityIdentifieris a stable, nonlocalized test hook. It is not the user-facing spoken text.accessibilityLabel, value, traits, and the surrounding accessibility structure contribute to the VoiceOver experience.Output.utterancevalidates the synthesized result that a user hears.
Do not put test-oriented text into accessibilityLabel. Give reusable views a required stable identifier for automation and a separately localized, user-meaningful label.
XCUIApplication.performAccessibilityAudit(for:_:) is older—it has been available since iOS 17—but remains complementary. Run it on every important screen to catch broad issues such as descriptions, hit regions, contrast, element detection, Dynamic Type, clipped text, and traits:
@MainActor
func testCheckoutAccessibilityAudit() throws {
let app = XCUIApplication()
app.launchArguments = ["-uiTesting", "-checkoutFixture"]
app.launch()
XCTAssertTrue(
app.otherElements["checkout.summary"].waitForExistence(timeout: 5)
)
try app.performAccessibilityAudit(for: .all)
}Use the audit issue-handler only for a narrowly identified, documented exception. Returning true tells XCTest that the issue was handled and prevents it from being recorded; a blanket true silently defeats the audit.
A useful layered E2E strategy is:
- Ordinary XCUI journeys prove that critical workflows function.
performAccessibilityAuditprovides broad per-screen static checks.XCUIVoiceOverServicecovers a small number of high-value reading-order and spoken-output journeys.- Test-plan configurations cover representative content sizes, locales, appearance, orientation, and accessibility settings.
- Manual testing with VoiceOver and other assistive technologies covers interaction quality that automation cannot model.
- Pin the test plan's language and locale. System words such as “button” are localized, and punctuation or phrasing can change between OS releases.
- Prefer exact utterance equality for a few critical semantics in a fixed configuration. Elsewhere, assert meaningful phrases and relative order to avoid turning system wording into a large snapshot surface.
- Keep the VoiceOver suite small and normally serial. Enabling/disabling a device-wide service is heavier and more stateful than querying an element.
- Always disable VoiceOver in cleanup. The throwing failure modes include failure to start or stop, calling navigation while VoiceOver is not running, and receiving no speech before the timeout.
- The API returns speech, not the focused
XCUIElement. It does not expose rotor selection, arbitrary custom-action invocation, or a VoiceOver “activate current item” operation. Use ordinary XCUI element actions for the functional part of the journey and retain manual assistive-technology testing. - Xcode 27's Device Hub also exposes accessibility settings, but an early-beta release note says its VoiceOver toggle may fail to enable VoiceOver on the device. That UI defect should not be confused with the separate XCTest service; validate both on the exact Xcode/OS seed used by CI.
Apple's accessibility lab recommends automated UI testing and explicitly calls out the new element-to-element VoiceOver API, while emphasizing that automation gets most—not all—of the way to a genuinely accessible experience.
Status: Confirmed deprecation and migration deadline. Secondary source: ImageCreator Is Deprecated. Primary sources: Apple's deprecation notice, ImageCreator documentation, and Create high quality images using Image Playground — WWDC26.
The Xcode 27 beta SDK declaration is:
@available(iOS 18.4, macOS 15.4, visionOS 2.4, *)
@available(anyAppleOS, deprecated: 27.0,
message: "Use ImagePlaygroundViewController or imagePlaygroundSheet.")
final public class ImageCreator: SendableApple says beta builds warn, TestFlight use can fail at runtime, and public 27 releases stop accepting the old implementation. There is no headless, programmatic Image Playground replacement.
Migration choices:
- User-driven generation: use SwiftUI's
imagePlaygroundSheetorImagePlaygroundViewControllerin UIKit/AppKit. - Copy the result from the temporary completion URL into app-owned storage before the session ends.
- Gate the feature with
supportsImageGenerationand provide a fallback for unsupported capability, language/region, or user settings. - A feature that truly requires background/headless generation needs a different service or product design. That is an architectural change, not a one-line API rename.
Search for ImageCreator, images(for:style:limit:), and images(for:style:options:limit:) across app, extension, test, and sample targets.
The series inventory contained 87 posts dated April 28 through June 15, 2026 at the time of review. It is valuable discovery material but is not itself a primary source. The migration guide incorporated the items with broad, immediate impact and linked them above.
| Series area skimmed | Disposition for a general Xcode 27 migration |
|---|---|
| SwiftUI performance/interop, What's New in SwiftUI, Xcode agent skills | Incorporated and checked against WWDC transcripts, exported Apple skills, and SDK interfaces. |
| Accessibility and E2E testing | Added a dedicated XCUIVoiceOverService section, kept automated accessibility audits as a separate layer, and compiler-verified the new VoiceOver Swift calls against the installed Xcode 27 beta. |
| UIKit scene lifecycle/adaptivity | Incorporated as a launch-blocking requirement and layout audit. |
ImageCreator discontinuation |
Incorporated as a feature-breaking migration. |
| Instruments 27 | Use for validation: start in Time Profiler, distinguish high-CPU work from blocked/idle waits, use Top Functions and flame graphs, Swift executors, syscall inspection, and Run Comparisons. Primary source: Find hangs using Instruments — WWDC26. Grok also surfaced Jacob Bartlett's launch-time flame-graph walkthrough as a practical secondary workflow. |
| MetricKit 27 / StateReporting | Optional production-telemetry project: new MetricManager async streams and state-specific performance attribution. Do not mix this into a source migration unless the app already consumes MetricKit. Primary source: Meet the new MetricKit — WWDC26. |
| SwiftData observation/history/performance | Framework-specific adoption: ResultsObserver, HistoryObserver, @Attribute(.codable), fetchCount, and fetchIdentifiers are present in the 27 SDK. Review when the app uses SwiftData; do not add it to apps using another persistence layer. Primary source: What's new in SwiftData — WWDC26. |
| On Demand Resources | Migration work for targets using NSBundleResourceRequest: iOS 27 deprecates On Demand Resources in favor of Background Assets. Inventory tags, hosting, download policy, eviction assumptions, and offline behavior before switching systems. Primary source: iOS & iPadOS 27 release notes. |
| Swift 6.3/6.4 language and testing posts | Compile all modules and tests under the Xcode 27 toolchain; adopt language features deliberately rather than changing language mode as part of an unrelated UI migration. |
| Camera, HealthKit, App Intents, Foundation Models, Core AI, RealityKit, Vision, accessibility, Safari/WebKit, Metal, and other domain posts | Treat as opt-in framework workstreams. They do not belong in every app's baseline migration checklist; open a separate verified plan when the codebase imports the relevant framework. |
The highlighted performance article includes paraphrases from locally recorded WWDC group labs for which Apple publishes no official transcript. They are useful leads—coarsening values returned from onGeometryChange, keeping high-frequency scroll position out of the environment, avoiding deep UIKit/SwiftUI layout “sandwiches,” and using a separate UIWindow for overlays that must sit above sheets—but they are labeled Secondary / not independently confirmed here. Measure them in the affected app and validate public API contracts before turning them into automated migration rules.
Run targeted searches and classify each hit rather than applying broad mechanical rewrites:
@State private var .*=
init(
canOpenURL
LSApplicationQueriesSchemes
Task {
.swipeActions
@Entry var
ForEach(
@Observable
private var .*: some View
LazyVStack
LazyHStack
onScrollGeometryChange
onGeometryChange
UIScreen.main
userInterfaceIdiom
interfaceOrientation
UIApplicationSceneManifest
UIWindowSceneDelegate
XCUIVoiceOverService
voiceOverService
performAccessibilityAudit
accessibilityIdentifier
accessibilityLabel
UILaunchStoryboardName
UILaunchScreen
UISupportedInterfaceOrientations
UIRequiresFullScreen
TabView(selection:
.textSelection(.enabled)
FileDocument
ReferenceFileDocument
ReorderDifference
NSBundleResourceRequest
dragInteraction
UIPresentationController
ImageCreator
MXMetricManager
_UIScrollEdge
updatePocket
Prioritize:
- UIKit targets that have not completed scene-lifecycle adoption or lack a launch-screen declaration.
- Build failures, new concurrency warnings, and a
TabViewselection that can name a hidden tab. - Critical screens with no automated accessibility audit, and critical flows with no VoiceOver reading-order coverage.
@Stateinitializer conflicts and reference-valued initializer side effects.ImageCreator, On Demand Resources, and URL-scheme list overages/install-detection UI.- Private UIKit dependencies and custom presentation/drag-interaction assumptions.
- Large/lazy collections with inline work, multi-shaped rows, unstable offset thresholds, or post-appearance layout feedback.
- Frequently updating observable properties and broad value inputs.
UIScreen.main, idiom, orientation, andUIRequiresFullScreenassumptions in resizable UIKit layouts.- Custom iPad chrome that looks wrong while inactive.
- Optional adoption of the new reorder, document, toolbar, image, swipe, and adaptive-sidebar APIs—without assuming sample-only helpers are framework APIs.
Test at least these combinations before merging a migration:
| Build SDK | Runtime | Why |
|---|---|---|
| Xcode 27 / iOS 27 | Latest iPhone seed | New APIs, warning behavior, resizable/adaptive layouts |
| Xcode 27 / iPadOS 27 | Multiple windows, active and inactive | appearsActive, navigation adaptation, custom chrome |
| Xcode 27 / iPadOS 27 | Continuous resizing, each declared orientation, iPhone-only compatibility | Separate intended adaptivity from seed-specific orientation defects |
| Xcode 27 / iOS 27 | VoiceOver enabled through XCUIAutomation, fixed locale | Reading order, spoken semantics, container navigation, cleanup reliability |
| Xcode 27 / oldest supported OS | Physical device or simulator | Back-deployment and availability fallbacks |
| Xcode 27 archive | Inspect archived Info.plist and validate an upload | Scene manifest, launch-screen gate, capability and packaging failures |
| Xcode 26 baseline, if still available | Existing supported runtime | Compare state construction and regressions during transition |
For performance rules, measure representative large data sets. A structurally cleaner row or narrower input is desirable, but the codebase should still use Instruments, signposts, _printChanges() in temporary diagnostics, and realistic scrolling/update workloads to prioritize work.
- Apple: What's new in SwiftUI — WWDC26
- Apple: Dive into lazy stacks and scrolling with SwiftUI — WWDC26
- Apple: Use SwiftUI with AppKit and UIKit — WWDC26
- Apple: Modernize your UIKit app — WWDC26
- Apple: Transitioning to the UIKit scene-based life cycle
- Apple: TN3187 — Migrating to the UIKit scene-based life cycle
- Apple: TN3208 — Preparing your app's launch screen for App Store requirements
- Apple: TN3192 — Migrating from
UIRequiresFullScreen - Apple: Updating UIKit views automatically with observation tracking
- Apple: SwiftUI updates, June 2026
- Apple: TN3211 — Resolving SwiftUI source incompatibilities for State and ContentBuilder
- Apple:
swipeActionsContainer() - Apple:
EnvironmentValues.appearsActive - Apple:
UITabBarController.Sidebar.preferredPlacement - Apple:
UITabBarController.Sidebar.Placement - Apple:
UIApplication.canOpenURL(_:) - Apple: iOS & iPadOS 27 release notes
- Apple: SwiftUI reordering demo and sample-helper explanation — WWDC26
- Apple open source:
apple/swift-collections - Apple:
UIScrollEdgeEffect - Apple:
UIScrollEdgeElementContainerInteraction - Apple:
ImageCreatordeprecation notice - Apple:
ImageCreator - Apple: Create high quality images using Image Playground — WWDC26
- Apple: Find hangs using Instruments — WWDC26
- Apple: Meet the new MetricKit — WWDC26
- Apple: What's new in SwiftData — WWDC26
- Apple:
XCUIVoiceOverService - Apple:
XCUIDevice.voiceOverService - Apple: Performing accessibility audits for your app
- Apple: Accessibility Technologies Group Lab — WWDC26
- Swift compiler:
NoUseUnstructuredThrowingTask - All 12 unique supplied X posts are linked individually in the source inventory above; the distinct Grok-discovered follow-ups are linked in the follow-up table.
- Secondary discovery: Blake Crosley's Apple Ecosystem series, especially SwiftUI Performance and Interop in iOS 27.
The first gates are now launch and distribution viability: scene lifecycle must be in place, and a 27-SDK upload must declare a launch screen. The next urgent migrations are build-linked semantics and diagnostics—@State initialization, visible TabView selection, selectable-text gestures, discarded throwing tasks, ImageCreator, On Demand Resources, and URL-scheme probing. Xcode 27's VoiceOver service is a meaningful new E2E layer: keep broad accessibility audits, then add a small set of real VoiceOver reading-order and utterance tests for critical journeys. The highest-leverage SwiftUI performance work is to respect lazy estimation and prefetching while making identity and dependency boundaries explicit: stable defaults, unary rows, prepared collections, narrow inputs, and real subviews. Observation makes an incremental UIKit/SwiftUI seam practical; it does not require an all-SwiftUI rewrite. New reorder, document, toolbar, image, swipe, and adaptive-sidebar APIs are adoption opportunities behind availability checks. Sample-only helpers, private scroll-pocket findings, and unpublished lab paraphrases are not framework contracts.
Because this guide was produced during the beta cycle, re-run the SDK/compiler probes and re-check the linked Apple release notes before shipping with the final Xcode 27 toolchain.