Issue #890

In WWDC21, WWDC22 Apple provide a Slack channel https://wwdc22.slack.com/ for people to interact with Apple engineers in digital lounges. Here I note down some interesting Q&As

WWDC22

What’s the difference between a custom ViewModifier vs View extension

Q: What’s the difference between a custom ViewModifier (without DynamicProperty) that uses some built-in modifiers in body(content:), and a custom View extension func that just use those built-in modifiers? Similarly, what’s the difference between a custom ViewModifier with some DynamicProperty and a custom View with some DynamicProperty (also has a @ViewBuilder content property to receive content to modify) ? I think two have the same render result and behavior.

A: Because of the way a ViewModifier is expressed, the engine knows it’s not changing the content passed in and can apply performance optimizations (compared to just an extension on View)

What are the differences between [any Animal] vs [Animal]

Q: What are the differences between [any Animal] vs [Animal]

A: Right now, [Tool] and [any Tool] mean the same thing, but you should start to use the [any Tool] syntax going forward!

ObservableObject vs StateObject

Q: Why would anyone use observable object instead of state object?

A: You might want to use @ObservedObject if the lifecycle of your model is not owned by your view. An example is if you are using UIHostingConfiguration and you have your model owned by UIKit in a UIViewController and just passed to a SwiftUI cell.

A: Also, in general you want to use @StateObject in a parent view, and if you pass the same ObservableObject instance to child views you can use just @ObservedObject.

Local some variable

Q:

protocol Tool {}
struct Hammer: Tool {}
struct Wrench: Tool {}

struct Robot {
    func fix(tools: [Tool]) {}
    func fixAny(tools: [any Tool]) {}
    func fixSome(tools: [some Tool]) {}
}

func def() {
    let t1: some Tool = Hammer()
    let t2: some Tool = Hammer()

    let robot = Robot()
    robot.fixSome(tools: [t1, t2]) // DOES NOT COMPILE
}

A: Semantically, those local variables have two distinct opaque types, and the function requires each element to have the same time. The compiler generally does not know the exact underlying type at compile time, and when it does, only uses that information for optimization.

some vs any

Q: Can there be situation in which both “any” and “some” is possible and where this decision changes the behaviour or performance of the implementation?

A: Yes! I didn’t touch on performance in the talk because I think my original proposal for the any keyword focused too much on performance and it gave a lot of folks the wrong idea that performance was the only motivation, when it’s really about the semantic differences.

A: You’re right that sometimes both will work. One example is non-inout function parameters where the function doesn’t access associated types. some will be more efficient because it can accept values without boxing them, and avoids “opening” the box to call protocol methods.

A: One case that comes to mind where behavior would change is if you’re passing something to a generic function that declares and the function uses T.Type. If T is some P, T.Type means something different than if T is any P.

Style Layout for specific subview

Q: Is there a way to build a Layout that would effect each child view with ViewModifiers? Let’s say I always want to style the first view one way and the second another.

A: LayoutValueKey allows you to pass information from your view hierarchy along to layout, but please note that layout can’t use this information to make style changes to the view, only to determine its layout.

The information is passed through to layout through layout proxies, so the original view isn’t accessible from the layout protocol.

LayoutValueKey vs PreferenceKey

Q: Do LayoutValueKey values travel up through nested Layouts? Also, how are they different from PreferenceKey?

A: LayoutValueKeys only travel up to the nearest Layout container

A: One reason for this is for Layout to facilitate building of encapsulations that can be composed without having unanticipated effects on the other views that surround it

AnyLayout vs if else

Q: I saw that VStack and HStack conform to Layout. Does that mean using an if-else to switch between the two with the same content will now animate the change?

A: Yes

A: To animate between layouts you’ll need to switch between the layouts using AnyLayout for example:

let layout = isVertical ? AnyLayout(VStack()) : AnyLayout(HStack())
layout {
    Text("Hi")
    Text("World")
}

Use ViewBuillder

Q: But, what’s the recommended way to use a @ViewBuilder for custom components: calling it right away in the init() and storing the view, or calling it later inside the body and storing the view builder itself?

A: We’d generally recommend resolving it right away and storing the view

