SwiftUI
Unofficial AI summary of the WWDC26 group lab. May contain mistakes.
Related Labs: SwiftUI for Beginners, SwiftUI (June 11)
Is there a specific architecture that the SwiftUI team expects developers to adopt?
- SwiftUI is architecture-agnostic; any architecture that works for the app should work with SwiftUI.
- When developers encounter ergonomic issues integrating the chosen architecture with SwiftUI, provide feedback.
- Observable can be used to store models and facilitate transitions to SwiftUI, independent of UI frameworks.
- App architecture decisions should be driven by the need to ship features, not by the architecture itself.
- Structure the data model robustly and testably, with SwiftUI views acting as a projection of this data.
What are the performance tradeoffs of @ViewBuilder closures vs separate view structs?
- Separate view structs have their own identity, allowing for independent dependency tracking and more granular invalidation.
- Computed properties within a view body are treated as part of the parent view's body, offering less granular control — they do not create separate invalidation boundaries.
@ViewBuilderand@ContentBuilderare equivalent (type alias); use either for computed properties that return views.- Creating separate custom views allows SwiftUI to more precisely update only the views that have changed, improving performance.
- Extracting subviews also makes Xcode Previews more useful — developers can preview smaller units in isolation.
- Views are structs allocated on the stack, making them inexpensive to create, render, and discard.
- Extracting parts of a view into custom views or custom modifiers can minimize dependencies and improve performance.
- Custom views can also be necessary for reading environment values like
backgroundProminence.
What are common SwiftUI anti-patterns and how can they be avoided?
GeometryReader: Prefer using layout APIs instead to avoid invalidating entire view hierarchies when layout changes.onChange: Avoid usingonChangeto trampoline data back and forth; aim for more declarative views that respond directly to data.- Environment Properties: Analyze usage of environment properties; frequent changes can invalidate many views unnecessarily. Consider using
@Observablereference types passed via the environment to stabilize dependencies. - Wrapper Views for Controls: Instead of wrapping controls like
Buttonwith custom views and modifiers, explore usingButtonStyleor other style protocols for better reusability and access to built-in initializers. - Building Custom Components from Scratch: Leverage built-in components like
ListandGridas much as possible, as they evolve with system updates. Interoperability with UIKit/AppKit is also a valid approach when a native SwiftUI component is missing. - Conditional Modifiers: Avoid using conditional modifiers that change while the view is on screen, as this can lead to view recreation, broken animations, and state reinitialization. Prefer inert modifier variants (e.g.,
opacity(0)vs removing the view) when possible. - If a modifier lacks an inert variant, file feedback with the use case.
- Large View Bodies: Split large view bodies into smaller, more manageable custom views to limit the scope of invalidation.
What is a core concept in SwiftUI that developers often struggle to understand, and how can it be explained?
- SwiftUI has an internal graph representation of views with nodes and edges representing views and their inputs.
- This internal mechanism diffs and updates the view hierarchy based on state changes.
- UIKit layout is top-down; SwiftUI layout is bottom-up — understanding this direction matters for debugging layout issues.
- Body re-evaluation does not equal re-rendering; SwiftUI may re-run
bodywithout actually updating the on-screen result. - Understanding this update mechanism is crucial for diagnosing performance issues and unexpected view updates.
- Updates are driven by state changes, not by re-rendering and comparing views like in a DOM.
How does filtering data within a ForEach loop impact SwiftUI's performance and behavior?
- Placing
ifstatements directly within aForEachbody can hinder SwiftUI's ability to efficiently determine the total number of elements. - This can lead to SwiftUI iterating through the entire
ForEachloop for operations that require a count. - Solutions include filtering data upfront before passing it to
ForEachor wrapping conditional views in anHStackorZStackto maintain a constant view count.
What is the mental model for connecting @State and state management with Swift Concurrency?
- SwiftUI views are
MainActorisolated, meaning their state and property wrappers are also main actor isolated. - This simplifies concurrency by ensuring state is accessed and modified on the main thread.
- Rare exceptions exist where closures might be
Sendableand executed off-main; in these cases, explicitly capture a copy of the state value to avoid concurrency errors. - Most SwiftUI closures are
MainActorisolated, so these concurrency issues are uncommon.
What is the recommended approach for implementing navigation with custom transitions in iOS 27?
- iOS 27 introduces built-in support for customizing navigation transitions in SwiftUI, including crossfades.
- If the needs are met by these built-in transitions, they are the recommended approach.
- For older iOS versions or more complex custom transitions, consider interoperating with UIKit navigation controllers.
- App resizing is a key consideration; sticking to system navigation components (SwiftUI, UIKit, AppKit) generally provides better support for resizable apps.
What is the recommended way to track a scroll view's scrolling offset to show/hide view elements?
- Use modifiers like
onGeometryChangeorscrollEffectsfor tracking scroll position. - The
scrollPositionmodifier can identify the item currently on screen. - Consider using APIs that track the percentage of a view's visibility, especially for analytics or triggering actions when content enters the viewport.
- Content offset should be treated as an implementation detail, especially with lazy stacks; focus on relative view positions.
- For infinite scrolling, consider adding a view at the end of the list with an
onAppearmodifier to trigger fetching more data.
Is it possible to build custom layouts that are lazy in SwiftUI, similar to LazyVStack?
- Currently, there is no specific protocol for describing custom lazy layouts.
- File feedback with detailed use cases when developers require this functionality.
- For complex layout needs, consider interoperating with
UICollectionViewor composing existing lazy layouts with custom non-lazy sub-layouts (e.g., a lazy built-in container with a custom non-lazy mosaic sub-layout). - The protocol for non-lazy custom layouts is available and optimized compared to manual arrangement with
GeometryReader.
Are lazy stacks sufficient for infinite scrolling, or is UICollectionView needed?
- Lazy stacks allocate views on the heap as needed and benefit from implicit prefetching and partial graph evaluation.
- However,
onAppearon list items can invalidate prefetch work if it triggers heavy side effects. - For very large infinite-scroll lists with complex cell reuse needs,
UICollectionViewvia interop remains a valid option. - Profile the specific use case before assuming developers need to drop to UIKit.
Why does ContentBuilder improve type-checking performance?
ContentBuildercollapses many overloads into a single builder, reducing the compiler's search space.- This turns a wide set of competing overloads into a single entry point the type checker can follow more efficiently.
@ViewBuilderis a type alias forContentBuilder, so the same benefits apply to view bodies.
What is the difference between @ViewBuilder and @ContentBuilder?
- They are the same type —
@ViewBuilderis a type alias forContentBuilder. ContentBuilderis the general name; use it (or the alias) for custom DSLs outside view bodies, not just SwiftUI views.
How do I debug mystery re-renders in SwiftUI?
- Use
_printChanges()and_logChanges()in debug builds to see what triggered a view update (underscore APIs — don't ship to production). - A random-color
bodytrick can visually reveal which views are re-rendering. - Use the SwiftUI instrument in Instruments to analyze update patterns.
- Agents can help analyze SwiftUI instrument traces.
- Split views to isolate dependencies once developers have identified the trigger.
What are the most common performance mistakes in large SwiftUI apps?
- Excessive Invalidations: Too many invalidations, often starting from the top of the view hierarchy and propagating down, are a common issue.
- Frequent Environment Updates: Placing frequently changing values (e.g., time in milliseconds, scroll position) in the environment can cause widespread invalidations.
- Passing Large Value Types: Passing complex value types down through many views, even if not all views use them, requires quality comparisons at each level. Using
@Observablereference types can be more performant as only the reference is passed. - Resizing Issues: Over-reliance on
GeometryReadercan cause views to update on every frame during app resizing. Aim to minimize view updates during resize events. - Large View Bodies: Large view bodies increase the scope of invalidation. Splitting them into smaller custom views helps limit updates.
Do @State properties holding @Observable classes allocate on the correct thread?
- SwiftUI views are
MainActorisolated, meaning their bodies and state are called on the main thread. @Stateproperties holding@Observableclasses will automatically be allocated on the main thread.- With modern concurrency settings (main actor isolation inferred), custom observable classes are also automatically main actor isolated for UI code.
- For background computations, factor them into separate functions,
awaittheir results, and then update the UI on the main thread to ensure atomic updates and smooth animations. TimelineViewcan be used for updates that need to occur on every frame.
How can multi-module apps hide concrete view types without AnyView?
some Viewis the preferred approach as it provides opaque types, allowing the type checker to know the concrete type without exposing it externally. This maintains static information for features likeForEach.- If
some Viewis not feasible,AnyViewcan be used, but ensure the underlying type remains consistent to avoid performance issues. - Wrapping
AnyViewin anHStackorZStackcan helpForEachby communicating that only a single view is returned.
What are the performance tradeoffs (speed and memory) of using CompositingGroup or DrawingGroup?
CompositingGroup: Primarily for visual effects, not performance. It allows applying effects (like shadows) to the entire group of views as a single unit, rather than to each individual element.DrawingGroup: Consolidates rendered layers into a single layer, significantly improving performance for complex custom graphics by reducing the number of on-screen layer representations. This is useful when many individual views would otherwise be rendered.Canvas: Offers more direct control over drawing invocations, similar todrawRectin UIKit/AppKit. It's suitable for custom graphics but does not draw SwiftUI views, meaning gestures and accessibility need to be handled manually.