WWDC22 Q&A - SwiftUI Lounge

Während der diesjährigen WWDC war ich auch in verschiedenen Slack-Kanälen von Apple. Unter anderem auch in Q&A-Sessions.

SwiftUI Lounge

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

Answer: 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. 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.


Question: 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.

Answer: 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).


Question: Is there a way to use a capacity Gauge outside of the lock screen?

Answer: Yes! Gauge is usable outside of the Lock Screen, and also has a whole host of new styles! I would take a look at the linearCapacity, accessoryLinearCapacity, and accessoryCircularCapacity styles. Please note though that gauge styles prefixed with “accessory” are really intended to appear in contexts like the Lock Screen / widgets, or some similar design context within your app proper, so please give careful consideration to where you use them.


Question: ViewThatFits feels very convenient but I'm concerned about making copies of the same content for each layout option. I assume a custom layout would be required to achieve this?

Answer: ViewThatFits is a convenience that might not be appropriate if you need something with animated transitions, or in general something with more complex behavior.


Question: 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.

Answer: 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.


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

Answer: LayoutValueKeys only travel up to the nearest Layout container. 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.


Question: Are Deep Links possible in SwiftUI? If so, does it work well? Are there any limitations vs what is possible in UIKit?

Answer: Check out the SwiftUI Cookbook for Navigation session and the What's New in SwiftUI session for 2 examples of deep links. They are indeed possible, and we think they work pretty well 🤗. 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. 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.


Question: When creating a MenuBarExtra, is it possible to create something that runs on its own, separate from the parent app? I have an app that users can save data to by means of various app extensions. If the app is closed, however, those changes don't get synced to CloudKit. I was thinking of having a menu bar app that would essentially be my workaround: always listening, always active. Can a MenuBarExtra do that for me?

Answer: Hi - you can certainly define a SwiftUI App with only a MenuBarExtra Scene. If your app should not show a Dock icon, that will require some changes to the Info.plist file, which should be noted in the documentation for MenuBarExtra.


Question: When using the new MenuBarExtra in window style is it possible to control the width and maximum height of the content window?

Answer: The window size should be derived from the content provided to it, though we do impose a minimum and maximum on the resulting size. If you are seeing unexpected behavior in this area, please do file a feedback with a sample to reproduce it, and we can take a look.


Question: Are there any types of macos apps or interfaces where you would still recommend using appkit rather than swiftui?

I'm yet to invest the considerable amount of time learning swiftui and I keep reading mentions of people saying it's still a mixed bag for macos development, so I don't want to potentially waste time. Thanks!

Answer: Good question! Across all platforms, we’d recommend comparing your needs to what SwiftUI provides (so no hard rules/recommendations) — and keeping in mind that you can adopt SwiftUI incrementally.

Within Apple’s own apps on macOS, we’re ourselves using the full spectrum of approaches. From just a specific view/views in an app, e.g. in Mail, iWork, Keychain Access; to an entire portion of the UI or a new feature, e.g. in Notes, Photos, Xcode; and all the way to the majority of an application, e.g. Control Center, Font Book, System Settings.

But in the end, I’d recommend starting with a part you’re comfortable with and building up from there! You should look at SwiftUI as another tool in your toolset in enabling you to build the best apps you can.


Question: Is it possible to have a presented view that switches between the popover/sheet presentation styles depending on the size class (i.e., sheet in compact, popover in regular)?

Answer: Yes! You can do this by creating a custom view modifier that uses looks at the UserInterfaceSizeClass. Custom modifiers are great for conditional combinations of other modifiers, views, etc. and this is a perfect use case. We have an example of this use case in the BookClub sample app, in ProgressEditor.swift.


Question: Not sure if this was already asked before since it's such a common question. 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?

Answer: We’d generally recommend resolving it right away and storing the view. 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!


Question: 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.

Answer: You can do this with the Focus State API.

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.


Question: Previously in NavigationView on iPad, the detail view would be displayed with the ForEach list being collapsed in a sidebar. With the new NavigationSplitView, can I use a modifier to not collapse the ForEach list?

Answer: Thanks for the question. NavigationSplitView takes a columnVisibility binding. You can set it to .all to reveal all the columns programmatically!


Question: Does the new NavigationSplitView preserve state when we switch to a new navigation destination like tab bar in UIKit, or do we need to roll our state restoration, like what we had to deal with using the old TabView?

Answer; If you want to switch to a TabView in narrow size classes, you’ll want to design a navigation model type that provides selection information for the NavigationSplitView in regular size classes and a different projection of the same information to a TabView that’s shown in narrow size classes.


Question: Is there any new control in SwiftUI like the NSSegmentedControl used under tables to add / delete a row? This is a common pattern in macOS applications and the current SwiftUI segmented control does not fit to this use case.

Answer: Good news: There’s not a new control, but an old one 😃. Last year we introduced ControlGroup that enables building these kinds of controls (which you might have used NSSegmentedControl for with AppKit). You can create one with Buttons, Toggles, and more!