A: Storing the view will have better performance than storing a closure (potentially avoiding allocations). Of course, if you need to dynamically resolve a view from a closure (such as if it takes parameters), then storing the closure is also fine!

A: Yes you can attach @ViewBuilder to properties! You can actually support it on both view and closure properties, depending on whichever you need:

struct MyView<Content: View>: View {

  @ViewBuilder var content: Content

  // or

  @ViewBuilder var content: (Int) -> Content

Resign keyboard TextField

Q: What is the best way to resign (text field) focus from some distant view in the view hierarchy. scrollDismissesKeyboard is a great step in the direction I need, but I’d like to programmatically trigger that same behavior, for example, on some button tap.

A: You can do this with the Focus State API: https://developer.apple.com/documentation/swiftui/focusstate You want to bind a focus state property to your text field using View.focused(_:equals:), and then set the binding’s value to nil/false from your button action as a way to programmatically resign focus and dismiss the keyboard when the bound text field has focus. Making the action available to distant views is a matter of arranging your app’s data flow appropriately. There’s no single answer, but for example, you could declare your focus state property on your scene’s root view and pass a reset action down to any descendant views that need it. Or if the action is created by a leaf view, you can use the preferences system to make the action available to ancestors.

Layout place subViews

Q: What happens to a subview if you don’t call .place() on it in placeSubviews in a Layout? What happens if you call it twice?

A: If you call the method more than once for a subview, the last call takes precedence. If you don’t call this method for a subview, the subview appears at the center of its layout container and uses the layout container’s size proposal.

A: You interact with the subviews from within the layout protocol via the subview proxies that you get as input. This lets you do things like propose a size or place the view, but not perform arbitrary operations on the view, or apply arbitrary modifiers. If there’s something specific that you’d like to do but can’t, please do file a feedback with your use case.

popover iPhone

Q: Hello, is it possible to present a popover as popover on iPhone, like it’s the case with iOS?

A: On iPhone, the .popover(…) modifier always presents as a sheet. There isn’t currently a way to customize that behavior. I’d love a Feedback with details of your use case though!

Q: Are Deep Links possible in SwiftUI?

A: Check out the SwiftUI Cookbook for Navigation session and the What’s New in SwiftUI session for 2 examples of deep links

A: They are indeed possible, and we think they work pretty well :wink: Of course, there’s a lot of routing to consider with any app — for instance if your deep link is behind some authentication token, you’ll need to do the extra work there.

A: The general idea is that a deep link is certain destinations presented — in order — on a navigation stack, and with this years' new APIs you can have full control over what is on the navigation stack.

UIHostingController rootView

Q: When using UIHostingController, is it better to add it to the view controller hierarchy (with addChildViewController), or could we use its rootView and simply add it as a subView ?

A: You want to always add a UIHostingController as a childViewController.

// Add the hosting controller as a child view controller
self.addChild(hostingController)
self.view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)

without the view controller relationship things that depend on the UIViewController hierarchy won’t work. Such as UIViewControllerRepresentable

A: There are a few other types as well that depend on this relationship in their underlying representations so its best practice to add the parent/child relationship.

A: There are a few other types as well that depend on this relationship in their underlying representations so its best practice to add the parent/child relationship.

A: yes the AppKit underlying infrastructure makes NSHostingView work while UIKit is not able to do so due to the hierarchy requirements. It depends on exactly what you are trying to build, but potentially you could make a reusable view controller that your teams can use instead depending on where the complexity is at. Parenting at the rootViewController would have issues with any navigation calls you might try to make in SwiftUI.

Layout and UIViewRepresentable

Q: Should Layout work fine with UIKit-representable views?

A: es! To get more control over how UIViewControllerRepresentables size themselves in SwiftUI layout you may want to implement their new sizeThatFits method: https://developer.apple.com/documentation/swiftui/uiviewcontrollerrepresentable/sizethatfits(_:uiviewcontroller:context:)-35p75

Implicit animation

Q: What are good tells that I should use implicit vs explicit animations in SwiftUI?

