Issue #918

Interesting SwiftUI Q&A during WWDC23

Observable vs ObservableObject

Q: With the new SwiftUI @Observable macro, are there any cases where ObservableObject would still be a better alternative?

A: Use ObservableObject when you need to back deploy to before iOS 17

A: SwiftUI registers dependencies is a value is read during the evaluation of body. Indirect modifications should invalidate the dependencies.

containerRelativeFrame in ScrollView

Q: For the containerRelativeFrame, does that work cleanly in a List as well as a ScrollView?

A: Yes, a List establishes a container relative frame like ScrollView. You can see the docs for this modifier for more examples of what other views establish a container relative frame:

Inject ViewModel as StateObject

Q: The most perfomant way i found to do injection of viewModels while keeping the @StateObject performance is to use this specific syntax. Is there a better way of achieving property injection into state objects?

class Bar {}

class FooVM: ObservableObject {
    let externalDependency: Bar

    init(externalDependency: Bar) {
        self.externalDependency = externalDependency
    }
}

struct FooView: View {
    @StateObject private var viewModel: FooVM

    init(viewModel: @autoclosure @escaping () -> FooVM) {
        self._viewModel = StateObject(wrappedValue: viewModel())
    }

    var body: some View {
        Text("Hello World")
    }

}

A: The is the correct (and only) syntax available to initializer a property wrapper in the init of a type

Observable MainActor

Q: Does the new @Observable wrapper automatically apply @MainActor - we’ve been using MainActor on our ViewModel classes to ensure @Published vars are updated on main.

A: @Observable does not require @MainActor, though it’s still best practice to always update UI-related models on the main actor. Updating @Observable model properties on non-main actors that are used by SwiftUI views will trigger SwiftUI view updates as expected, however those updates may not work correctly with animations and/or may not guarantee atomicity for a set of model updates relative to a single view update. So there are cases where background updates to @Observable models will work just fine, but for “view models” or models heavily used directly by SwiftUI views, having those aligned to the main actor is a best practice.

Default keyboard shortcuts List

Q: Is there any documentation that states which default keyboard shortcuts are utilized by SwiftUI components? For example when using a list sidebar on macOS, typing a letter will by default select the list item that starts with the same letter. Sometimes it’s desired to deactivate this behavior.

A: There is the new typeSelectEquivalent(_:) API that can be used to either customize or disable (with an empty string) the type select behavior for Lists and Tables:

Own state for Observable

Q: With the old property wrappers, @ObservedObject was used when a view did not own its state (and the ObservedObject was often injected into the view via constructor), while @StateObject was used when a view did own its state (and the StateObject could just be constructed there). How do these ownership models translate to the new @Observable macro? Is it the case that @State is used if the view owns its state, and a plain old property is used if the view doesn’t?

A: That’s exactly right

Macro backward compatible

Q: Could you give any examples of Macros that are backwards compatible? For example, is #Preview?

A: n general if the code generated by a macro is backwards compatible, then the macro itself is. #Preview is not backward compatible in seed 1.

List sidebar accent color

Q: When using a list with selection as a sidebar on macOS the highlight color seems to be the current accent color. Is there any way to override this highlight color or change the accent color locally for the list? I’ve noticed that many other apps use a color different to the accent color.

A: Hi, currently the sidebar will follow the app-wide accent color - by ‘change accent color locally for the list’ do you mean you want the List to have its own accent color that is different from the one in app?

A: There are some focus-related problems when mixing SwiftUI and AppKit/UIKit views, but in general I’m not seeing the kind of problem you’re describing. For instance, this results in circles with circular focus rings, whether or not keyboard navigation is enabled system wide (on older versions of macOS, focusable only applies when keyboard navigation is enabled):

struct ContentView: View {
    var body: some View {
        HStack {
            ForEach(0..<5) { _ in
                Circle()
                    .focusable()
            }
        }
    }
}

State autoclosure