Question: What is the difference between .onChange and .onReceive modifier? .onReceive is like a combination of .onAppear and .onChange, is this the complete and accurate picture?

Answer: .onReceive is specifically to subscribe to Combine’s Publisher types and produce a side effect. .onChange is used to produce a side effect when a property of your view changes. For example you can use that to produce a side effect when the scene phase in the environment changes.


Question: 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?

Answer: Yes! 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")
}

Question: With graphics and custom layouts being added, when should we use native elements vs custom styling? How far should custom designs go?

Answer: It entirely depends on your use case! We ultimately trust your sense of design on this one since only you know what’s right for your app. To give some general guidance though: Take native elements as far as you can, and if you find you need further customization beyond that to finely polish the experience, then don’t be afraid to use it!

One quick disclaimer: It can be easy to drop to custom layouts prematurely. Stacks and Grids are incredibly powerful already, so be sure you can’t get what you need with them before you use the new layout protocol.


Question: What is recommended way to style navigation bar in SwiftUI?

Answer: Check out the .toolbarBackground modifier, new in iOS 16 and aligned releases.


Question: Adding border to views when developing helps debugging/developing your code. Adding borders to every view in the screen can be tiring. Is there a way currently to automatically add borders to all the views in the screen with the press of a button/setting?

Answer: If you'd like to see the borders of each view in the preview you can use the non-interactive mode then in the menu bar go to "Editor > Canvas > Show View Bounds". Additionally, you can toggle "Show Clipped Views" in the same menu to show or hide views that extend beyond the edges of the preview.


Question: 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)?

Answer: 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.


Question: I'm working with CoreImage filters, and currently using the CIContext to create a CGImage, which is turned into a UIImage, which initializes a SwiftUI Image. Is there a better or more efficient way?

Answer: You should be able to avoid the intermediate UIImage and initialize a SwiftUI Image directly with a CGImage.


Question: If your app is always behind an authentication session what is a good approach for blocking the app's content when authentication is required? In UIKit apps it was common to display a separate UIWindow atop your app's main window. Is this still a good way of handling it in a SwiftUI app?

Answer: Interesting question! Without knowing the details of your app, my inclination would be to try the RedactionReasons API. You can use the .redacted modifier to set a redaction reason. SwiftUI controls will automatically react to that, hiding sensitive data. You can also read the reason from the environment to redact in custom controls. You could also use an overlay modifier at the root of your hierarchy to present a view over the whole app, then adjust that view’s opacity based on the log in state.


Question: What is the difference between SpatialTapGesture and TapGesture?

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


Question: Is there a newer way to debug a view while it's in preview?

Answer: Best way to debug a preview now would be to use Xcode's menu item Debug > Attach to Process (or the Process by Pid or Name) and select/use the name of your app. Then update a string literal in that view and it should cause the View's body to get called and hit any breakpoints you've got in that code.


Question: 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.

Answer: 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.


Question: 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.

Answer: I wouldn't consider it a bad practice. 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. But if you're using @EnvironmentObject with a class that's effectively immutable that shouldn't be a problem.


Question: 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?

Answer: 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.

Question: I think @ObservedObject also has a the initialiser init(initialValue:) is that preferred? The documentation doesn't mention any warning on this initialiser.

Answer: 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.


Question: We can now use .foregroundColor(.white.shadow(…)) on SFSymbols. Will this work with custom PNGs/SVGs as well?

Answer: .foregroundStyle apply only to shapes, image masks, and text. If the Image is configured as a template, then foreground styles should be applied. If the image is non-template, they won't.


Question: What is the advantage of using custom Symbols rather than SVG with template rendering mode? The ability to programmatically change the colors of several layers? Any other reason?

Answer: In addition to the great color support, using a custom symbol also helps it better fit with text by growing with fonts, respecting bold text, and matching baselines.


Question: Using .buttonStyle(MyCustonStyle()) instead of MyCustomButton(...) is more SwiftUI-y. But why should I prefer one over the other (considering MyCustomButton uses MyCustonStyle under the hood)?

Answer: We strongly recommend separating semantic controls and styles as it provides a lot more flexibility.

For example, using the button style modifier will apply to the whole hierarchy, so if you decide you want specific parts of your app to use a different button style, you can just apply it to that specific hierarchy without changing the control itself.

With this approach you also get multi-platform behaviors for free.


Question: SwiftUI Mac specific question:
When using .contextMenu on a List , how can I find out what was actually selected? When looking at Finder, the selected (=item on which the action should be performed on) is either the selection (1 or more) OR it is the item that has been right clicked on directly. How can I replicate this behavior using SwiftUI?

Answer: Check out the new context menu API that accepts a forSelectionType parameter. This passes the value of the selection into the modifier for you to act on.

List(selection: $selection) {
 ...
}
.contextMenu(forSelectionType: MySelection.self) { selection in
 // check selection
}

Be sure to match the type of your lists selection to the type you provide to the context menu modifier.


© Woodbytes