A: I usually default to withAnimation to set up animations based on when a change occurs. That way, whatever UI changes as a result will implicitly be animated. In some cases, View.animation is useful when you have a certain view or view hierarchy you always want to change with an animation, regardless of what triggered it. A good example is the knob of a Switch: it should just always animate when it changes. (And make sure to pass an appropriate value, like .animation(myAnimation, value: mySwitchValue), so that you only animate when the property you care about changes, and don’t accidentally animate too often.)

ObservableObject properties in model

Q: In a WWDC20 session “Data Essentials in SwiftUI” there’s a point in the video where the presenter touches on using a single data model that can have multiple ObservableObject projections to target SwiftUI updates to specific views. How can I achieve this?

A: The exact solution really depend on your data model but its can simply be as having a model that contains a bunch of properties where each of these type is an ObservableObject; or you could have a function that given an identifier return you the ObservableObject that corresponds to that id. What we wanted to get across was that you want and you can scope your ObservableObject to specific screen of functionality of your app.

ScrollView and Chart

Q: Forgive me if this question has the same answer as the scrolling chart question, but could we utilize a scrollview and a chart to display a live chart? So new data added is always visible to the user, instead of having to manually scroll it into view.

A: Yes, this should work, but will extra work. You may have to observe the data and manually keep the scroll view positioned at the very end

A: It sounds like you want to use to be able to scroll backwards through an otherwise-live feed, but if that isn’t the case, the data plotted in your chart could always be just the last 7 elements of an array or something conceptually similar

Init StateObject

Q: There has been a lot of controversy regarding the ObservedObject(wrappedValue: ) initializer. Is it safe (and encouraged) to use it, or do we have an alternative for it this year?