Q: Follow-up question about data ownership and @Observable! StateObject has an init(wrappedValue:) initializer which can be carefully used for data dependency injection, with all the usual caveats about how it will not be updated if that data changes because it’s an autoclosure, etc. Is there an analog…

A: State gained an autoclosure version of the initializer for @Observable objects.

No Bindable need

Q: what I mean is that the following code works just fine

@Observable
class Model {
    var count = 0
}

struct ContentView: View {
    
    @State var model = Model()
    
    var body: some View {
        VStack {
            Text("count = \(model.count)")
            ModelView2(model: $model) // Binding
        }
    }
    
}

struct ModelView1: View {
    
    @Bindable var model: Model
    
    var body: some View {
        Stepper(value: $model.count) {
            EmptyView()
        }
    }
    
}

A: Yes, it’s fully supported to use @Observable models with @State and create bindings from that! No @Bindable necessary in that case. @Bindable is to help provide the same kinds of conveniences as @State in cases where @State isn’t appropriate, such as referencing an object in a view that’s not created/owned by that view.

There is a subtle distinction between a Binding and a Bindable — A Binding means we have a writable reference to the object, i.e. we could swap out that object with an entirely new object. A Bindable allows creating bindings to properties of an object, but you can’t get a read-write binding to the reference of the object itself. For example, ModelView1 in your example can’t do something like self.model = newModel or get a Binding to $model itself.

Q: Hi! How does NavigationSplitView decides when to reload the detailView? I want to be able to persist multiple navigation path like what Apple News does. Is it possible with NavigationSplitView?

A: A NavigationSplitView with a NavigationStack in the detail updates the detail when the selection changes in the sidebar or the path changes in the detail. What you want is a bit tricky, because changing the selection pops the stack.

A: One is to use an onChange modifier to detect the selection change, then update the path of the detail using a DispatchQueue.main.async to delay the path change.

A: If you can require iOS 17, take a look at the new preferredTopColumn argument to NavigationSplitView, which let’s you control what happens when a split view is shown on iPhone.

Keyframe animation duration

Q: In animation packages a cubic bezier keyframe occurs at a point in time with handles on the left and right. With KeyframeTrack it seems like keyframes have a duration and instead handle the tail of the previous point and the start of the next. I haven’t tested yet but I’m wondering if this would make it more difficult to create smooth transitions from one keyframe to the next. Am I thinking about this right? Is there some kind of smoothing going on at the transition between keyframes?

A: Great question. Similar to the earlier point about the use of duration instead of absolute timestamps, there are some differences between this API and keyframe editors in visual animation editors. In SwiftUI, each keyframe defines the curve to the keyframe’s value, which works well when mixing different kinds of keyframes.

Each keyframe preserves velocity when possible. If you use a sequence of CubicKeyframes, the automatic velocity across each keyframe will give you a Catmull-Rom spline. If you specify a custom end velocity on a one keyframe, then follow it with another cubic keyframe that does not specify a start velocity, the interpolation curve to the second keyframe will automatically use the custom velocity that you specified at the beginning. The framework will always do the ‘best thing’ by automatically choosing a continuous velocity across keyframe boundaries when possible.

ScrollView scroll state

Q: I’ve noticed that SwiftUI.ScrollView maintains @State as views are scrolled offscreen even when using Lazy* views. That’s probably the correct behavior in most cases. But for very long lists, things like image assets and just general state can build up over time leading to performance and memory issues. What’s the best way to evict memory in this case?

A: Views in and of themselves generally do not have large memory footprint. If a view is holding onto something large, like an image, you could try nil’ing out the state when the view scrolls offscreen using onDisappear or keeping the image in an LRU cache that automatically evicts things stale items (then the view would only have to hold onto the key)

Stack 10 views limit

Q: Just curios Is there any chance the 10 view limit in a stack gets reviewed in future versions?

A: @ViewBuilder currently still has that limit. However, note that there are a few different ways to work around that syntactic limit in your own code. For example, you can include a Group of views, which only takes up one “slot” in a builder:

