Hi, and welcome to another SwiftUI and UI Frameworks group lab. I'm Kurt, and I'm part of the worldwide developer relations team here at Apple. I'm joined by engineers and leaders from the UI Frameworks team: Aditya, Jason, Taylor, David, and Seema. In addition to those on screen, there's a team behind the scenes helping with the triage of all your inbound questions. We're excited to answer your questions today about all things SwiftUI. If you have code-specific questions or we somehow can't get to your question today, please bring your questions to the developer forums at developer.apple.com slash forums, where we'll continue the discussion. If you have a bug or a feature request, go to feedbackassistant.apple.com, as we'll focus on questions today that will help the broadest audience. So without further ado, let's go to your questions. So our first question today is from UJ. And UJ says, I have an architecture question. Is there an architecture like MVC or MVVM or other that the SwiftUI team expects us to adopt? Sima, do you want to start us off on that one? Yeah, so there's really no architecture that we expect you to adopt. SwiftUI is really designed to be architecture agnostic and really like any question, any architecture that works for your specific app should work with SwiftUI. And if for some reason the architecture of your choice integrates poorly with SwiftUI or like you see some ergonomic issues with like trying to like pass data around or other issues, feel free to let us know. And we will see what we can do to make sure your experience is the best possible. So, David, in your dub dub talk this year, you talked about using Observable with AppKit and UIKit and then how that makes it easy to transfer to SwiftUI. Does that have implications for choosing an architecture? You can use Observable to store your model and make it easier to transition to the new framework. But in other cases, also, it's just Swift code, essentially. It's not integrated with any UI. Like, it's integrated with UI frameworks, but it's not dependent on them. And so you can use that and just write tests for it and use it in other components and listen to changes yourself. One of the kind of core messages in this session is, like, how you can adopt SwiftUI incrementally. So one of the important things with this architecture question is you maybe have an existing app with an existing architecture. And SwiftUI needs to be able to handle whatever architecture that is, including as you start to mix and match with the AppKey UI kit, or as you start to just grow more and more SwiftUI unique code. Yeah, the other reason is that your apps can be very different. You can have a very small app for which you probably don't need to go for some elaborate architecture that requires you to refactor most of your code, and you just want to get something started really quickly. And you can also be working on a very large app where you have maybe a team committing daily to the large code base. And maybe in those cases, you want to reach for some of the other solutions that require your code to be more standardized across the entire code base. So it really just depends on the kind of app that you have. I think it's really underrated, the ability to stay nimble. So you don't want to build your app just because you have a certain architecture. You want to be able to ship features to your users. And that's what should drive all decisions, I think. Yeah, and I think that's a great point. The other thing that I think is interesting is structuring your data model just around the UI framework is maybe not sort of the leading way to approach it. You have to think about persistence. How are you syncing? Do you need a CRDT to keep simultaneous edits? So all these things drive your data model also. And so if you can keep your SwiftUI views so that it's just a projection of your data model to pixels and then design the data to be as robust and testable as you can, I think that's the best move. So another question, and I think I'll take this to you first, Jason. So apologies that this is basic. No apologies needed. We welcome all questions. But what's the difference between at view builder closure or computed property and a separate view structure? Are there performance pros and cons between these approaches? Yeah. So it's quite common that if you have like a very large view body, you'll quickly want to break it up into smaller pieces. It's just it's easier to reason about your view. And often people will create maybe a computed property that is at ViewBuilder or now at ContentBuilder to do this. And yeah, there are differences. So one of the major ones is that a view itself has its own identity. And so it can actually track dependencies. So you pass in the data that is specific to that view, pass that into this new view, and it can independently track dependencies. Whereas if you're just using the computed property, it really is in effect just like as if that was still in your view body. You've just moved it out into a separate method. But the view, a new view is entirely itself a new view. And so, yeah, maybe David, you have, or Sima have more to... Yeah, like from SwiftUI's perspective, we're just executing a function. And we do not have more granular control if the function is calling other functions. We cannot intervene there. But if it's a separate view that is created, we are the ones that call the body computed property. and so there we can scope the dependency tracking and invalidation to each view independently. Yeah, so it's really for more performance wins, you want to move some parts of your view out to a separate custom view that has its own body and that would let us more granularly update only the views that have changed. So that can really help you with performance. Could I ask, what's the trade-off there? like at what you don't want probably like every single component made into a separate view right so like what level should like someone reach for like creating like a sub view essentially this is one of the surprising things when people approach swiftui for the first time is they think okay i can't make too many views right there's like an overhead to each individual view and swiftui it's different um where like there is this performance benefit uh there's also preview benefit you can now preview things at a finer grain level. So I don't think that we have a recommendation of like, what is too small of a view to break apart if there's reasonable dependency differences. Yeah, I mean, if we tunnel into the detail level, it's like each view, because it's a struct, is allocated on the stack. And so it's very inexpensive to allocate a view, render it, and then discard it. And so the fewer pieces of state that a particular view depends on, the less likely it is to have to be redrawn. So that's where the performance win comes from. It's useful to think of your view body like a description of your views. They're very short-lived. You could even think about it as just a data model for your views. They're not going to stick around very long, so it's very lightweight. I think we often talk about how great observable is for invalidation. And if it's like only a small little piece of your big view reads that property, it invalidated the whole view. So that's like another great example of a place where you can extract out and minimize the dependencies. - Right, yeah, that makes sense. - And this also extends beyond views to modifiers, where we have the same concept where you can extract couple of modifiers into your own modifier and then get the same benefits as splitting it up into multiple views. - That's true. - Yeah, and if you're looking for advice on like, how and when to start like breaking up your views, the kind of guiding principle that I would recommend is also if you're seeing maybe a repeated pattern of the same set of modifiers that you apply across your view hierarchy, maybe you have some sort of style system in your app where there's a repeated pattern that you have, then that's a great place to say, okay, maybe this can be a custom modifier that I can just apply in various parts of my view hierarchy, and that same goes for custom views. - It's also sometimes important to have a view so that you can do things like read from the environment. Right? And so one example where this maybe trips some people up is we have an environment value called background prominence that is increased when a table or a list row is selected. And so you can use that in your view to adapt to that selection state and maybe change the color of something in the selection so that it appears better against the darker background. And if you don't create a new view, if you just have the stuff that's within the cell, you can't necessarily read from the environment. So that's another, I think, good use for doing that. So I think I'll start with you on this next question, Taylor. And especially as we see more internal adoption all the time with SwiftUI, what are some common anti-patterns we see people fall into with SwiftUI? I'll open this up to everyone, but we'll start with Taylor. That's a good question. And I think there's anti-patterns, and then there's also what's the right way to do things. A number of things come to mind. I think we have things like geometry reader, which we often talk about how it's great to analyze where you're using Geometry Reader in your app and whether or not you can use a layout instead. Layout was introduced several years ago and is a great alternative so you're not, similar to our previous discussion, invalidating an entire view hierarchy when the layout changes and instead really customizing the layout at that point for similar tools. OnChange is another similar one to watch out for. If you're using OnChange to try to trampoline data back and forth, it's a good sign. Sometimes there's a correct use. So it's not like never use OnChange. but you want to analyze are you using it in a way where there's not a better substitute. Yeah, I see that in workshops we do is that a lot of times people we use on change a lot as they're moving from an imperative UI framework to a declarative one because it's sort of comfortable imperative territory to jump to an on change. And so you need to think about whether you can make the view more declarative so it just responds to data instead of you writing the code that reacts to the data. Exactly. And like we both said, they're not wrong to use. There are appropriate uses. It's really just analyzing and making sure you're using them in the appropriate way. Anybody else have a favorite anti-pattern? I think so environment, every time you kind of refer or declare an environment property in your view, every time the environment changes, we would re-evaluate all views that kind of read that environment. So I think something to watch out for some value that's frequently changing in the environment that you're reading, and maybe some views don't even need to read that environment, and they're now suddenly getting invalidated because the environment was declared, but even if the body of that view doesn't read the environment, we still have to invalidate. So analyze usage of environment properties and see if you can remove some of them or reduce the frequency. That's another place where observable works really well. Because you can pass observable down through an observable object down through the environment. The object pointer itself is not changing. So the environment itself is stable, and your view is just reading properties from it. And so that's a great use there. I have one that's not so much an anti-pattern, I guess. But I would say going down to the level of controls or leaf views or something, I've often seen people create wrapper views around things like buttons, because they have customizations they want to make to a button that they want to use throughout their app. So they'll make my custom button view that has effectively a button with a bunch of modifiers applied to it, or styling the label within the button. And I think that's a great opportunity to explore button styles and all the other various style protocols that we have. If you're doing that, you can often just create a custom button style. And within the button style, you can actually pass the configuration right through to another button if you want to just apply some modifiers to a button. Or you can just use the buttons configuration.label and apply whatever modifiers you want there. So that's a good way of creating reusable custom buttons or any other control, really, and share that across your app. And that way you can use all of the button initializers that exist, which include all the convenience ones for localization and using system images. And you don't have to recreate those initializers. You don't have to have your custom view let you pass through a title, and then you're doing a lot of stuff that the framework can give you for free. And that's especially important, too, when you're creating something custom, because if you apply a modifier to a button, that may be different than applying it in the label. If you want to add padding to a button, you want to really do that in the label, or ideally do that with a custom button style. The cementer is something that Adi and I were talking about. the other day, which is like how much list gives you out of the box. I think another similar thing is, especially with AI tooling today, it's easier than ever to build just like a completely custom view from scratch when sometimes like using a list or using some other standard component is the right thing to do for how much it actually gives you. And as the new design has evolved this year, if you're using the built-in components, you're evolving right along with it without doing any additional work. Yeah, and often, if this has been a wrapper that you wrote maybe five or six years ago, sometimes you might not even know. You might update your app. And we've found this in some workshops where even the developer, neither us from Apple nor the developer who's trying to update their app, can seem to figure out why something isn't changing. We have to spend half an hour debugging to figure out, well, oh, is this wrapper that everyone's forgotten about? So if you can stick to stock components, it's definitely easier to update. Yeah, I think a similar thing is when it's like, oh, well, SwiftUI maybe today does not have the standard component that I really want to use. I should just rewrite it from scratch in SwiftUI, versus like we were talking earlier, the interoperability. It is so easy to bring in a UIKit or AppKit view that does what you want. That is totally recommended. That is not an anti-pattern. That is what we would recommend doing. One more anti-pattern is potentially using conditional modifiers. If you write your own modifiers and then you have an if condition, you apply one modifier and otherwise you'll turn, for example, just the view as is. And this is, I think, acceptable if you like. The condition never changes once the view has been initialized. But when the condition changes while the view is on screen, it completely recreates the view, which has all sorts of issues. The animations can break. state gets reinitialized, but also from a performance perspective is a concern. And that's a good excuse for me to call out Renz's talk on lazy stacks that was in this year's WWDC videos, where he goes into lots of things that you can do that will accidentally cause a lazy stack to update more slowly than it should. And so that's one of the ones he talks about. There's some other ones in there, and I'd encourage folks to take a look at that. Thanks for that question, Ozzy. Clearly, we have opinions on that one. So let's move on to the next one. I think we'll do this just down the line and let everyone take a crack at it. So we'll start with you, Sema. What's one concept in SwiftUI that takes many developers the longest to understand, and how would you explain that? Yeah, I think that's a good question. So the kind of the hidden part of SwiftUI the way how views update is how we kind of have like an internal representation which is like a graph and it effectively has like edges and nodes that represent individual views and the edges are like the inputs to the view and so we use that data structure internally to you know understand which parts of your hierarchy updated as a result of like a state change and I think it's important to on some level kind of be aware of that and be aware of the fact that there is some underlying mechanism that like diffs or like effectively figures out if your view has indeed changes and if so like updates its body. And I think as your app grows and as you kind of look into more like performance tooling and instruments, it's very helpful to know about that like underlying update mechanism to then reason about what is wrong with my app's performance. Why is my view updating more than I would expect it to? Updates are all about state change, not re-rendering and comparing the views. We're not diffing the DOM of an HTML hierarchy. We're actually looking at the state. So David, do you have a concept that you think people trip over? Yeah, sometimes with 4-H, when you want to filter the data, out a lot of people write like an if in the body of the forEach and that like gives you the result visually but has a problem of then SwiftUI often to figure out the total number of elements that we often need for certain operations internally we need to iterate for the whole forEach right And then this is ifs are one reason why that happens, but any views as well. - Right. - And in our docs, we have some examples of that and also a flag you can pass to debug this issue. And one of the simple solution is either filtering it up front and having returning constant number of views or for any view, putting it in an H stack or Z stack. So that makes it a constant number of views from the. Yeah, so the for each can count without doing more interpretation. Exactly. I'm going to riff off of David's anti-pattern about conditional modifiers. I think one of the things that's interesting, like mental model shift, is your view's body is a description of that view in all the states it can go across. And so coming to SwiftUI for the first time, you're often like, well, I want to make the opacity be zero, but normally it's normal. Normally it's just the default value. But most of our modifiers have what we call inert variants. So instead of thinking of it as adding and removing an opacity modifier, it's, well, the opacity is either one, it's fully opaque, or you've changed it to zero or some other value. And so you can use ternaries-- and opacity is just the simplest example. You can kind of apply this to almost any modifier. You think of it as, here's my description of all the states it can be in, and the values I pass to those modifiers is what's kind of capturing that, which even helps with things like animation. it makes it easier to animate between all these different states as well. Yeah, definitely. I also say, if you come across anything that doesn't have this inert version that lets you do this, definitely file feedback for that. Feedbackassistant.apple.com. Good plug. I think, so going, also riffing off the idea that views are a description, Something I often see is people referring to the view body being re-evaluated as re-rendering or re-drawing. But that's not always the case. It's really just like your body can be re-evaluated, but it doesn't necessarily mean that something needs to be re-jawed or rendered again. SwiftUI will also handle that. So that's something I've just seen often. I think that, especially if you come from another framework, it's easy to think about it, that the body is going to all, like re-evaluating the body is always going to cause this big task to happen. Yeah, yeah. Aditya, I know you come with a deep UI kit understanding and background. Is there anything about SwiftUI where the impedance difference there is? Yeah, I mean, for me, as someone who has been writing UIKit for more than a decade, which sounds pretty wild to say. The biggest change for me, it's something I understood or know, but it's hard to internalize, is that UIKit lays out and arranges and renders views top down. So we'll start at the outside, the window, and go down to the smallest child and have some loops as needed, where a Swift UI tends to do the other way around. It'll start at the innermost node and build outwards. And so sometimes, especially if you're working in a sandwich or a cake. - A sandwich or a cake, Adich means that you're using interop and you have layers for alternating. - It's something to try to keep in mind that there's a slight difference in approach in both frameworks. And so you might want to consider how your data flows and how you decide to lay out and invalidate state to take this difference into account. Yeah, that's one of the things that was challenging for me when I approached it, too. It's like the layout system is kind of like all the sub-user asked, well, how big are you? And the sub-user asked, well, how big do you want to be? Until we get down to the leaf nodes, like, oh, I want to be this big. And then that sort of flows back up. There's a really great video from Paul a few years ago. He discusses this, and I found that to be very helpful in understanding the layout system. Yeah, it's a video on custom layout, I believe, from WW23. Probably. You would know. I think I was kind of busy that year, but, yeah, I think that was the year I did what's doing SwiftUI. I guess so. So I think we'll go back to you on this one, Adi. This is from Tyler. And the question, in UIKit, the concept of cell reuse was fundamental to optimizing scrolling content. In SwiftUI, are lazy stacks and grids sufficient for infinitely scrolling grid layout? Is there guidance to drop down to hosted SwiftUI and UI collection view in these cases? What's the right way to think about that sort of issue? Yeah. I mean, I think the primary reason UIKit was using reuse was because views are allocated and stored on the heap. So there's like this fixed cost that you have to pay up front no matter what you're doing with your views. So like cell reuse was primarily to get around the fact that, well, you have to instantiate and allocate memory for each one of your rows or cells. and then all of them come with their overhead, adding them and removing from the hierarchy, excuse me, have their own overhead. SwiftUI doesn't have the same issue necessarily. Sorry, excuse me. While I can take some drinks, does anyone else want to jump in on that? Yeah, like SwiftUI has also some benefits. It has some implicit prefetching. So while you're scrolling in one direction, we look in what cells come on screen next, and we start evaluating those few bodies. And we have, because our graph is made up of individual nodes, we have the concept of partially evaluating that. And so while we have rendered the current frame, there's sometimes some time left. And we reuse that to continue to evaluate the graph for the next cell and then stop right before the next frame begins. Nerding out, that is one of the coolest things about this graph model is that you can do this partial evaluation and then sort of say, hey, we're out of time for this frame. We can keep it up the next one. Yeah. It's really cool. And again, Renz's talk has this great animation of how this process works. And this actually gets at a perf thing that I thought was really interesting. Sort of I learned while working with Renz on this session where if you've got an on a peer that reconfigures the state of a cell that causes it to need to be laid out again because it changes the size, you actually throw away all that pre-work that was done in prior frames. And so it's really important to try to get that work done in init, not in body or in on a peer so that you get it done earlier. It's kind of similar to our discussion about on change. There's sometimes a good use case for on a peer, but as much as you can avoid it and have your data model handle it or otherwise. It's great. The reason for both of on change and on a peer is that both of them have to go into the view body, and so we have to recompute the entire view body in order to execute those closures. All right, excellent. So the next question I think is super interesting. It's very pertinent to this year. The change from view builder and other builders to content builder looks great. Thank you. I mean, I didn't do it. Thank you. I'm fascinated how it works now and why this improved type checking performance, and could you say a little bit more about how that change was thought about and created? Maybe Sima, you want to jump on that? SIMA SINGH: I can get started on that one. First, I wanted to call out what's new SwiftUI talk. It has a great visualization of the type checking work that needed to be done before this change, and now how it changed with Content Builder, where essentially the compiler needs to consider less overloads when it's like trying to type check your view body code. And that was causing-- that type checking work was causing the notorious expression is too complex to type checker that we probably, most of you, have encountered. And this year, we did so much work to improve the type checking story there. And that involved us moving away to this new content builder type where essentially it let us collapse the amount of overloads we had for each custom type, for each group and section, which used to have each, all of their own ViewBuilder overloads. Now we only have one. And I think it's a great improvement, and hopefully a long awaited one, because I feel like even all of us here have dealt with the type checking problem. We've even limited our API in some cases because of this. Yeah, we had a ceiling, and we were like, oh, we can't introduce more because it's going to work. 4each only worked in some types because we couldn't add it to any more places. Steven showed a great analogy when we were working on the What's New session that in the past you could think of like a view with lots of groups and sections and for each. It was sort of like a cave and the type checker had to explore every path through it and then it could find the path out. And we've made it a hallway. It's content builder all the levels down until you know that it's a view. I think that's a great analogy. And it backports all the way to the beginning of SwiftUI because it's effectively a type alias to ViewBuilder, and then ViewBuilder got a lot smarter. Excellent. That's one of my favorite things, so thanks for that question. Oh, this one's great. This is a throwback to the SwiftUI group lab last year, I think. SwiftUI's most loved debugging tool is self.printchanges, underscore included. When your teams hit a mystery re-render storm, maybe I'll start with you on this one, Jason. What's the real workflow? How do you debug why re-rendering is happening? Is it instrument? Is it underbar print changes? Is it some mystery? technique that we don't share with the world? Yeah, so I'm assuming they're asking, like, what do we do internally? And it's really, yeah, all of those above, too. Another thing I've often seen happen or people do is, like, do you have a color that just is, like, random? So you can very easily see if your, like, body is reevaluating. Maybe on resize, maybe something's evaluating. You're not expecting that to happen. And it's also a good way of visualizing, I think, the differences that we talked about in the first question or the second question. I can't remember now. But of, you know, having the computer property view builder versus the separate view, that's a good way to visualize that. Does anybody have any? No, because that means you can sort of like add these print changes. There's also underbar log changes, which uses OS logging. So a good tip in case you ever stumble across that. But yeah, you can sort of add it at these different layers and see like, okay, well, it's this small little view changing. Sometimes you'll see like self changed. And sometimes that can feel like a mystery of what that means. It's really just like the value of that view has changed. And that often means you just need to crawl up to the next level and see why that parent view changed. I think this is like a great reason to use actually the SwiftUI instrument. The instrument might seem as though it's mostly for optimizing performance, but it's also a really good debugging tool. If you can reproduce your issue and capture trace, you can figure out exactly what inputs cause something to reevaluate. So if you have a hunch that, oh, I think something here changed, but I don't know what, it's actually great because you get this giant graph of exactly what caused your body to reevaluate, which can be sometimes a little simpler than trying to use print changes. And especially given the strength of agents now, you can throw a trace or a crash report also at an agent. And sometimes the problem is one line in 1,000 or 10,000 lines, and it's hard for a human to see that. We get bored reading that, right? But the agent doesn't get bored. Sometimes it even notices the one thing you overlooked. I've had this happen a few times when we've been investigating things. It's like, oh my gosh, how did it find that one? I often use the SwiftUI instrument. It also has a way to represent the view hierarchy as a tree. And then it can draw a flame graph that is weighted by how much time we spend in each view. But I also just like to look at it if I want to understand what an app does. I often look in our first-party apps and see where there are performance problems. Recently, I was looking into the TV app, and I noticed that we update sliders. But if I looked at the screen, there are no sliders. And so this was something interesting to look at. This is often like if you have an intuition of what you would expect from an app or look at the app and see there are things updating that are not even on screen or shouldn't update at that point in time, this is really a good indication that there is something that is worth investigating. Right, and either there's a bug in the app or in your understanding of it, and other things are good things to fix. Exactly. So there's a follow-up to this one. Why the underscore still? I think that's just really a signal to not ship this. Like, remove this when you're done with it. Don't submit to the App Store with this. You don't want your user's console to be filled up with all these print chains. Yeah, and there's a little bit of performance overhead of printing these changes, right? It's not a heavy optimized path. Right, the string has to be generated. It has to be written out. Yeah, exactly. Excellent, thanks. So I think we've talked about this next question already, but it's... Actually, I think I'm going to go past this one because we opened with it about architecture and move on to the next one. Oh, this is a great question. And maybe I'll start with you on this one, Seema. So nice to see the compilation improvements with at Content Builder. Is there any reason to continue using at View Builder? Is there a reason to choose between those? That's a great question. So there's probably some confusion around those two names. So Content Builder and View Builder are actually the same. It's just the type alias. but something that Content Builder would allow you to, it's now possible to use it outside of your view. So you can have custom SwiftUI-like, if I can say, like DSLs, where you can create your own building blocks that aren't necessarily SwiftUI views. So we're kind of opening up that, all of it to the outside, which is-- - Oh, interesting, interesting. I hadn't thought of that. Anybody have anything to add to that? That's a good description. I learned something, so thanks for that question. I love it. All right, so this question is another architecture one, but a different take on it. So a person asked, I've used Swift and MVC for over a year, but SwiftUI's property wrappers feel like opaque magic. So my worry is if I don't fully grasp how at-state manages lifecycle under the hood, how will I safely adopt Swift concurrency? So I think concurrency means I'm going to start with you, Seema. But what's the mental model for connecting at-state and state management and concurrency, and how should I think about those together? That's a great question. So in SwiftUI, we really try to make it as simple as possible to adopt concurrency in your code. So views are main actors. So it means everything that's declared in your views, all of your state property wrappers, will also be main actor isolated. And that makes it really easy to work with. The one problem that you can encounter, like an issue, that also actually we did cover in last year's WT talks is some closures in SwiftUI are sendable. And it actually means that as an optimization, we would execute them from off main thread and we annotate those closures without sendable to kind of signal that. And so you'll notice sometimes when you're trying to use your state values, you'll get some concurrency errors. And the reason to that is you basically want to kind of use the copy of the value in the state because the value of the state property wrapper or macro now is actually conditionally sendable when the value is sendable. So there is a literal, not trick, but a way you can use those values is by basically declaring an explicit capture list in the closure and kind of copying over the values. And that's just going to tell Swift compiler that, hey, just use the copy of this state value in this closure. But these closures are really rare. In SwiftUI, most of our closures are main actors, so you shouldn't encounter any of this normally. But, yeah, that's like the only thing I can think of where like you can run into maybe like a concurrency issue with State. Yeah. Daniel did a great – well, Daniel and Seema both did great talks on concurrency and SwiftUI last year. And Daniel's talk in particular talked about the sendable closures. And the one place where I thought it was interesting that those were used is like for animations, where it's something that has to be done at frame rate. And so it takes it out of that main rendering loop, which is nice. All right. Thanks. Oh, this is interesting. So Taylor, I'm going to start with you on this one. Yeah. And then maybe you'll throw back to me. We'll see. But what's the recommended way for implementing navigation in an iOS app in iOS 27 if custom transitions are required? Swift UI? UIKit plus SwiftUI? Yeah, that's a great question. And it really ultimately depends on the needs of your app and the user experience you're trying to deliver. There is, since you asked about iOS 27, there is an API for customizing navigation transitions in SwiftUI this year, like with built-in support for crossfades and other custom ones. So if those fit your needs, I think iOS 27 has your back. If you're trying to backdeploy, that's maybe where you have a different question if you need to support releases before this API was introduced. And we mentioned interop a few times. I think it is totally expected that if the experience you need is something that UIKit can deliver in the release you're trying to deliver for, you should feel free to pull that in, mix and match both ways, as our layer cake example called out. So hopefully that answers your question. And it's kind of a case-by-case basis based on-- Anybody have anything to add to that? I think this is a good question for the forums. And I believe tomorrow there's a SwiftUI Q&A happening on the forums. Providing recommendations for navigation, it's difficult because it's highly dependent on what your app is and what your goals are. So I think if you can bring a more specific question to the forums, you'll probably get an answer that's better tailored for your situation. I think another important thing to think about there, and this was emphasized in the State of the Union on Monday, and please watch it if you haven't, but resizability of apps is important. iPhone apps can resize on iPad. In the iPhone mirroring, they can resize. And so as much as you can stick with one of the system components for your navigation, whether that's UIKit or AppKit or SwiftUI, try to do that, because those are going to support that resizing, that adapting for you. You'll have a lot more work to do if you have to support it and you're trying to actually build your own custom thing. All right, the next question is about scroll views. Maybe we'll start with you on this one, David. Sure. So what's the recommended way to track a scroll view's scrolling offset? The goal is to show certain view elements based on how far the user has scrolled. Yeah. So we have different APIs depending on what you exactly need, like the on-genomology change modifier. We also have scroll effects that also take away a little bit of work that you need to do but also are more efficient in certain situations. And generally, we have the scroll position modifier as well to see which item is currently on screen. Right. And there's a really interesting API that I can't remember the name of, I'm afraid, but Renz talks about it in his talk, where you can track what percent of a view is visible. And this is super useful if you're doing analytics and you want to see, okay, this ad or this impression was on screen or fully on screen or whatever. But you can apply that to the first or last view in a scroll view. And now when that transitions to 80% visible, use that to trigger the update instead of trying to track the content offset. Because the content offset is actually estimated based on the estimated heights of the views that aren't in the viewport, in the visible range. So I thought that was a really interesting application. Yes. Yeah, I'd really like to emphasize, like the content offset is, especially when you combine with lazy stacks, is essentially should be treated as an implementation detail. and it doesn't really have any semantic meaning. You should try to focus on something relative to the views that are on screen. Go ahead. You were going to jump in? Oh, I'm sorry. The scroll position modifier, I think it takes also an ID and not an offset. Right. And so this allows you to also be more relative to what your data model is. Yeah. And that actually reminds me, and it's, I guess, sort of unrelated, but maybe tied up in the question, it often comes up when people are trying to create an experience where more views are paged in as you go farther. And so instead of looking at scroll position or content offset, you can add a view at the bottom of the list that that view actually has an on a peer, which then tells your networking layer to go fetch more data and then updates the data model, And that's a way you can implement infinite scrolling lists by paging based on the last view becoming visible, which is kind of cool. Yeah. All right, so this is a-- I guess it's sort of an API design question. So maybe I'll start with you on this one, Taylor. The question, is there a way to build custom layouts that are lazy similar to lazy VStack? TAYLOR CHOE: That's a good question. And we were just talking about different types of lazy views. So today, no, there is no protocol for describing a custom lazy layout specifically. So please do file feedbacks like we were talking about earlier through Feedback Assistant that you'd like to see that in a little bit more detail about your use case. So today, this would be a great example where it may be appropriate, depending on your layout, to interoperate and bring in a UI collection view if your use case needs that. Or we have a suite of other lazy layouts, lazy grids, lazy stacks, et cetera. But, you know, behind your question was also, you know, there is a protocol for non-lazy layouts. And so if you don't require that laziness, I do recommend checking that out. That's a great tool for, you know, building a custom layout that is much more optimized than trying to, say, like, arrange things with a geometry reader and offsets and things like that. I think the other thing to think about there is can you compose those? Can you use one of our lazy layouts and then a custom layout within that for a sub piece of it? And so you get the laziness from the built-in component and then the custom layout from your component. That's not always going to be possible. When appropriate is great. I've seen some pretty cool mosaic layouts built that way that look a lot more complex than a simple stack or grid but are basically built using that technique. And I think that lets us do a nice transition here to another performance question. We'll start with you, David. What's the most common mistake you see developers make in large SwiftUI apps that ends up hurting performance? Yeah, usually it's, well, often it's too many invalidations that tickle down, maybe start from the top and then cause invalidations downstream. One is if you have something that you put up in the environment at the very top that is read something downstream or by a lot of views, If that's updating too often, like a very bad example would be to put in the time in milliseconds. Have we seen that? We have seen the scroll position, for example, being put into environment, which also updates every frame, potentially every frame while you're scrolling. Not the time yet. Another one is if you use a value type that you have high ARP in your view hierarchy and pass down through many views, and some views maybe just really don't use it but just forward it, we still need to evaluate those views to pass it to the next view. And so using observable macro helps here because the reference stays the same. You just pass it down, And then only at the views that are actually accessing any properties, there we track the invalidation and update those. Right. Yeah. And to put a little more color on that, a complex value type that is getting passed down level by level, you have to do a quality comparison on it at each level. Yes. And so the difference there is that the observable we just compared that the pointer is unchanged. And so that's where actually maybe somewhat surprising to folks who have really gotten into Swift and value types. There are cases where a reference type is the right choice, and that's why Observable is a reference type. And they can also be put into environment, and then you can indirectly update something more frequently. It is technically in the environment, but the environment itself is not really changing. Right, static. Yeah, exactly. I mean, I think we talked about how much users love smooth scrolling. and these tips and tricks for scroll views and lists. Another thing is resizability. On Mac, iPad, and now even iPhone, you can resize your app, and that's another place where users really expect a really smooth experience. And so that's another place where we've kind of talked about, hey, try to avoid Geometry Reader, because using Geometry Reader means on every single frame as you resize, we are updating that view's body. It's another great place where you can kind of watch out for. You want to have as few views updating as possible during the resize, only when it's actually really necessary. Otherwise, keep it completely scoped just to the layout. Yeah, and this is another place to bring in the thing Jason mentioned of putting a border or a background or transparent overlay that's in color, and then if you resize your scroll and it's suddenly a disco dance party, like you've got a lot of invalidations going on. Yes. Also avoiding large view bodies, as our scope of invalidation is the view body and everything that the view body is calling. If you can split that up, especially if the view body or something you're accessing in the view body updates often, that also really helps. So the next one eventually becomes a question about threads in isolation. So I'm going to start with you, Sima. So do ad observable properties-- and ad observable is a type, really. So maybe it's state properties holding ad observables-- automatically get allocated on the right thread? Or is it recommended that we explicitly make these types at main actor if the UI is going to reference them? Are there some best practices on how to decide where the state should live? - Yeah, so as I mentioned before, Swift UIs we use are at main actor, and this means that currently at runtime, we would call your buddies and your views on the main thread. And so that means that any time you put an observable class declared in state, then it's going to automatically get allocated in the main thread. Now with approachable concurrency settings, and I think for new apps, we're now setting the isolation to main actor. So main actor is going to be inferred for everything, including your custom observable classes. And I know before that setting, it used to be the case where if you were to adopt Swift 6, you would need to manually annotate your observable classes with that main actor for them to be usable from your UI code. So now you don't need to do that if you set the approachable concurrency setting and also the main isolation set to main actor. Both of these settings make it really easier to integrate observable classes with SwiftUI views. Anybody have anything to add to that? Suma is our concurrency expert. Suma covered that great. We're not going to contradict SEMA's expertise, that's for sure. I would add that because your state can also be accessed on the main thread, you also usually want atomic updates, so not coming down like you update part of your observable and that's already rendered while you're still running on the background thread, is that I would factor out the background computation into a separate function and then await the result. And once it's done, jump back to the main thread and then update the. MARIUSZ GASIEWSKI: That's a great point. And that also goes back to the updates to state in your view should be mostly synchronous so that SwiftUI can produce better animations. And you don't experience any hitches or weirdness when your app is running. So yeah, I think it's a really good point. RICK VISCOMI: Yeah, because at the user experience level, instead of seeing like we've probably all seen different apps that have these like glitchy things where it's like things appear like back to back to back really quickly versus, you know, waiting a tiny bit longer and having this much more continuous smooth experience. Yeah, it's amazing to me how much of those things is actually, it's feel based. And you need to test on device with the release build at least some of the time to really feel what those things are like because that intuitive sense of quality that comes from something updating crisply is so important. Oh, I also wanted to mention the timeline view. If you need something that updates on every frame in your app, timeline view can be a great API to adopt because it would update on every frame, and I think it's an optimized way to deliver that kind of experience. And that is a great chance for me to direct folks towards Haoqian's session this year at DubDub on advanced graphics and animation, which has the best explanation of shaders that I've personally ever seen and the best explanation of alignment guides. But Haoqian puts a shader inside a timeline view and gives this gorgeous flowing background on a sample podcast app. I could just watch that animation all day. It's so good. My favorite SwiftUI feature is that you can integrate custom shaders. Like, this is really rare in any other framework. Yeah. Yep. And then those shaders can... You hit one of my favorite things. Those shaders can be used as foreground or background styles, which means you can use a shader to render the glyphs of text in your app, and then you can animate it and have the gradient flowing through the text, and it's so fun. Then you throw in a text render, and then they can move around. And the sizes can change. You can make some pretty amazing cool stuff. Yeah, yeah. So that's why I had a thought. Now you get a sense of what we all do in our spare time. All right. So this is another kind of architecture question, but it's sort of a design system kind of architecture. And maybe, Jason, I'll let you take a first crack at this one. So the question is, in our multi-module app, so an app that's made up of distinct modules, we return any view from protocols to hide the concrete view types and avoid cross-module dependencies. Is there a more idiomatic way to do this without any view? For example, should we be using at view builder closures or any view existentials, or what's the approach that we should be looking at here? Yeah, so definitely use some view if you can for your use case. that will handle that better. If that's not possible, any view is fine, as long as the underlying type isn't changing. So just try to avoid dynamic changes to the type, which I think also falls in line with a lot of the advice that we've already heard about avoiding conditionals. I'm curious, have you seen any patterns of this being done well or poorly? Yeah, so one issue is that we do not know, don't have static information about this type, right? And so for each relies on that, for example, to get the static count of the view that we've heard from the body closure. And so for any view, because the type can actually change, we can never be sure about any static information we gather from that. And there, like, a good thing to do is put it in a H stack or a Z stack to then communicate to Swift UIs for each that it only ever returns a single view. That's a good one. It can tap into a better performance there. And in our APIs, the APIs in Swift UI, we're almost always returning some view, and you'll see that referred to as an opaque type. But the type checker actually knows what type it is without having to expose that type to the people using that. And so that's a way to hide the type but still give the type checker the information it needs and give our internal systems, like, clear knowledge that it's a static type. So instead of swap, instead of, you know, Jason said it's totally true. Like, if you keep the value that's returned and wrapped in any view consistent, well, that can be performant. But if it's a sum view, then we know that you're doing that. Like, the compiler will catch you if you try to change it. Lots of great performance questions today, so I appreciate that. So Taylor, I think I'll start this one with you. What are the performance tradeoffs, speed and memory, of using a compositing group or a drawing group? This is some of the stuff that's been there since early on. Since the very beginning, yeah. And that's a good question because, yeah, There's lots of these something-something group. So compositing group is not strictly about performance. It's really about how you can apply visual effects. I think one of the things that we were talking earlier about what surprises people is if you create this kind of structure of views with like a Z stack and things, and you apply a shadow to the overall thing, if you're used to other UI frameworks, you might think, OK, well, I'm just applying a single shadow. But actually what it's doing is it's applying a shadow to each individual visual element, which is sometimes what you want, but sometimes you do just want to say, hey, I want to take the overall visual result and apply a shadow to that. So compositing group is great for that. It's less about performance and more about really fine-tuning the visual result you want. Versus drawing group, I remember this was, I think, one of the very first, this was the very first year SwiftUI was introduced. We had this great example where it was like this kind of pie chart-esque visual, and it was showing how far you could push SwiftUI to build these custom graphics. And you could get pretty far, but at a certain point, And it was just like there was just too many individual views being rendered, not SwiftUI views, but on-screen layer representations of the views. And so what Drawing Group does is it takes all of the rendered layers that would have been drawn and puts them onto a single one. And so in that example, you saw the performance suddenly shoot back up. Because even though there were the same exact number of SwiftUI views updating, it was rendering to a single drawing layer. So that's a great tool when you have that kind of setup. And the cool thing is, like, all of the same other APIs you'd expect from SwiftUI work. So gestures work the same way, et cetera. So it's, like, a great tool for these very rich interactive experiences that otherwise would have a lot of layers. I think it wasn't mentioned in the question, but Canvas is kind of similar in that way. Do you want to explain what Canvas is? Oh, yeah. So, Canvas is, if you have ever used UIKit or AppKit, there is a drawRect method. It's very similar to that, where you get more control over each individual drawing invocation and can compose them in very interesting ways. If you want, instead of this way that you describe the view, you want more direct control and want to invoke the functions. Yeah. I'll take this opportunity to shout out David's dub dub talk this year where he uses Canvas to build a very unique color picker. And I will call out that if you're watching that talk, look at the code snippets because the code for drawing that is in the code snippets of the talk even though we didn't share it in these slides. That's cool. And I mean, the one trade-off with Canvas is those are not SwiftUI views being drawn. And so you can't attach sub gestures to each individual piece, for example. Right. But there you will also see in the code that I have one drag gesture that applies to the overall slider. And there are three sub-sliders. And so I need to do some math to recognize where I'm starting the drag from. Accessibility is kind of in a similar vein where you would need to use a proper thing. But that's a good plug for accessibility representation, one of my favorite APIs. Yeah. Because it's so cool where you can say, hey, here's my overall canvas. But actually, it's represented by three sliders. You don't need to think about how that fake accessibility hierarchy looks like. It is three sliders. And I'm afraid, despite having lots of questions that we didn't get to-- and so thank you for those-- that we've reached the end of this group lab. We do have another SwiftUI group lab at 7:00 PM Pacific time tomorrow evening. So we'd love to have you back for that. We're so thankful that you joined us today. A big thanks to our panelists and to all the folks working hard scenes to make this happen today. If we didn't get to your questions, come back tomorrow evening, or visit the developer forums at developer.apple.com slash forums. You can try out the new AI search experience at developer.apple.com to get answers about frameworks, designs, accounts, and everything related to development on our platform. And of course, as we mentioned, please go to feedback assistant.apple.com to file any bugs or feature requests. Speaking of feedback, you'll receive an email with a survey link to let us know about your experience with WWDC. We'd love to incorporate your feedback into future events. Again, thanks for joining us. I hope you have a great WWDC.