A: This initializer is fine to use! In fact, the ObservedObject(wrappedValue:) initializer is invoked every time you construct an ObservedObject , even if you don’t explicitly write it yourself. When you write: @ObservedObject var myObservedObject = myModel, The Swift compiler converts that standard property wrapper syntax to a call that looks something like: var _myObservedObject = ObservedObject(wrappedValue: myModel). The controversy I think you’re referring to is using that initializer explicitly in the initializer for one of your views. Typically, we see people doing this to allow observed objects to be instantiated with specific information. That is also something which we think is fine to do. We recognize that the ergonomics of that case is not ideal (since you have to access the de-sugared property wrapped (in the example I gave, _myObservedObject), but it’s not at all harmful.

A: The state initializer worries me a bit more. Not because it’s dangerous — it’s totally fine to use it yourself (as I mentioned, the normal syntax is just sugar for the fully spelled out case) — but because I can’t think of as many cases where you need that syntax for @State that aren’t dangerous. Remember that @State is initialized once per lifetime of the whole view, not once per time a view’s initializer is called The views representation will be recreated on demand. That means that if you’re re-initializing the state every time the views init is called, you’re going to be clobbering your own state.

A: As a final note: regarding the “don’t call this initializer directly,” that’s mostly because as I mentioned, the cases where you “need” the underlying initializer (for @State and @Binding especially) are few and far between. Most of the time, you’d want to be using the standard property wrapper syntax, so we want to make sure people reading the docs look there first

EnvironmentObject as dependency injection

Q: Is using @EnvironmentObject for dependency injection of entities not directly related to the view state, like a Service (to fetch values from network) or a telemetry logger, considered bad practice? Thinking on a MVVM architecture context.

A: Be mindful when using plain @Environment, that if you’re passing a struct, any change in value will invalidate any views reading that value from the environment

A: But if you’re using @EnvironmentObject with a class that’s effectively immutable that shouldn’t be a problem

StateObject in master-detail view

Q: Is there a recommended best practice for managing StateObjects/ObservableObjects in a primary-detail type setup? I’ve been commonly using StateObject(wrappedValue:) to inject a stateobject into a detail view, but of course because wrappedValue is an autoclosure it doesn’t get updates to the data value that may occur from the primary view. Passing the entire primary StateObject to the detail view can expose more data to the detail view than necessary. And creating and managing an ObservableObject owned by the primary StateObject can be boilerplatey and messy to manage. Any approaches I’ve overlooked, or tips on how to approach those tradeoffs?

A: My intuition is that for a primary-detail setup, you would want the detail using @ObservedObject (or even @Binding) instead of @StateObject

A: @State and @StateObject are defining a source of truth and lifetime for the state/model. You want the lifetime of the state/model to live on beyond changing the selection in the primary. @ObservedObject will listen to changed in the model object and update the view without establishing a new source of truth.

Alter NavigationPath

Q: With NavigationPath, I’ve already felt the need to pass it down to child views as a @Binding or @EnvironmentObject so that they can influence the stack programatically. Is that a reasonable approach or am I overlooking something?

A: Passing the binding or passing a navigation model as an environment object are both reasonable approaches with the API we have available today. I hope we can make this more ergonomic. I’d love a Feedback with your use case!

A: I personally like the navigation model approach, since I can easily do other work when the navigation state changes without putting that code in my Views.

State in Layout

Q: Is it possible to use @State, et. al from within a custom Layout? Or is there a different way we should parameterize our layouts? I noticed that the protocol doesn’t inherit from View, so I wasn’t sure if using the property wrappers would work.

A: You can add input parameters to your layout, and you can attach values to particular views using the LayoutValueKey protocol. To store data between calls into your layout, you can use the cache, but that’s only to avoid repeated calculations, and you should be prepared for the cache to go away at any time.

Use GeometryReader less

Q: As we get more and more familiar with SwiftUI we use GeometryReader less and less, but what are some tells that there’s no way around it?

A: Layout gives you a superior tool for encapsulating layout logic for re-use. And while using GeometryReader you can position children, the GeometryReader can’t reflect back any custom sizing behavior. This is one of the reasons it can become unwieldy when used in complex layouts—because the GeometryReader becomes fully flexible

SpatialTapGesture vs TapGesture

Q: What is the difference between SpatialTapGesture and TapGesture?

A: For SpatialTapGesture the event in onEnded now provides the tap location.

Tab to change focus Mac

Q: SwiftUI macOS: How can I control how the focus moves around the controls of my view when I press “tab”? Currently it jumps from top left to right, then down. But I have two columns in my window and would prefer it go down first on the left side and then again up to the right. Is there some functionality to pre-define the order of the controls?

A: You can use View.focusSection() for this, which is newly available on macOS 13.0. Marking a view as a focus section causes the Tab loop to cycle through all of the section’s focusable content as a group before moving on to the next thing in layout order. So, something like this should get you the sort of column-wise navigation you’re after:

struct ContentView: View {
    var body: some View {
        HStack {
            VStack {
                FocusableView()
                FocusableView()
            }
            .focusSection()

            VStack {
                FocusableView()
                FocusableView()
            }
            .focusSection()
        }
    }
}

A: Also, a plug for my colleague Tanu’s WWDC21 talk: Direct and reflect focus in SwiftUI https://developer.apple.com/videos/play/wwdc2021/10023/ It goes over some tvOS use cases for View.focusSection(), and also describes how to work with focus programmatically using focus state bindings.

switch case inside Grid

Q: is it recommended to use switch-case views for e.g. showing different view types in a list or grid instead of using specific types via generics etc.?. are there any better ways?

A: When you use a switch within a @ViewBuilder closure, you are in fact using the power of generics :wink:, but in most common cases you hopefully don’t have to think about that overtly. In general, using switch in view builders is a great tool! The best approach really case-by-case though, just as when deciding to use a switch versus other control flow in regular imperative code.

Branching inside ViewBuilder

Q: Does branching in a views body (via if-else or switch) cause the creation of each branch’s views?

A: t only creates the views for the branch of the if/switch that’s actually taken.

UIKit to SwiftUI

Q: As we are incrementally adopting SwiftUI in a UIKit app, are there guidelines for replacing the patterns that we are used to in a UIKit app, like delegate pattern, notifications, target-actions etc? Some of these are obvious but I kind of feel I’m doing something wrong whenever I pass a closure into a view to get a feedback to replicate delegate pattern for example

A: Delegates do a lot of different things in UIKit, and so the way that they should be translated into SwiftUI can vary heavily depending on what they’re being used for in UIKit. In some situations they’ll become closures that you pass around, in others, a Binding or State, sometimes a onChange(of:), etc. UIKit and SwiftUI have two very different programming models, so often times, concepts don’t map over in a one-to-one way. I’d highly recommend you book a lab appointment so we can chat more about what specific case you’re trying to translate, and where your pain-points are there

Large dataset Chart

Q: I currently have a large data set (over 10k datapoints) that I’m working with. Are there any tips on how to improve the graph performance when working with creating line graphs from data sets of that size or larger? Context is cycling related data in a macOS app that is presenting 6 or more in a LazyVGrid.

A: We recommend simplifying the data before rendering it. For your example, you could simplify the line or summarize the data points in small intervals (let’s say you have data for a year, you summarize the data for a day). The added advantage is that you can summarize using mean, min and max and show the full range within the small interval.

A: Another useful resource are the number and date bins we released this year: https://developer.apple.com/documentation/charts/datebins and https://developer.apple.com/documentation/charts/numberbins.

Chart pan pinch

Q: How easy is it to support interactions like pan-to-move and pinch-to-zoom?

A: For pan-to-move, you can use a SwiftUI gesture in conjunction with ChartProxy. From the gesture, you get the the pan distance, and then you can use ChartProxy to figure out the pan distance in the data domain. Then, you can set the domain for the X scale with .chartXScale(domain: start + offset…end + offset) , where you can adjust the offset to pan the chart.

Chart multiple scales

Q: Is there a way to have multiple scales per chart? Let’s say I’m doing weather and I want one line with cloud cover (a 0-100% scale) and rainfall per hour (in, say, mm). The x axis on both of those would be time, but the y axes are different.

A: An axis always has one scale. So x and y have one scale. You can have multiple axes to display e.g. temperature in C and F. If you want to show multiple measures, I would recommend multiple charts with aligned x-axes. You can even hide the axis of the upper chart to create a more compact view.

Chart value label key

Q: Adding data to charts and the modifiers always requires this .value(_:) function that requires a label key. What exactly is the purpose of that label key? Is it some kind of identifier? Does the label key in a foregroundStyle have to match one in a LineMark, for example (if referencing the same data)?

A: The label key is used for generating a good default description for the chart for VoiceOver users and the audio graph. For example, if you have x: .value(“time”, …), y: .value(“sales”, …) , VoiceOver users will hear something like “x axis shows time, y axis shows sales”. The label key in foregroundStyle doesn’t necessarily need to match LineMark, but it’s a good practice to use the same label if the data is the same.

Q: Is it possible with SwiftUIs new NavigationStack to hide the tabbar of a TabView when the destionation view appears. With the existing NavigationView it was possible but not so easy to handle the navigation title and buttons.

A: Take a look at the new toolbar visibility accepting modifier. This is new in SwiftUI and allows configuring the hiding or showing of different bars like the navigation bar, or the tab bar.

ContentView()
 .toolbar(.hidden, in: .tabBar)

See https://developer.apple.com/documentation/swiftui/presentedwindowcontent/toolbar(_:in:)

Support drag drop VStack

Q: As of Monterey, List supports drag-and-drop reordering of items on macOS and iOS/iPadOS, but it seems that other types (e.g., HStack, VStack, etc.) support drag-and-drop reordering of items on macOS only in Catalyst apps. In any other kind of macOS app, the same SwiftUI code that works as expected in iOS/iPadOS and Catalyst doesn’t work (i.e., a drag cannot be started at all). The code compiles just fine, but it doesn’t actually work. How can I support drag-and-drop reordering of items (or sub-Views) in SwiftUI code in HStacks, VStacks, etc. in non-Catalyst macOS apps?

A: N/A

Native Form Catalyst

Q: Hi, is there any recommended way of making controls inside a SwiftUI’s Form look more native on macOS in an app made with Catalyst? Would I have to have an AppKit bundle/plugin that renders those SwiftUI views?

A: To enable macOS-native UI in your Catalyst app, go to your target’s settings: General → Deployment Info → Mac Catalyst Interface, and switch from “Scaled to Match iPad” to “Optimize for Mac”. Controls in SwiftUI will automatically adapt to be more Mac-like in that mode. Also check out the new Form styles, to have the controls arranged in a traditional Mac columnar layout.

Use _printChanges

Q: When using Self._printChanges() in the body of Views, what should we pay close attention to? Is @Self printed often harmful? What are the numbers @44 we see when using FetchRequest in the View.

A: @Self self is not inherently harmful. The purpose of Self._printChanges() is not to indicate that something is wrong but rather as a tool to match your expectation with what is happening at runtime. What you want to be on the look out for is that you don’t do unnecessary invalidation which might cause performance issue. If you see @ followed by any number it means that we couldn’t find the field (property) name, so printed the byte offset of the swift view struct field instead.

onTapGesture double tap

Q: Can onTapGesture be used more than once on a single view? 
Like single-click to select, double-click to open in a window?

A: onTapGesture can indeed be used more than once but you need to get them in the right order, so that the double-tap fails before the single-tap succeeds. Something like this:

ItemView()
    .onTapGesture(count: 2) {
        ...
    }
    .onTapGesture {
        ...
    }

Conditional os version check

Q: What is the recommended way to conditionalize code for view modifiers that are only available on newer versions of iOS? For example, if I have a View, and previously was using the regular onTapGesture modifier on iOS 15, but when running on iOS 16, I want to instead use the new version that provides location?

A: We recommend factoring out the common parts and then using if #available checks to use the relevant modifier. Something like this:

let common = commonViewParts
if #available(iOS 16.0, macOS 13.0, *) {
    return common.newModifier()
} else {
    return common.oldModifier()
}