VStack {
    ContentView1()
    ContentView2()
    ...
    ContentView9()
    Group {
        ContentView1()
        ContentView2()
        ...
        ContentView9()
        Group {
            // etc, no limit on nesting!
        }
    }
}

Or, if it’s more convenient, you can use a @ViewBuilder-annotated helper variable, property, or function in place of Group for the same trick:

VStack {
    ContentView1()
    ContentView2()
    ...
    ContentView9()
    additionalContent
}
...
@ViewBuilder var additionalContent: some View {
    ContentView1()
    ContentView2()
    ...
    ContentView9()
    // etc
}

Improve build time SwiftUI Preview

Q: Do you have any general guidelines for improving build times when working with SwiftUI previews?

A: Good question – previews can be a great way to iterate on animations using some of these new APIs, and keeping previews fast makes that easier. I usually start by using the build log in Xcode to look at which operations take the most time during a build.

For smaller projects, sometimes that can be a file that uses complex generics, or some other language feature that takes more time during compilation. As the project grows, I also like to split off UI components into their own module (sometimes using a Swift Package) to make sure that I’m only building the smallest possible portion of my project while using previews and building animations.

Keyframe animation then loop

Q: Is there a way using phase or keyframe animations to have an animation that starts off doing one thing then continues with a looping animation?

A: The modifier loops over the phases that you specify in a collection – and you can choose which type of Collection to use. You could even build a custom Collection that dynamically returns phases in any order that you want, if you want full control

Xcode Preview build all targets

Q: Why does an Xcode Preview build ALL test targets that the current scheme depends on?

A: reviews does a best-effort attempt to avoid targets that aren’t necessary for the preview. Sounds like your situation is unique and will be best diagnosed by looking at the previews diagnostics. You submit them through the Feedback app, and if you post the Feedback ID here in this thread we can try to take a look during the session.

scrollPosition vs ScrollViewReader

Q: Is the new operator .scrollPosition(id:) intended to actually scroll to a new position? In other words, is ScrollViewReader deprecated?

A: You can use the scrollPosition(id:) modifier to accomplish similar kinds of things you can do with the ScrollViewReader API and it does additional things that ScrollViewReader can’t do. But you might still want to use a ScrollViewReader for scrolling to views in List and Table. Or if you want more fine grain control over scrolling to nested views within non-lazy stacks.

Swipe gesture for List

Q: There doesn’t seem to be a good swiping gesture solution for List or ScrollView in swiftUI. Either swiping left and right in List doesn’t customize the background of the button, etc., or swiping left and right in scrollview using DragGesture conflicts with scrollview gestures, such as stuck and delayed. How is it possible in the Diary app for iOS 17 to swipe to the right to show the Favorites button, and the swipe gesture does not conflict with the vertical gesture for List or scrollview? Or is it implemented in UIKit? Is it also necessary to face and solve these gesture problems in spatial-computing?

A: Hi! You could try use the .swipeActions modifier on the row view to add buttons and customize the background appearance of the button using .tint. Is there a specific goal you want to achieve here?

A: Thanks for the image! That helps us to understand the issue. Right now swipe actions follows platform conventions so it might not be obvious to have a customize swipe button. I would suggest filing a feedback so we could keep track of this feature request!

Create binding from Environment Observable

Q: When you access an @Observable object via the @Environment property wrapper, is it possible to create bindings to its properties? Using @Bindable doesn’t work here, right?

@Observable @MainActor final class MyObservableModel {
    var name: String = ""
}

struct ContentView: View {
    @Environment(MyObservableModel.self) var model
    
    var body: some View {
        TextField("Name", text: $model.name) // Does not compile
    }
}

A: You can derived a @Bindable from your view body in this case

 var body: some View {
        @Bindable var model = model // add this line
        TextField("Name", text: $model.name) // works!
    }

Q: Why can’t this happen automatically? A: For that to work, we’ll need an API that adds a projectedValue to @Environment.

Get ScrollView contentOffset

