WWDC26: SwiftUI Group Lab - Q&A
Direct answers from Apple Engineers during WWDC26
SwiftUI rewards a different mental model than UIKit or AppKit. This lab focused less on single APIs and more on how SwiftUI wants you to think about architecture, view identity, invalidation, layout, built-in controls, debugging, and performance.
As usual, the goal is simple: make the questions easier to scan, easier to revisit, and easier to connect with real app development problems.
I tried to preserve the original wording and combine related answers where appropriate. However, some inaccuracies or mismatches are still possible.
Enjoy! And subscribe so you don’t miss the next Lab.
Is there an architecture like MVC, MVVM, or another pattern that the SwiftUI team expects us to adopt?
No. SwiftUI is designed to be architecture-agnostic.
Use the architecture that fits your app, your team, and your data model. A small app may not need a heavy architecture at all. A large app with many contributors may benefit from stronger conventions and more standardized patterns.
The important part is not to design the app around a UI framework alone. Your data model also has to support persistence, synchronization, testing, and other app-specific needs. Ideally, your SwiftUI views are a projection of that model into pixels.
@Observable can help here because your model can remain plain Swift code. It can be tested, reused from UIKit or AppKit, and then observed by SwiftUI when you adopt SwiftUI incrementally.
What is the difference between an @ViewBuilder closure or computed property and a separate View? Are there performance pros and cons?
A computed property or @ViewBuilder helper mostly behaves as if the code still lived inside the original view body. It helps readability, but SwiftUI does not get a new independent view identity from it.
A separate View is different. It has its own identity and its own body. SwiftUI can track dependencies and invalidation more granularly for that separate view.
That means extracting a subview can improve performance when only part of a larger view depends on a piece of state. The smaller view can update independently instead of causing the whole parent body to be reevaluated.
There is no hard lower bound for how small a view can be. Views are lightweight value types. Split when it clarifies the code, isolates dependencies, gives you finer previews, or lets you read environment values in the right scope.
The same idea applies to modifiers: extracting repeated modifier chains into a custom modifier can provide similar structure and dependency benefits.
What are some common SwiftUI anti-patterns developers fall into?
Several patterns came up.
First, watch for overuse of GeometryReader. If you are using it to drive layout, a custom Layout may be a better fit because it avoids invalidating a larger hierarchy unnecessarily.
Second, be careful with onChange. It is useful, but if you are using it to trampoline data back and forth, that may be a sign the view is still written in an imperative style. Prefer making the UI declaratively respond to state.
Third, avoid putting frequently changing values in the environment when many views read them. Environment is useful for passing data through the tree, but if the environment value changes often, every view that declares that dependency may be invalidated. Passing an observable object through the environment can be better when the object identity is stable and only specific properties change.
Fourth, avoid custom wrapper views around controls when a style protocol is the better tool. If you are wrapping Buttononly to apply a consistent look, a custom ButtonStyle often keeps you closer to SwiftUI’s built-in behavior and built-in initializers.
Finally, be careful with conditional modifiers that add or remove modifiers while the view is on screen. Changing the structure can recreate the view, break animations, reset state, and hurt performance. Prefer inert modifier values, such as changing opacity between 1 and 0, instead of conditionally adding and removing the modifier itself.
What is one SwiftUI concept that takes many developers the longest to understand?
A major concept is how SwiftUI updates views.
SwiftUI does not work like a DOM diff where the framework simply compares rendered output. Internally, SwiftUI maintains a graph of views and dependencies. State changes flow through that graph, and SwiftUI uses those dependencies to decide what needs to be reevaluated.
This is why dependency scoping matters. If a large view reads a piece of state, the whole view may be reevaluated when that state changes. If a smaller extracted view reads it, SwiftUI can update that smaller unit instead.
Another common mental shift is that reevaluating a view body is not necessarily the same as redrawing or rerendering pixels. A body can be reevaluated without causing expensive rendering work if the resulting view description does not materially change.
Why can ForEach with filtering or if statements become a problem?
If you put filtering logic or if statements inside the body of a ForEach, the visual result may look correct, but SwiftUI may need to iterate through the whole ForEach to understand how many views it contains.
That can hurt performance, especially in large lists or lazy containers.
A better approach is to filter the data before it reaches the ForEach, so the ForEach receives a collection with a predictable number of elements. If type erasure or structural uncertainty is involved, wrapping content in a container such as HStack or ZStack can sometimes give SwiftUI a constant structural shape to reason about.
How should developers think about SwiftUI layout compared with UIKit layout?
UIKit and SwiftUI approach layout differently.
UIKit is often experienced as top-down: the outer container lays out child views and pushes constraints or frames downward.
SwiftUI often feels more bottom-up. Parents propose sizes, children decide what size they want, and those answers flow back upward. The final layout is the result of that negotiation.
This difference matters when mixing UIKit/AppKit and SwiftUI. If you build a “sandwich” or “cake” of interop layers, with frameworks alternating across the hierarchy, you need to be aware that each framework has different layout and invalidation expectations.
For deeper understanding, the panel pointed to earlier WWDC material on custom layout.
What did the new ContentBuilder work change, and why does it matter?
ContentBuilder improves how SwiftUI handles complex view structures, especially views with many groups, sections, and ForEach blocks.
The panel used an analogy: older type-checking could feel like exploring every path through a cave. With the newer builder improvements, it becomes more like walking down a hallway.
The practical result is that SwiftUI code with nested structural containers can type-check more predictably. ContentBuilderalso backports because it is effectively a type alias to ViewBuilder, while ViewBuilder itself became smarter.
When your teams hit a mystery rerender storm, what is the real debugging workflow? Is it Instruments, _printChanges(), or something else?
Use all of the tools.
_printChanges() and _logChanges() are useful for seeing why a view body is being reevaluated. _logChanges() uses OS logging, which can be more convenient in some workflows.
Another simple visual trick is to temporarily apply a random color to a view. If the color changes unexpectedly, you can quickly see that the body is being reevaluated. This is also useful for comparing a computed @ViewBuilder helper with an extracted subview.
For deeper investigation, use the SwiftUI instrument in Instruments. It gives a more systematic view of invalidation and view update behavior.
What are the performance tradeoffs of compositingGroup and drawingGroup?
compositingGroup is less about performance and more about visual correctness. It lets SwiftUI treat a group of views as a single composited result before applying effects.
For example, if you apply a shadow to a ZStack, you might expect one shadow around the combined visual result, but without compositing SwiftUI may apply the shadow to each individual visual element. compositingGroup lets you apply the effect to the overall result.
drawingGroup is more performance-oriented. It can render many visual layers into a single drawing layer. This can help when you have many on-screen rendered elements, such as a complex custom graphic, and the cost is dominated by layer rendering rather than the number of SwiftUI value-type views.
That said, it is not a universal optimization. It trades memory and rendering behavior for fewer rendered layers, so profile before using it broadly.
When should I use Canvas, and what are its tradeoffs?
Canvas is useful when you are drawing custom graphics where you do not need each individual piece to be its own SwiftUI view.
The tradeoff is that the things drawn inside a Canvas are not SwiftUI views. You cannot attach independent gestures to each drawn element the same way you would with separate views. If you need interaction, you may need to do your own hit testing or math.
Accessibility also needs extra care. A canvas can be visually rich but semantically empty unless you expose an accessible representation. The panel called out accessibilityRepresentation as a useful API: for example, a visual canvas slider could be represented to accessibility as three real sliders.
🏆 Acknowledgments
A huge thank-you to everyone who joined and shared practical SwiftUI questions throughout the session. Your questions made the discussion useful for developers thinking about architecture, view identity, invalidation, layout, performance, debugging, built-in controls, UIKit/AppKit interop, and accessibility.
Question acknowledgments: U.J., Azie, Tyler, and the online WWDC audience who submitted and upvoted the remaining SwiftUI and UI Frameworks questions.
Finally, a heartfelt thank-you to Kurt, Adicha, Jason, Taylor, David, Sema, and the teams behind the scenes for leading the session and explaining how to reason about SwiftUI in real apps.