Invalidate NavigationView

Q: In SwiftUI when you had a List view with items that were continuously synced with a server and you displayed the details view of a selected item then the app jumps back to the main list. Are NavigationStacks solving this problem?

A: Generally the popping back problem is because there is a state change that’s invalidating the view that contains the NavigationView. This causes SwiftUI to discard the NavigationView and create a new one, popping you back to the root.

Circular widget wtUIVisualEffectView

Q: Is there a way to recreate the appearance of the widgets on the Lock Screen using UIVisualEffectView in an app using either SwiftUI or UIKit? I’m specifically interested in recreating the appearance of the circular widget.

Q: A variety of the gauge styles used in circular complications on the Lock Screen are available in app as well! You can access them using any of the SwiftUI gauge styles with an “accessory” prefix. Please be aware though that those styles are really only intended for Lock Screen / widgets and contexts in your app that should have a similar look and feel, so please be thoughtful about where you use them

Use FocusState in iOS 14

Q: What’s the recommended way of using a property wrapper such as @FocusState such that it will compile targeting iOS 14 for example? AFAIK you can’t add an availability annotation

A: You are correct that you can’t apply availability to stored properties in Swift. There are a few techniques you can use to work around this. Most commonly, you can factor out the portion of your UI that uses this property into it’s own View, and apply availability to that entire view. However, the best way to structure the code really depends on your specific use case, and we agree that this can be tricky to handle in some situations.