Q: What is the best way to get ScrollView’s contentOffset? Is below code right way to get the contentOffset?

A: There’s not an API that provides you a binding to a CGPoint as a scroll view’s content offset. However, there are several new APIs that allow you to transform views based on its relative position within a ScrollView. The scrollTransition() API lets you apply customizations like scale, offset, and rotate that change as the view traverses a scroll view’s visible region: https://developer.apple.com/documentation/swiftui/view/scrolltransition(_:axis:transition:)

And the visualEffect() API lets you do even more flexible things both inside and outside a scroll view by providing you a geometry proxy and letting you do customizations based on that: https://developer.apple.com/documentation/swiftui/view/visualeffect(_:) If there are other cases where you’d like to read a scroll view’s content offset, please do file feedbacks detailing your request!

CoreData object with Observable

Q: In SwiftUI, Core Data objects conform to ObservableObject, so our views can update automatically when the object changes. For example: struct DetailView: View { @ObservedObject var item: Item … } With Observable replacing ObservableObject/ObservedObject property wrappers, will CoreData objects automatically conform to Observable?

A: @Model macro used to mark SwiftData models automatically provides conformance of the model to Observable. This way, the models have the capabilities that you’d expect. While SwiftData can coexist with CoreData and connect to the same database, these two worlds don’t mix. NSManagedObject conforms to ObservableObject, and @Model-tagged types conform to Observable.

@backDeployed

Q: I’m curious about @backDeployed attribute from Swift 5.8. Is it something that cannot be used for new SwiftUI APIs or just was not adopted yet? For example .scrollTargetBehavior modifier. It seems like it requires completely new type for parameter hence is not back deployed. Are there any @backDeployed APIs in SwiftUI this year?

A: Only functions and computed properties can be back deployed; anything that relies on new types or system functionality can’t be back deployed.

matchedGeometryEffect across views

Q: Is there a way to get a matchedGeometryEffect working across views from different “view controllers”, like during a push/pop transition of NavigationStack or a present/dismiss of a sheet?

A: A matchedGeometryEffect doesn’t currently work across most complex container views, like List, NavigationStack, or NavigationSplitView.

Performance opacity vs hidden

Q: I wonder if there is any performance difference between .opacity(0) and .hidden()? Also, is there any way to cause call of onDisappear on a subview, but preserve its state (for instance, if it’s a ScrollView)? Simply removing view from hierarchy does the first, .opacity(0) does the second, but I’m struggling to find a way to do both.

A: onDisappear is tied to the view’s lifetime so whenever its called the state inside that view will be reset. So there is no way to achieve saving that state but also calling onDisappear. However, you can always lift the state higher than the view and keep the state around yourself.

Communicate with offscreen lazy element

Q: Is there a way for a parent view to communicate with an off screen child inside a Lazy Stack or Grid? I want a child that has been scrolled off screen to know when the parent view has done something and adjust the behavior of its next onAppear, maybe with a boolean flag or something similar.

A: When the view is scrolled offscreen in a lazy stack, it won’t receive updates but you can communicate information by passing the info directly to the view.

For example, you could have a counter the parent keeps track of and pass the count to the child view. When the view next updates it will receive an up to date value of count. Hope that helps!

animation with content

Q: In the SwiftUI animation talk, Kyle shows the new animation modifier which takes a content to avoid passing the animation down the attribute graph. Like so:

ontent
  .animation(.smooth) { $0.scaleEffect(...) }

Can the same be achieved with extra modifier to cancel the animation after the attribute I wish to animate? Like so:

content
  .transaction { $0.animation = nil }
  .scaleEffect(...)
  .animation(.smooth)

Q: That’s right – both approaches control the animation that is set on the transaction at the level of the modifier (.scaleEffect in this case). The benefit of using the new .animation(…) { … } modifier is that it only impacts the modifiers that you apply within the closure, and passes the original transaction+animation down to the rest of the hierarchy. Using a separate .transaction modifier to ‘reset’ the animation would require you to know what the original animation was (unless you know that it should actually be set to nil for the rest of the hierarchy, as in your second code block above).