Hide navigation bar in UIHostingController

Q: By default UIHostingController configures a navigation bar. What is the best way to hide it? Overriding viewWillAppear to call setNavigationBarHidden does not always give the expected result. The best result I got was by overriding viewWillAppear and NOT calling super.viewWillAppear. Is there any risk with this method?

A: I would not recommend overriding viewWillAppear and not calling super. Generally, I’d recommend either using a navigationBarHidden(false) or the new toolbar(.hidden) modifier inside of your SwiftUI view. Or if you are managing a UINavigationController yourself, and you can’t use those modifiers, you should be able to set the isNavigationBarHidden property to false yourself and the hosting controller will try to respect that. I’ll call out though the release note in the first beta of iOS 16:

SwiftUI might incorrectly modify the isNavigationBarHidden property on UINavigationControllers not created by SwitftUI.

A: If you’re having trouble with this on older releases, I’d recommend trying to use .navigationBarHidden() for hiding the navigation bar. If you are unable to do that, then you could try subclassing the UINavigationController to avoid hiding or showing the navigation bar if SwiftUI incorrectly tries to mutate that property.

Mouse click wth modifier

Q: Is it possible in SwiftUI on Mac to use a modifier with a mouse click like ⌘-leftClick? I like to select non-adjacent items like in Photos and Finder.