Keep track of properties used in body ⭐

Q: How does SwiftUI keep track of what properties were used inside a body to invalidate views?

A: There’s a lot we could say about that, but I’ll try to provide a brief overview: In general, SwiftUI will compare the values of the properties stored in your views to detect if those values have changed. If any property of a view changes, the view will update and body will get called. Part of how SwiftUI can perform these checks efficiently is due to SwiftUI views being lightweight structs.

However, views can also store references to reference-type model objects, such as ObservableObjects or classes using the new @Observable macro and protocol. For ObservableObjects, if any published property is set, SwiftUI will update all views depending on it. This is why it’s often important for performance to avoid having many views in the same app all reference the same ObservableObject, since they will all get updated regardless of the properties they use.

With the new @Observable API, we’re able to improve on that significantly. When a view calls its body property, SwiftUI will record any getter accesses on properties of @Observable instances, and start listening for changes to those specific properties. If any of them change, SwiftUI will reevaluate the dependent views, re-record any property getter accesses that occur during the execution of body, and repeat the process. This ensures that views will only update if their dependent data has changed. We think it will be a big win for performance in most apps compared to ObservableObject!

A: @Observable is powered by the new open-source Swift Observation feature. You can read the open-source proposal to learn more: https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md

A: SwiftUI never itself instantiates your view structs, those only get instantiated within the execution of other body properties that are creating those structs.

So if a view depends on an @Observable object and no other properties, and the only piece of data that’s changed is an observed property of that object, then the body of that view will be reevaluated without the view struct itself needing to be reinstantiated.

A: SwiftUI is tracking the dependencies for the views — model objects do not have any internal knowledge of SwiftUI.

Implement flow layout

A: The rough way for doing this would be to implement a custom layout conforming to Layout. Let’s say you are implementing a horizontal flow you’d then use HStackLayout inside your implementation to measure and check each row.

  • Propose a size to each child (nil width) to get back it’s ideal width. Use this to determine how many of the children will be in the current row.
  • Once we have a row we can use HStackLayout to stretch and place them.
  • We now continue to the next row and we stop once all children has been consumed.

Control progress of animation

Q: Is there a way to programmaticaly control the progress of an animation in SwiftUI? A kind of UIViewPropertyAnimator but for SwiftUI?

A: There is not a tool directly analogous to UIViewPropertyAnimator, but there are a few different approaches that you can use depending on the situation. One new API this year is KeyframeTimeline, which lets you directly compute keyframe-driven interpolated values for a time that you specify, which you can then apply to the view using modifiers. You could drive this using a gesture, or any other source of ‘animation progress.’

A: Yes, you could use a TimelineView if you want to update based on time. Or, if it’s gesture-driven, you could use a gesture-controlled @State property in your view as a way to compute progress.

Avoid animate first Query reload

Q: Is there any way to make a view refresh animated upon changes to a @Query parameter when deleting an object from a ModelContext while also avoiding the view animating upon the first load of the data from the ModelContext?

A: There’s not a simple way to do that today. I’d love a Feedback with that use case! If your model is equatable you could probably hack something together along these lines:

@Query... var models
@State private var hasLoaded = false

MyView()
    .animation(hasLoaded ? .spring : .none, value: models)
    .onChange(of: models) { 
         hasLoaded = true
    }

Infinite scroll

Q: Is it possible to now have a ScrollView that can move in either direction (up/down) with no limits (infinite scroll to any date in the past or future, think calendar) without any stutter/jump?

A: There have been improvements to scrolling backwards in iOS 17.0 in general and if you use a scrollPosition modifier, the ScrollView will try to keep the view currently visible at the same relative offset when your data changes. However, there still isn’t a concept of an “infinite” scroll view. It has a finite size for its content so you would be responsible for adding / removing views as a user gets close to / away from the end.

Environment MVVM ⭐

Q: With the new SwiftData, if I understand correctly we access the model using an @Environment variable on the view. Doesn’t this contradict the idea of MVVM which the idea is to have your view be as simple and be driven by the View Model. What is Apples recommended architecture or method to handle this?

A: The environment is a data flow tool for helping to conveniently pass data through large portions of your view hierarchy, as well as scope data dependencies to only the views that need them.

It’s not required for models to be passed through the environment. You could pass models directly between views as normal properties, to give a simple example. The right way to pass data through your app often depends on the specific data your working with and how it’s used.

So the environment is just a tool, and should be used for the cases where it works best. For example, many apps leverage the environment to make it easy to pass larger models between views that represent the main screens of the app, helping avoid boilerplate. But then within those screens, that break apart those models into smaller pieces of data that are passed as normal properties to smaller component views, like a specific custom control, so that each view’s data is scoped to just what that particular view needs, i.e. allowing those views to be as simple as possible.

When architecting your views, I’d focus on that general guidance: for this particular view, what data does it need? If it’s a smaller view that’s driven by a few specific properties, then it might not be appropriate to pass in a whole model via the environment. If it’s a larger view that’s constantly being iterated on, like an entire screen of the app, then accessing the full model conveniently via the environment might be the best and most convenient option.

Style TextField

Q: I see that iOS 17 brings TextEditorStyle which we can make ourselves as it has a makeBody method. Is there anyway to do this for TextField as it’s TextFieldStyle is not public in the same way.

A: The isFocused environment value tells you if you have an ancestor view that has focus. That won’t be the case for MyTextField, since the text field that it wraps is what actually has focus. Instead, I’d expect some combination of @FocusState/focused(_:equals:) to work for you. For instance, with MyTextField, you could use a local private focus state binding to see when focus is in the wrapped text field, and draw a border accordingly. Something like this:

struct MyTextField: View {
    @FocusState private var isFocused: Bool
    ...

    var body: some View {
        TextField(...)
            .focused($isFocused)
            // stuff for drawing border if isFocused == true
    }
}

Query outside View ⭐

Q: Can we use @Query outside of views? For example, in a DataFetcherService class? If not, how to fetch SwiftData outside of views like this, in a way that will make views update themselves when the underlying data changes?

A: Query is designed to work dircetly with SwiftUI views. SwiftData provides imperative APIs for accessing data. In a “service”, for example you could construct a ModelContainer, and fetch or modify models via its mainContext. Your “view model” can be an Observable object that stores result of those fetches, and it’ll work great with SwiftUI

Full text search TextEditor

Q: Is it possible to implement a full text search in TextEditor?

A: TextEditor has built in support for find and replace as of iOS 16.0. You can control the presentation of this UI with the findNavigator() modifier:

Observable update for offscreen view

Q: What happens when an observable property changes, but the view observing it is out of visible bounds? Does the view get updated or update happens when the view becomes visible?

A: The view does get updates regardless of visibility of the element displaying the object’s property.

SwiftUI for keyboard navigation

Q: Will there be additions to SwiftUI for keyboard navigation instead of what we currently use NSEvent local monitoring for?

A: As far as keyboard input goes, there’s a new onKeyPress() view modifier that lets you react to keyboard input directed at the focused view. That may get you what you need, though of course it depends on what you’re currently using event monitors for. If you have specific cases where you still need to fall back on AppKit event monitors, please consider filing feedback reports describing them!

ScrollView pan gesture

Q: Do the new additions to ScrollView provide hooks into the pan gesture that powers the view’s scrolling?

A: There are no new APIs around hooking into the state of the scroll view’s pan gesture. However, there is a new scrollTransition() and visualEffect() API that lets you transform a view based on its relative position within the scroll view’s visible region. You could use potentially use this to create a custom refresh indicator without needing to interact with the pan gesture. I’m thinking put a refresh indicator at the top of your scroll view that reveals itself as you scroll up.

layerEffect SwiftUI Layer