A: Gestures have .modifiers(_:) modifier, which allows restricting that gesture to only respond when the modifier is active. So in this case you could use a Tap gesture with .modifiers(.command)

View in computed property

Q: For complex views, I often define subviews inside computed vars to keep my body block more readable. Especially for components that don’t need to be reused elsewhere in the app, so they don’t seem to warrant a reusable struct.

A: SwiftUI’s traversal of your view tree isn’t impacted at all by whether you chose to use new structs, or computed properties, for small pieces of views, etc. So this is totally reasonable. Using @ViewBuilder here also shouldn’t have a performance impact. I would highly recommend doing so! The only thing that you should pay attention to is your mechanisms for invalidating your views will be a bit less fine-grained, as you’re making a larger part of your view hierarchy dependent on the sources of truth specified by MyView . Make sure you’re paying attention to what pieces of your view depend on which pieces of data, and when you see computed properties that have completely disparate dependencies from the rest of the view, you consider breaking those out

Tab order TextField

Q: Is there a way to specify the tabbing order of text fields, akin to textView.nextKeyView (FB10020959). My app has a combination of SwiftUI TextField Views and NS/UITextView wrapping in NS/UIViewRepresentable, and I’ve seen some odd cases where the default tabbing order is quite unintuitive.

A: By default the key view loop follows the same default order as AppKit — leading to trailing, then top to bottom — do please file a feedback if you find a situation where that’s not the case. There is support for customizing that order by describing the sections that separate your UI using focusSection() (https://developer.apple.com/documentation/swiftui/view/focussection()?changes=_4)

ViewThatFits

Q: How does ViewThatFits decide if a view fits? I find it a bit strange that Text with a line limit of 1 still “fits” even if it’s truncated for example.

A: ViewThatFits consults the view’s ideal frame size for both dimensions.

The latter behaviour isn’t intended. A feedback # and keeping an eye on later seeds is a good strategy here.

WWDC21

@StateObject or ObservedObject?

Q: Let’s say I have a purely SwiftUI flow. I have a ListView with a @StateObject var listFetcher, that makes requests for a list of items. Tapping on an item in the list navigates to a DetailView, which has an ObservableObject property detailFetcher, that makes requests for details on the item. What’s the best way to structure DetailView and which property wrapper would we use for detailFetcher in DetailView?

Have an initializer like init(itemID: Int), and use @StateObject? This would require us to eventually update the detailFetcher property with something like detailFetcher.itemID = itemID in the body’s onAppear. Pass in the detailFetcher into the initializer like init(detailFetcher: ObservableObject) and make the property @ObservableObject? If this is preferred, where would this detailFetcher live if not in SwiftUI?

A: In general, use @StateObject when the view in question owns the associated object, i.e. the object will be created when the view is created, and should be destroyed when the view is removed.

In contrast, use @ObservedObject when the view needs to reference an object that is owned by another view or something else. So the view is dependent on the object, but their lifetimes are not tied together.

For example, you could have a main screen that uses @StateObject to initialize your app’s model, and then pass that object off to detail screens using @ObservedObject. Also check out the Demystify SwiftUI talk to learn more about this!

ObservedObject not used

Q: When an observedObject is passed into a view, does SwiftUI differentiate between views that actually use it (the object is used in the body) and ‘intermediate’ views (which just pass that object to a child? )? Or are all views just invalidated?

A: Yes, there is a difference. If you don’t use any of the ObservableObject property wrappers (@StateObject, @ObservedObject) the view would not observe and update the instance. So you you just need to pass an ObservableObject through some intermediary view just make it a regular property on the view but make sure to use the property wrapper if you ever read any of the value in the view, otherwise your view will no be consistent with your data.

Also, @EnvironmetObject is a great tool when you have an ObservableObject that you want to pass down multiple levels of your view hierarchy without having to manually do it every step of the way.

frame vs Spacer

Q: In most cases, the layout behavior with Spacer can be replaced with .frame(maxWidth:alignment:)(or height) seamlessly. Since Spacer is an actual View that is arranged within the view hierarchy, using Spacer will consume more memory and cpu resources. And Demystify SwiftUI also says “modifier is cheap”. So should I use .frame instead of Spacer as much as possible?

A: While Spacer is a view, it doesn’t end up displaying anything of its own so it is plenty lightweight. Using .frame can have other behavior introduced to the way the view gets laid out beyond just changing its size. They both have their uses, so use them each where appropriate.

A: To add a little more onto this, even in cases where you will get almost entirely the same behavior between the two, the performance difference will be so minimal that I would strongly suggest prioritizing code readability over performance / memory use to make this decision.

If Spacer makes it more clear what layout you’re trying to specify, use that, and vice versa.

Get View size

Q: Is there any way to get GeometryReader size from another view? I want to replace “???” with the height of “Hello world!”.

A: Using a GeometryReader in a background of a view ensures the GeometryReader doesn’t grow to be larger than that containing view, but it makes it tricky to bubble its size out. You could do something like this:

struct ContentView: View {
    @State private var height = 100.0
    
    var body: some View {
        MyView().background {
            GeometryReader { proxy in
                Color.clear
                    .onAppear {
                        height = proxy.size.height
                    }
                    .onChange(of: proxy.size.height) {
                        height = $0
                    }
            }
        }
    

A: You must ensure you will not cause a continuous layout loop here, if your layout responds to height changing in a way that causes the GeometryReader to lay out again and cause height to get updated, you can get into a loop!

Q: Are NavigationLinks “lazy” in iOS 15

A: NavigationLinks do not fully resolve their destinations until they are triggered, though the value of the destination view is created when the NavigationLink is created. In general, we recommend avoiding as much work as possible when a view is initialized, which would avoid potential issues here. This is important for performance. Instead, have that work be triggered within the view’s body, such as using onAppear or the new task() modifier. SwiftUI may reinitialize views for any number of reasons, and this is a normal part of the update process. I’d recommend watching the Demystify SwiftUI talk, which helps explain some of these concepts in more detail.

Downside of single app state

Q: I have a class Store<State, Action>: ObservableObject that holds the whole app state. It lives as @StateObject in the root of the App lifecycle and passed via environment to all views. View send actions to the store and update as soon as store’s state updated. It allows me to keep the app consistent. Could you please mention any downsides of this approach from your prospective?

A: That approach makes every view in your app dependent on a single Observable object. Any change to a Published property forces every view that references the environment object to be updated.

StateObject directly

Q: When I’ve needed to inject data into a detail view, but still let the view have ownership of its StateObject, I’ve used the StateObject(wrappedValue:) initializer directly in my view initializer, for example:

public struct PlanDetailsView: View {

    @StateObject var model: PlanDetailsModel
    
    public init(plan: Plan) {
        self._model = StateObject(wrappedValue: PlanDetailsModel(plan: plan))
    }

    ...
}

Is this an acceptable use of the initializer? I know StateObject is only supposed to initialize at the start of the View’s lifetime, and not on subsequent instantiations of the View value, so I want to make sure I’m not forcing it to re-allocate new storage each time the View is re-instantiated.

A: Yes, this is an acceptable use of the initializer and your understanding is correct: that object will be create only at the beginning of the view lifetime and kept alive. The StateObject’s wrapped value is an autoclosure that is invoke only once at the beginning of the view lifetime. That also means that SwiftUI will capture the value of plan when is firstly created; something to keep in mind is that if you view identity doesn’t change but you pass a different plan SwiftUI will not notice that.

EnvironmentObject vs ObservedObject

Q: In general, to pass data around, would it better to have an EnvironmentObject that could be called within a view, or an ObservedObject that gets passed down (and/or injected) through child views?

A: Both have their uses, and it depends on the architecture you’re building. If you have one (or a few) large ObservableObjects that large parts of the view hierarchy need to see, I would generally recommend EnvironmentObject as SwiftUI can look at which of your views depend on the EnvironmentObject and only invalidate those when your ObservableObject changes (you can get this behavior with ObservedObject too, but it’s more cumbersome). Plus, views that don’t actually use the ObservableObject don’t get cluttered with code relating to it.

That said, if your model is, for example, an object graph that is largely not structured based on your view hierarchy, it may make more sense to use ObservedObject to grab pieces of that model out to use in your view.

Read more