Q: The new layerEffect modifier requires a parameter of type SwiftUI::Layer for its Metal function

A: Please add this line #include <SwiftUI/SwiftUI.h> at the top of your Metal files to have proper reference to SwiftUI::Layer

modelContainer database

Q: I would like to integrate SwiftData with my SwiftUI app. I would like to ask that will .modelContainer(for: [Class.self]) init brand new database instant every time? Or it can detect the database creation status automatically?

A: The overloads taking model types are conveniences for quickly adding a container. If you’d like to take full control over instantiating containers, there is an overload that takes a ModelContainer instance instead.

A: As Curt said, the modifiers are a convenience and you can create ModelContainer instances directly for full control. A few more answers to your questions:

The modifier will not create a new database every time, it will connect to the same persistent backing store. If you use multiple modelContainer() modifiers with the same configuration, that’s equivalent to creating and using multiple ModelContainer instances with the same configuration: they will resolve to the same, shared, persistent backing store, but will vend different model contexts and use separate in-memory caching state.

One example of where modelContainer() is helpful even in apps that create their own containers is in #Preview code, where you can conveniently configure a preview to have it’s own, isolated, in-memory container.

Shader ShapeStyle

Q: Can the new Metal shaders API for SwiftUI be used with SwiftCharts?

A: The foregroundStyle modifier on ChartContent accepts anything that conforms to ShapeStyle, so you should be able to provide a Shader.

Bindable vs Binding :star:

Q: what is the difference between @Bindable and @Binding?

A: @Bindable is a utility to allow arbitrary @Observable objects to be able to create bindings to any of its properties.

You can store an @Observable object in @State, and create bindings to it using the state $ prefix operator syntax, without needing to use @Bindable at all. However, there are some cases where you need to create a binding to an object’s property, but you only have a reference to the object, and it’s not appropriate to store it in @State. @Bindable is there to provide that capability.

Observable with property as struct

Q: When using @Observable, if one of the properties is a struct, how will that behave when changing the properties inside (for instance title inside Item below?

@Observable class Model {
  var item: Item
}
struct Item {
  var title: String
  var description: String
}

A: Because struct have value semantic, when you mutate a property of the struct you’re effectively mutating the whole struct. In your example if you mutate the item’s title, Model will publish a change for it’s item.

Q: When using a NavigationSplitView is it possible two have either a two-column or three-column layout depending on a condition? For example, in Notes it’s three columns when viewing in a list and two columns when viewing as a grid.

A: The best way to do this today is to switch between a two- and three-column NavigationSplitView inside an if.

A: If you do this, be sure to lift any state above the view containing the if, as changing the condition changes the view’s identity.

Published with Observable

Q: When moving to @Observable from @ObservableObject, can you still use @Published, such as in cases where you want the @Observable type to have Combine-related functionalities in addition to updating SwiftUI views?

A: No, @Published only works when applied to properties contained in a type conforming to the ObservableObject protocol

A: To add some additional detail, it’s possible to apply the @Observable macro to a type that simultaneously conforms to ObservableObject, and both observation systems can work in parallel.

However, it’s currently not possible to apply @Observable to a class that also uses @Published on any of its properties. We understand the practical impediment of that, and we’re tracking that issue. Manually triggering ObservableObject notifications via the objectWillChange publisher will work, however.

Does LazyVStack cell reuse

Q: Do LazyVStacks leverage cell reuse behind the scenes? I really want to use the new ScrollView APIs (that aren’t compatible with List) but am worried about performance when using ScrollView with LazyVStack in the context of infinite scrolling (eg. such as a social media feed).

A: LazyVStack does not currently have cell reuse. In general you’ll want to make the data associated with items in a LazyVStack as lightweight as possible. Often this can be accomplished by minimizing the state created by the item and having it reference a separate data model.

Preview iOS 17

Q: I saw that there’s a known issue in which #Previews isn’t compatible with targets earlier than iOS 17 in another question

A: The preview macro is implemented with an entirely new preview API under the hood.