One of this year’s WWDC highlights was the introduction of the Digital Lounges. Unfortunately, it was as good as it was ephemeral. In an instant… puff! it was gone!
Many weren’t able to attend due to other commitments, lack of time, failure to register, etc. I personally failed to follow them as closely as I would have like, due to an inconvenient time zone (I’ll plan better next year). Still, before the channel was taken down, I managed to take my notes. Fortunately, when the moderators were asked, they said the information is not considered private and can be shared with others.
In that spirit, I have categorized, curated, and in some cases commented the SwiftUI Digital Lounge questions. For better and faster browsing, I simplified many of the questions to one (or two) lines. Still, the original text can be found inside. I have also removed all names, just in case someone is uncomfortable with it.
In some of the questions, I added my own comments, by either expanding on the topic, or appending an example of what is discussed. These questions are marked with 💬.
When a question is related to a new feature introduced in WWDC ’21, I flagged it with a ♦️ symbol.
I also put a ⭐️ mark, on those questions that I found particularly interesting. Some of the reasons why a question gets the star are:
- The answer provides a brand new piece of information.
- The answer from Apple confirms something we suspected for a long time, but wasn’t documented anywhere.
- The answer from Apple confirms a pattern we’ve adopted in the community, but not seen used by Apple until now.
- The question discusses a topic rarely treated and deserves some attention.
- The answer provides some insight into the inner workings of the framework.
- Or just any other question that made me stop to think 🤔
Introduction over… let’s start questioning…
Animations
Is there a way to animate a View moving from one place in hierarchy to another? Like when a View changes parents? 💬
Yes! You should check out the matchedGeometryEffect()
API, which was introduced last year: https://developer.apple.com/documentation/swiftui/view/matchedgeometryeffect(id:in:properties:anchor:issource:)
Comments: I have written a two-part series post with an extensive explanation of the matchGeometryEffect
modifier. Check it out here if you are interested.
Is there a way to animate text font size changes? ♦️ 💬
Full Question: Is there a way to animate text font size changes? Currently in iOS14 a view can smoothly animate between two sizes or colored backgrounds, but fonts will just jump from the before animation size to the after size with no interpolation in between.
Answer: This would be a great feedback to file, so that we can investigate!
If you’re interested in a workaround, the Fruta sample code project has an AnimatableFontModifier
that uses an explicit font size as its animatable data. This is used for the ingredient cards in the main smoothie detail view. This works for Fruta because the use-case is limited and the text using this modifier is for primarily for graphical purposes. https://developer.apple.com/documentation/swiftui/fruta_building_a_feature-rich_app_with_swiftui
Comment: I have a series of articles about advanced animation techniques, one in particular deals with animating text using an AnimatableModifier
. (Advanced Animations – Part 3), as shown in the example below:
Is it safe to employ multiple TimelineView with the animation schedule? ♦️ ⭐️
Full Question: Is it safe to employ multiple TimelineView
with the animation
schedule or is it equivalent to instantiating a number of CADisplayLink
s? I was thinking of the adage around “reusing” CADisplayLink
s as much as possible.
Answer: Yes! You should be able to use as many TimelineView
as appropriate to get your interface behaving how you want. SwiftUI will take care of scheduling things so they update like you want.
The thing to be careful of is to not have too much “different” between each update of the timeline content.
AppKit/UIKit
How can we get access to the underlying AppKit/UIKit that SwiftUI is built on? 💬
Original question: Sometimes with SwiftUI in AppKit or UIKit it’s only possible to do something by getting access to an underlying UI/NSViewController or NSWindow, but it requires some juggling to “peek beneath the covers” to surface these instances. Could SwiftUI provide more configuration proxies or some kind of factory-like mechanism to make this more explicit?
Comments: Although no answer was given to the question, the back and forth conversation asking what use cases the poster needed this for, indicate that there is no intention in providing such feature. Instead, they encourage people to file feedback to ask the addition of the missing feature that required “peeking” in the first place.
Although I would love to have this feature, I understand why we may never see it. If we start to peek on SwiftUI stuff under the cover, we run the risk of having our code break from one release to the next, simply because Apple changed the inner (private) workings of the view we are peeking.
Does SwiftUI have an equivalente to UIView’s drawHierarchy to render the view to an image?
SwiftUI doesn’t have API that supports this. That said, you can accomplish this by wrapping your SwiftUI view hierarchy in a UIHostingController
and applying drawHierarchy to the hosting controller’s view.
How can I control the ideal size of a UIViewRepresentable View?
Original Question: How can I control the ideal size of a UIViewRepresentable
View? I have a lot of trouble getting correct automatic sizing of wrapped views, especially if they wrap UIStackView
. Any recommendations for getting proper automatic sizing so I don’t need to use fixedSize
so much?
Answer: Try implementing intrinsicContentSize on your view.
Is it ok to use AnyView with UIHostingController? ⭐️
Original Question: I have a framework that vends a SwiftUI View to the main app via a HostingController. The view handles everything it needs internally, so all the types are internal. The only public method is the one that vends the HostingController. In order to maintain the isolation I do it this way: return UIHostingController(rootView: AnyView(SchedulesView(store: store)))
Is this a correct way to use AnyView
Answer: Yes, that’s an OK usage, particularly because it’s at the base of the hierarchy view and not used to add dynamism.
But there are other ways to encapsulate the exact type of the hosting controller, for instance returning an upcast or custom protocol type:
- Returning it typed as UIViewController instead its actual
UIHostingController<..>
type. - Creating a protocol with the extra API that clients might expect from the view controller, and returning it typed as that.
Or by using a container UIViewController that has a child of your hosting controller.
As an additional benefit, the calling module can remove the dependency on SwiftUI.
Why does a UIViewRepresentable update once after makeUIView and once before dismantleUIView?
The update function can be called for a number of reasons. It will be called at least once after make as the UIView
becomes extant. It may be called multiple times before the UIView
is eventually dismantled. You should not rely on any frequency (or lack thereof) of update calls.
Follow-Up: I have a UIViewDiffableRepresentable
that conforms to Hashable
to check whether properties have effectively updated and therefore prevent the updateUIView
call to through to expensive logic. Is it overkill? Is there a more sensible approach?
Answer: It’s overkill. The framework is able to call updateUIView
when properties outside of the representable struct have changed, like the environment. So, you’ll probably drop updates on the floor with this approach.
when creating a UIViewRepresentable, it is dangerous for the Coordinator to hold an instance of the UIView passed in updateUIView() or should it be strictly treated as ephemeral? ⭐️
That is OK! Your coordinator will have been created before any views are — so in makeUIView you can give the coordinator a reference to that view.
Is there any way to convert from the old AppDelegate/SceneDelegate lifecycle to the new “SwiftUI 2” lifecycle?
Yes! You can use the UIApplicationDelegateAdaptor
property wrapper in your App. Something like this: UIApplicationDelegateAdaptor var myDelegate: MyAppDelegate
SwiftUI will instantiate an instance of your UIApplicationDelegate
and call it in the normal fashion.
Furthermore, if you return a custom scene delegate class from configurationForConnectingSceneSession
, SwiftUI will instantiate it and call it as well.
Backward Compatibility
Can you talk about ways to have existing SwiftUI code inherit the newer features so I could support both iOS 14 and 15 with one codebase? 💬
Most new features do not back-deploy to earlier OS versions. You can use e.g.
if #available(iOS 15, *) {
...
} else {
// Fallback on earlier versions
}
to check for whether a feature can be used.
That said, some functionality is able to back-deploy. For example, the new ability to pass bindings to collections into List and ForEach, and get back a binding to each element, e.g.
ForEach($elements) { $element in
...
}
is able to back-deploy to any earlier release supported by SwiftUI.
Comments: Another WWDC’21 addition that is back-deployable, is enum-like styles.
Is the @ViewBuilder to remove AnyView (“Demystify SwiftUI” talk) only available with iOS 15? ♦️
No, in fact it can back-deploy to any previous version!
Coding Strategies
Do situations exist when the developers really need to use AnyView and there are totally no alternatives using Swift constructs? ⭐️
Lot’s of questions about when it is okay or not to use AnyView
!
If you can avoid AnyView
, we recommend that. For example, use @ViewBuilder
or pass view values around using generics instead of using AnyView
.
However, we offer AnyView
as API because we understand that there are some situations where no other solution is possible, or comes with other important tradeoffs.
One rule of thumb: AnyView
can be okay if used in situations where the wrapped view will rarely or never change. But using AnyView
to switch back and forth between different values can cause performance issues, since SwiftUI has to do extra work to manage that.
Which view body is computed first: child or parent? ⭐️
Original Question: From the dependency graph part of “Demystifying SwiftUI”: The video mentioned that both views, two views that are dependent on the same dependency, would need to generate a new body. Which view would need to generate a new body first if one view is the child of the other?
Answer: The parent view will generate its body first, and recursively travers all the child views.
How can we pass a view to a ViewModifier, without AnyView? ⭐️ 💬
Original Question: I have created a ViewModifier that adds a custom modal “overlay” on top of a view, somewhat similar to a sheet. Is there a way to pass a View as parameter of this ViewModifier initializer, without resorting to AnyView? I want to be able to pass the view that will be the actual “content” of the overlay.
Answer: You can do this by making your custom ViewModifier
generic on the content, something like: struct MyModifier<C: View>: ViewModifier { … }
, then using a property with the generic like so: var content: C
Comments: To expand on the issue, here’s a full example that implements a modifier that adds an overlay view only when the view is hovered over.
struct ExampleView: View {
var body: some View {
RoundedRectangle(cornerRadius: 10)
.fill(.green)
.frame(width: 200, height: 80)
.modifier(MyHoverModifier(hoverView: Text("hello!").font(.largeTitle)))
}
}
struct MyHoverModifier<C: View>: ViewModifier {
@State var isHovering = false
var hoverView: C
func body(content: Content) -> some View {
content
.overlay(self.hoverView.opacity(isHovering ? 1.0 : 0.0))
.onHover {
isHovering = $0
}
}
}
Group or ViewBuilder?
Original Question: Fun question on conditions: We need to put them at least in Group { if whatever { … } else { … } }. So … is it preferable to use ViewBuilder commands to create it, like ViewBuilder.buildBlock(pageInfo == nil ? ViewBuilder.buildEither(first: EmptyView()) : ViewBuilder.buildEither(second: renderPage) ) Awful to read, but is it better?
Answer: Using a Group is preferred in this case.
Follow-Up Question: Is that for readability reasons, or is there a performance (or other) benefit here? Isn’t the if/else above going to create the same conditional content block nested in the Group?
Answer: Mostly readability — it’s generally not recommended to call the result builder implementation directly and instead let the compiler handle it.
Can we use .id() to keep view equality? ⭐️
Original Question: If we apply same id in the condition, will SwiftUI sees this as a same view:
var body: some View {
if isTrue {
Text("Hello")
.id(viewID)
} else {
Text("World")
.id(viewID)
}
}
Answer: No, these will be two different views.
This is because body is implicitly a ViewBuilder. If you don’t use a ViewBuilder, such as in another property, it would be the same view.
Alternatively, the view can be written as:
var body: some View {
Text(isTrue ? "Hello" : "World").id(viewID)
}
Follow-Up Question: Are there any other examples except body
that are implicitly a ViewBuilder
?
Answer: Yes, ViewModifier
‘s body function, makeBody
for view styles, preview providers, and many more.
Follow-Up Question: so… we should avoid conditionals in view builders as a pretty much blanket rule?
Answer: Definitely not! They exist for a reason. The message here is to just be careful when using them over-aggressively.
Is there any situation in which Hashable may be preferred to Identifiable?
If you just need to be able to identify a value, that’s what Identifiable
is for, which means that only the id
needs to be Hashable
, not the whole type.
How can we use conditional modifiers with styles? ♦️
Original Question: How can we conditionally set different modifier, for example list styles?
List {
...
}.listStyle(isIpad ? .sidebar : .insets)
Answer: Styles in SwiftUI are static, and are not permitted to change at runtime. This is a case where a branch is more appropriate, but consider whether you really want to change the style— a single style is almost always the correct choice.
If there is a certain dynamism you’re looking for here, please file a feedback.
Is there a good way to apply a modifier to a SwiftUI view conditionally?
Original Question: Is there a good way to apply a modifier to a SwiftUI view conditionally? I’m using a custom .if modifier, and it refreshes the whole view on state change 🙁
Answer: Consider making an inert version of the modifier as discussed in the session. If there is a modifier that lacks an inert version that you’d like to see, please file a feedback.
When using the new SwiftUI Table View, can you Group to have more than 10 TableColumns? ♦️ ⭐️ 💬
Yes, you can; just like Views!
Follow-Up Question: Do I understand correctly that there are no more limitation on the number of objects in @ViewBuilder
?
Answer: There is no change to @ViewBuilder
this year, so it is still limited in the number of elements it can build into a block. But Group
as well as nested builders are great tools to allow for as many views to be combined together as you want
Comments: After some testing, I found that ForEach
and control-flow statements do not work with TableColumnBuilder
. That is most unfortunate, I can see many scenarios when that will be necessary. I have filed feedbacks: FB9189673 (ForEach) and FB9189678 (control flow).
How to avoid AnyView with Core Data and UIHostingController ⭐️ 💬
Original Question: When using SwiftUI with something like Core Data and UIHostingController
, how can we avoid using AnyView
if the environment modifier changes the type of the view. See example below:
import UIKit
import SwiftUI
import CoreData
struct MyView: View {
var body: some View {
Text("!")
}
}
class MyHostingController: UIHostingController<MyView> {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// custom stuff here
}
}
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let persistentContainer = NSPersistentContainer(name: "MyStore")
let rootView = MyView().environment(\.managedObjectContext, persistentContainer.viewContext)
let hostingController = MyHostingController(rootView: rootView) // this will not work anymore because type has changed with the environment modifier
// more stuff
}
}
Answer: Great question! Here’s a technique, though it runs the risk of angle-bracket-blindness…
In class MyHostingController: UIHostingController
MyView
isn’t really the right type. You want the type of MyView().environment(.managedObjectContext, persistentContainer.viewContext)
On the let hostingController = …
line, you’re probably getting an error message about the types not matching. That error message will include the full type of the right-hand side of the assignment.
Something like ModifiedContent<MyView, …>
with lots of stuff inside the …
there.
What I like to do is (1) copy that type, then (2) add a top-level type alias: typealias MyModifiedView = ModifiedContent<MyView, …>
where the right-hand side is the value I copied from the error message. Then you can write class MyHostingController: UIHostingController
Comments: The proposed solution used to work in the past, when the swift compiler complained with the value type. However, swiftc no longer reports the type, instead it reports some View
, which doesn’t help much. Luckily, with some minor modifications, it is still doable.
One way to determine the real type, is to just print it out:
let rootView = MyView().environment(\.managedObjectContext, persistentContainer.viewContext)
print("\(type(of: rootView))")
But that is not enough, because it still won’t compile. You will need to force cast your rootView:
rootView as! MyModifiedView
The full code will end up like this:
import UIKit
import SwiftUI
import CoreData
typealias MyModifiedView = ModifiedContent<MyView, _EnvironmentKeyWritingModifier<NSManagedObjectContext>>
struct MyView: View {
var body: some View {
Text("!")
}
}
class MyHostingController: UIHostingController<MyModifiedView> {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// custom stuff here
}
}
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let persistentContainer = NSPersistentContainer(name: "MyStore")
let rootView = MyView().environment(\.managedObjectContext, persistentContainer.viewContext)
let hostingController = MyHostingController(rootView: rootView as! MyModifiedView)
// more stuff
}
}
If SE-309 comes to effect and applies to View, will there be a difference between a View existential and AnyView in terms of view identity? ♦️ ⭐️
Original Question: SE-309 allows using protocols with associated types as existentials, assumedly including View
. At that point, will there be a difference between a View
existential and AnyView
in terms of view identity?
Answer: I ♥️ that proposal! I can’t comment on implementation details, but generally AnyView
erases more information than an existential, so the existential would still have the edge.
Are there any places where you think: yep, this is the place to use onAppear rather than task? ♦️ ⭐️
Answer (engineer #1): onAppear()
is still fine to use. There’s no need to update existing code that uses it. Going forward, I think task()
provides a more general solution, even for short synchronous work, since it sets you up to evolve towards asynchronous work if necessary in the future.
Follow-Up Question: So… you would always use task()
for new code, or are there still places where onAppear()
is the right solution? I was maybe expecting to see onAppear()
being gently deprecated or similar.
Answer (engineer #1): I’d always use task()
personally, but there’s also a nice symmetry between onAppear()
and onDisappear()
that some folks will want to maintain.
Follow-Up Question: Is there really any difference?
Answer (engineer #1): While task()
cancels any async work at onDisappear
time, it doesn’t trigger new work then.
Answer (engineer #2): We generally try to avoid deprecating things unless they’re actively harmful / dangerous / tricky. As we mentioned, onAppear
is more limited than task, so I would recommend using it for new code, but onAppear
isn’t harmful in any way.
Concurrency
Is the refreshable property the only SwiftUI property that supports async code? ♦️
The task
modifier also provides support for async code out of the box! Generally, we only make user provided closures async if we can provide some additional utility to you by doing so, such as canceling a task
when the attached view’s lifetime ends, or finishing the refresh animation. If you want to dispatch to async code in other places, you can use an async
block!
Is it possible to back-deploy concurrency? ♦️ ⭐️
For now you should assume that Swift concurrency cannot be back-deployed, however the team understands that this is a popular request and is investigating.
The fun part is that Swift is open source, and so you can visit https://forums.swift.org to peek in on the active development process for Swift concurrency, and they will likely discuss this topic more there as well.
Data Management
@StateObject or ObservedObject?
Original Question: 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 thedetailFetcher
property with something likedetailFetcher.itemID = itemID
in the body’sonAppear
. - Pass in the
detailFetcher
into the initializer likeinit(detailFetcher: ObservableObject)
and make the property@ObservableObject
? If this is preferred, where would thisdetailFetcher
live if not in SwiftUI?
Answer: 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!
Any way to control when the @SceneStorage gets saved?
Original Question: Any way to control when the @SceneStorage gets saved? Many are in my root app view, and they only seem to be written at app background, not before.
Answer: I’m sorry, but we don’t provide API to control scene storage. The intent is that it’s saved on background and restored on foreground/scene recreation.
Should @State and @StateObject be usually defined as private?
Yes! It’s often helpful to make them private
to indicate that the state is for this view and its descendants.
Is there any difference between the persistence of @State vs @StateObject vs @ObservedObject when identity changes?
Yes! ObservedObject
does not have any lifetime implications— when you use that property wrapper, you are responsible for managing the lifetime yourself. The object can live anywhere— either in StateObject
or possibly even in another data structure in your app.
What are the downsides of having the whole app state in a single StateObject set in the root view? ⭐️
Original Question: 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?
Answer: 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.
Follow-Up Question: What is your recommendation, assuming that we have multiple views depending on the same data that should keep views in the consistent/updated state?
Answer: I’m sorry but architectural choices are really context dependent, so there’s no blanket policy. Scoping down your model as you move down is one approach. Passing a binding to a subcomponent of your state is another.
Is there any difference between State/Binding vs EnvironmentObject?
Original Question: Are there any difference between passing a State
as a binding through each view in the hierarchy vs passing it as an environment object and only access it in the subviews that use it, say in a Text view would the first one have worse dependency graph that needs more updates?
Answer: environment object is a slightly different tool than State since it requires an ObservableObject
. Environment object is optimized to invalidate only the views that read its value.
As for State, when you pass the binding down to subviews, changing the binding will mutate the state that in turn will invalidate the view holding the state and its children.
Performance wise, ObservableObject or EnvironmentObject?
Original Question: Performance wise, is it preferred to pass ObservableObject
s down to subviews explicitly, or use EnvironmentObject
s?
Answer: Using one or the other shouldn’t make much difference in any given view, but if you don’t need to use the object in some views then EnvironmentObject
is a great way to avoid the boilerplate of pass it down through intermediary layers, and can avoid creating unintentional dependencies.
Is is ok to use child CoreData context as @StateObject?
Original Question: Is it okay to use child CoreData context as @StateObject vars in SwiftUI views, or would you recommend passing those in the environment and holding on to them outside of SwiftUI?
Answer: There is not need to use a @StateObject
since the context is not an ObservableObject
. Passing it down using the environment is a good solution.
Why the error “view environmentObject may be missing as an ancestor of this view”? ⭐️
Original Question: For when using @EnvironmentObject
, it seems like I need to pass the .environmentObject
modifier for every layer of subview. If I don’t do that, it will give error “view environmentObject may be missing as an ancestor of this view” But I thought the point of Environment Object
is to make data available to all view without writing a lot of code. Is that a design choice or am I doing something wrong?
Answer: Thanks for the question. .environmentObject(…)
mostly flows down the view hierarchy, however there are some places where we intentionally do not forward it. For example, the destination of a navigation link does not get the environment object, because there is a conflict as to whether the destination should get the object from the originating link or the placeholder of the destination, or even the previous view on the stack.
There was also a bug where sheets and popovers didn’t get the environment object, but that was fixed.
If you find other places where environment object does not propagate as expected, please file a Feedback. It’s important to me that we get this right!
May I call the StateObject initializer directly? ⭐️
Original Question: 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.
Answer: 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.
Follow-Up Question: May I suggest the documentation might be updated to reflect this? The documentation for StateObject.init(wrappedValue:)
states:
You don’t call this initializer directly. Instead, declare a property with the @StateObject
attribute in a View
, App
or Scene
, and provide an initial value.
Answer: Thank you for your feedback. If you find places where we can improve our documentation you should feel free to submit feedback for it.
Can custom property wrappers embed existing ones (e.g., State, ObservedObject, etc)? ⭐️
Original Question: If we create custom property wrappers that embed existing ones (like State, ObservedObject, etc) does SwiftUI still “see” those embedded wrappers and do the right thing? For example, can I create an @Whatever
wrapper as a convenience for @Environment(\.whatever)
and still expect that to work the same way?
Answer (engineer #1): Yes, you can make do that. As long as you add conform to the DynamicProperty to your wrapper this will work.
Follow-Up Question: Would I also need to implement update()
or is the default implementation sufficient?
Answer (engineer #1): No need to implement the update()
requirement.
Follow-Up Question: When implementing DynamicProperty
to my custom property wrapper, only those have SwiftUI’s property wrapper (like State
, ObservedObject
, etc) will trigger SwiftUI view’s update, isn’t it?
Answer (engineer #2): Or any other DynamicProperty
. State
, ObservedObject
and company all implement the DynamicProperty
protocol as well.
Follow-Up Question: Oh, that’s it. So I cannot implement DynamicProperty
by myself.
Answer (engineer #2): You DynamicProperty
should be implemented in terms of existing DynamicProperties
, correct.
Can a @State variable be mutated from outside the main thread?
Original Question: The State
documentation states “It is safe to mutate state properties from any thread.” What does it refer to considering that SwiftUI complains with runtime warnings when a PassthroughSubject
published from a non-main thread?
Answer: State
is thread safe and can be mutated from any thread. When you mention PassthroughSubject
I would imagine you are using that in the context of an ObservableObject
with either one of @StateObject
, @ObserverdObject
, or @EnvironmentObject
. ObservableObject
does require that you mutate all the properties observed by the view, and publish objectWillChange
on the Main Thread. I would recommend you check out Discover Concurrency in SwiftUI where my colleague Curt and Jessica talk all about Swift and concurrency.
Follow-Up Question: I am using it with onReceive
and a CurrentValueSubject
to set a State
within a modifier. Should I file a feedback.
Answer: I see. Yes, currently onReceive
expects you to publish on the main thread.
In general, notifying and do mutation for code that interact with the UI on the main thread is going to avoid you a lot of headache.
If an ObservedObject is passed into a view, will it get invalidated if it does not use the value? ⭐️ 💬
Original Question: 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?
Answer: 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.
Comments: To clarify with an example, the code below shows how IntermediaryA recomputes its body unnecessary when the model changes. IntermediaryB does not:
class Model: ObservableObject {
@Published var flag = false
}
struct ExampleView: View {
@StateObject var model = Model()
var body: some View {
HStack(spacing: 20) {
Button("Toggle") {
model.flag.toggle()
}
IntermediaryA(model: model)
IntermediaryB(model: model)
}
}
}
struct IntermediaryA: View {
@ObservedObject var model: Model
var body: some View {
print("IntermediaryA body computed!")
return Flag(model: model)
}
}
struct IntermediaryB: View {
var model: Model
var body: some View {
print("IntermediaryB body computed!")
return Flag(model: model)
}
}
struct Flag: View {
@ObservedObject var model: Model
var body: some View {
Image(systemName: model.flag ? "flag.fill" : "flag.slash.fill")
.font(.largeTitle)
}
}
However, this is precisely where EnvironmentObject
shines, by simplifying our code a lot. Intermediary
and Flag
no longer need parameters in their initializers!
class Model: ObservableObject {
@Published var flag = false
}
struct ExampleView: View {
@StateObject var model = Model()
var body: some View {
HStack(spacing: 20) {
Button("Toggle") {
model.flag.toggle()
}
Intermediary()
.environmentObject(model)
}
}
}
struct Intermediary: View {
var body: some View {
print("Intermediary body computed!")
return Flag()
}
}
struct Flag: View {
@EnvironmentObject var model: Model
var body: some View {
Image(systemName: model.flag ? "flag.fill" : "flag.slash.fill")
.font(.largeTitle)
}
}
Why do I get nil EnvironmentObjects on sheets? ⭐️
Original Question: I’ve had several intermittent crashes from environment objects being nil
when I pass them to a sheet or NavigationLink
. It’s tricky to replicate due to being intermittent and I usually work around it by architecting my code differently to avoid passing environment objects. Do you know of reasons this might happen? All I can think of is that the views that originate the environmentObject
further up the view hierarchy are being taken out of memory. Thanks for any help you can provide!
Answer (engineer #1): NavigationLink
by design doesn’t flow EnvironmentObjects
through to its destination as it’s unclear where the environmentObject
should be inherited from. I suspect this might what’s causing your issue. In order to get the behavior you expect, you’ll have to explicitly pass the environmentObject through at that point.
Answer (engineer #2):You can also apply the environmentObject
to the NavigationView
itself, which will make it available to all pushed content.
I need to support iOS13, but I need StateObject. What should I do?
Original Question: I am using @ObservedObject
now for my model, since I still need to support iOS 13. However I know that @StateObject
provides the correct behaviors to me. Is there a suggested way to use them at the same time for back compatibility? I first thought if #available
might work, but it does not work for a property.
Answer: For supporting iOS 13, you’ll need to use @ObservedObject
and keep your object alive through some other means, like using a static property or keeping a reference in your application delegate.
I don’t think trying to switch between observed object and state object buys you much here, since changing the owner of the object with availability checks would be awkward.
EnvironmentObject or ObservedObject: Which is better?
Original Question: 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?
Answer: 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.
Debugging
How can I debug AttributeGraph crashes? ⭐️
Original Question:Is there a way to debug AttributeGraph crashes? I’m getting AttributeGraph precondition failure: "setting value during update": 696.
, probably due to a hosting controller somewhere, but don’t know how to track it down.
Answer: That error usual means that some code evaluated in body or updateUIViewController
(or NS…) is mutating state.
We have a new debugging tool in the new SDK that might help narrow it down.
Inside body if you write Self._printChanges()
SwiftUI will log the name of the property that changed to cause the view to be redrawn.
(Note the underscore. This isn’t API, but is for exposed for debugging.)
Is there a way to profile SwiftUI code? ⭐️
Original Question: How do I profile SwiftUI code, to know how to optimize my views? Instruments is almost only showing SwiftUI library code, so it’s hard to see what is expensive to render…
Answer: Using the SwiftUI instrument will help call out expensive body methods. In addition it’s important to limit the number of times each views body will get reevaluated. Highly recommend watching the Demystify SwiftUI talk for some in depth looks at how this works.
Pro tip: call the new debug helper Self._printChanges()
inside body
to log which property caused the view to be reevaluated.
It’s not technically API —notice the leading underscore— so should only be used in debugging. My one sentence pro-tip is the extent of the docs I’m afraid.
Drawing
Is there an efficient way to set the values of individual pixels in a view in SwiftUI? ♦️
Original Question: Is there an efficient way to set the values of individual pixels in a view in SwiftUI? Sorry I haven’t fully examined the new Canvas
API yet. For the record, I don’t really know how to do this in UIKit either, other than writing a custom fragment shader for a Metal view. Thank you!
Answer: Canvas
is really the best bet here. You could fill a 1px x 1px rectangle of the color you want, and that would be the most efficient expression of that within SwiftUI itself.
And to get the size of a pixel you would request the pixelLength from the environment of the GraphicsContent
of the Canvas
.
Is there a way to disable pixel rounding in SwiftUI?
Original Question: Is there a way to disable pixel rounding in SwiftUI? The hover effect from my OS X Dock yesterday is jittery because padding is being applied to many views in a row, but rounded to the nearest pixel for each view. This inaccuracy in padding adds up, making the entire view jitter by a few pixels whenever I move the mouse.
Answer: SwiftUI layout is always rounded to the nearest pixel. But using any GeometryEffect
won’t take on the snapping behavior. Things like .offset
and .scaleEffect
are existing ways to achieve this, but you can also implement your own GeometryEffect
if you need something custom.
How far can we go (performance wise), with the new Canvas view? ♦️ ⭐️
Original Question: The Canvas
looks like a great new addition. I’m wondering about using it to render a single drawn surface that can be nested in a ScrollView
which is then panned, zoomed into etc. Is this a reasonable solution and in terms of performance, will there be any guidelines on how far to push it? I intend to put a whole bunch of images as well as other shape data in there.
Answer: This sounds like a good application for Canvas
. Like anything, performance will depend on how far you push it and the hardware it’s running on. I’m afraid that’s not a very actionable answer, but every app is different.
Comments: More information about Canvas, check Add rich graphics to your SwiftUI app.
How does Canvas behave with TimelineView? ♦️ ⭐️
Original Question: Hi guys, you all did a great job again congrats. My question is related to CanvasView
, that view is pretty awesome and opens lots of possibilities for creative and generative art for example. I would like to know how behaves a Canvas
encapsulated in a Timeline
receiving updates of Bindings Combine’s streams. During the render, does it lost the current time information, does it resets to re-render the new state or there’s no re-render at all and the Timeline
still running wherever the body changes?
Answer: Thank you! As long as the identity of the timeline is the same, and the value of the schedule didn’t change based on the body update, it shouldn’t trigger a new update of the contained Canvas
. If the content of the canvas itself changed, you will likely see another update from the TimelineView
with the date of the current entry of the schedule.
Focus
Is it possible to set focus programmatically this year? ♦️
Original Question: Are you able to programmatically change focus of text fields natively in SwiftUI this year? For example, having the a button at the bottom of the keyboard say “next” and focus into the next text field?
Answer (engineer #1): Look at the new focus API
Answer (engineer #2): Fun fact! @FocusState
isn’t just for Textfield
. On watchOS, you can use FocusState
to direct the Digital Crown and on tvOS for the remote.
Answer (engineer #3): Also check What’s New in SwiftUI and Direct and reflect focus in SwiftUI
Why doesn’t FocusState support an initial value? ♦️ ⭐️
Original Question: I noticed @FocusState
doesn’t support an initial value — instead, you can set the focus in onAppear
. Is there a reason for this?
Answer: One way to think of focus (not to be confused with the new user-facing feature we just launched) is that it is global state managed by the framework. A lot of the times, the user will be the one in control of this state, by selecting a text field, etc. The new @FocusState
and focused(_:)
API allows for some influence over that state as well, but ultimately, the source of this state is still internal to SwiftUI. Does that help to answer your question?
I tend to think of @FocusState
as a client’s view in into the state that the framework is managing.
Import/Export
Does the modifier of importsItemProviders work with other commands, or other stuff to import things in the app? ♦️
It works with any macOS system service that vends data. So if you had a service or shortcut that didn’t take input, but produced output — your app’s importsItemProviders
could consume that data when the user invokes that service.
Can importsFromDevices (i.e., continuity camera) work as a source for AsyncImage? ♦️
No, continuity camera will callback your importsItemProviders
with an item provider that will give the full image data and not a URL
. But if you have a use case where there could be some better integration between the two, please file a feedback
Layout
What is the layout and sizing behavior of Canvas? ♦️
A Canvas
will consume the space offered to it. You can put it in a .frame
to control the size if needed.
Is it ok to put GeometryReader inside a clear overlay to avoid impacting layout? ⭐️ 💬
Original Question: There’s a lot of sample code out there where people use GeometryReader
inside of clear overlays and the like to not impact layout… is that “ok”? Seems not like the way it was designed to work.
Answer: In general we’d consider that a bit of a misuse of the API. If you have specific use-cases where you’re needing to do this we’d love to hear about it in a feedback report!
Comments: I particularly liked this question. Many people stressed that although it’s ugly, sometimes it is just the only way to achieve certain layouts. I agree.
For a proof this is still an unfortunate pattern, read the following question, where the answer gives, in a way, its blessing to this technique (with reserves).
I encourage everyone that encounter a use case where this is the only solution, to file a feedback report. It is the only way this will ever change.
How can I get the size of another view? ⭐️ 💬
Original Question: Is there any way to get GeometryReader size from another view? I want to replace “???” with the height of “Hello world!”.
struct ContentView: View {
var body: some View {
VStack {
Text("Hello world!")
.background(
GeometryReader { proxy in
Color.clear /// placeholder
let _ = print(proxy.size.height) /// 20.333333333333332
}
)
Text("Height of first text is ???")
}
}
}
Answer: Howdy! 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
}
}
}
}
}
Then you can use your height State
property like usual.
⚠️ Beware: 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!
I think as long as you’re aware of the ⚠️ gotcha, you can use this technique. I would like to say though that this is becoming a common pattern and we would love feedback on how to improve this experience in SwiftUI —if you explain your use cases in feedback it’ll help the team understand what you’re trying to do!
Comments: There are multiple opinions about this technique (read the previous question).
I have rewritten the example from the question, to accommodate the answer given:
struct ExampleView: View {
@State private var height = 100.0
var body: some View {
VStack {
Text("Hello world!")
.background(
GeometryReader { proxy in
Color.clear
.onAppear {
height = proxy.size.height
}
.onChange(of: proxy.size.height) {
height = $0
}
})
}
Text("Height of first text is \(height)")
}
}
The Dependency Graph — can it have loops, or is it acyclic?
Graph cycles are not allowed and will trap at runtime.
How can we maintain view equality when switching its container from HStack to VStack? ⭐️ 💬
Original Question: Is there a good way to switch between HStacks and VStacks while allowing SwiftUI to understand that the contents of those stacks are the same?
Answer: Lots of questions similar to this, so repeating an answer from earlier: Yes! You should check out the matchedGeometryEffect()
API, which was introduced last year. Docs here.
Comments: I have written a two-part series post with an extensive explanation of the matchGeometryEffect
modifier. Check it out here if you are interested.
Is there enough public API to create our own containers with a fully custom layout? ⭐️
Original Question: Does SwiftUI expose enough API that would allow us to build our own Lazy{H,V}{Stack,Grid}
with a fully custom layout, or are there still a lot of “magical bits” under the hood that prevent us from doing so?
Answer: Unfortunately, we don’t offer support for building custom layouts today.
frame() or Spacer()? ⭐️ 💬
Original Question: 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?
Answer (engineer #1): 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.
Answer (engineer #2): 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.
Comments: To illustrate one case where frame
and Spacer
behave differently, consider the code below. Because the spacing parameter in the HStack
containers is left unspecified, SwiftUI uses auto padding. Spacer
and .frame
are handled different when auto padding. Although the outcome of both HStack
views will be very similar, they won’t be identical.
struct ExampleView: View {
var body: some View {
VStack {
HStack {
Spacer()
Circle().frame(width: 50, height: 50)
Spacer()
Circle().frame(width: 50, height: 50)
}
.border(Color.blue)
HStack {
Color.clear.frame(maxWidth: .infinity, maxHeight: 0)
Circle().frame(width: 50, height: 50)
Color.clear.frame(maxWidth: .infinity, maxHeight: 0)
Circle().frame(width: 50, height: 50)
}
.border(Color.blue)
}
}
}
Specifying an HStack
spacing, however, will make both HStack
views generate the same result.
What is error: Bound preference SizePreferenceKey tried to update multiple times per frame?
Original Question: How do we avoid incurring in Bound preference SizePreferenceKey tried to update multiple times per frame
?
It sounds like you have a cycle in your updates. For example, a GeometryReader
that writes a preference, that causes the containing view to resize, which causes the GeometryReader
to write the preference again.
It’s important to avoid creating such cycles. Often that can be done by moving the GeometryReader
higher in the view hierarchy so that its size doesn’t change and it can communicate size to its subviews instead of using a preference.
I’m afraid I can’t give any more specific guidance than that without seeing your code, but hopefully that helps you track down the issue!
List Views
How can I change a List background color?
Original Question: Is there a way to change the background colour of a list? Last year this wasn’t possible.
Answer: Currently it’s only possible to change background color of list rows, not the List
itself.
Follow-Up Question: Thanks, is this intended, or something that might change in the future? If so, what is the best way to change the background colour currently? I’d like to use system materials specifically.
Answer: Unfortunately, it’s not possible to use system materials with List
at the moment, please file a feedback and we will take a look at it. In the mean time, you could try using a ScrollView
instead of a List.
How can we make List rows editable? ⭐️ 💬
Original Question: How would someone go about turning a List
cell into a TextField
on tap, such as to edit the name (similar to the Reminders app)?
Answer: I’d try putting a TextField in an overlay of your cell content with an opacity of 0 and disabled. Then use a tap gesture to switch the opacity and un-disable it.
You could use the new FocusState
or onSubmit
when the user is done editing to switch back.
The trickiest bit will be getting your TextField
and the regular Text
to line up. You might need some custom padding for that. Take a look at the ScaledMetric
property wrapper to make padding that adapts to dynamic text size.
Follow-Up Question: What about the new selectable
text modifier?? Would that work here?
Answer: That’s a great idea. It would work for selecting and copying the text, but not for editing it.
Follow-Up Question: Is there any way to create a frame that takes up the same size as the text and then set that as the editable area to avoid padding inconsistencies across device screen sizes?
Answer: If the TextField
is an overlay on the Text
, it should be constrained to the same size. The challenge is that editable fields (on iOS at least) include some insets that static text does not. I’m imagining something like this:
Text(itemText)
.padding(2)
.overlay {
TextField($itemText)
}
That’s missing the bits to hide and show the pieces.
The trouble is if itemText
is a short string, the whole assemblage will be short. I think the toggling is the hard part and the rest will take some experimentation.
Follow-Up Question: So would it be better to put the Text
inside of a frame with the overlay
? That’s about all I can think of right now without diving into the documentation.
Answer: Yeah, a flexible frame seems like the right answer. Perhaps with a layout priority greater than 0 so it consumes most of the space.
Comments: To complete the answer, here’s an implementation of what was discussed. I did not implement any compensating padding, because that will very much depend on the platform you are using. Also, I like there to be a difference in padding when editing, it provides feedback to the user to know a row is being edited.
struct ContentView: View {
@State var editingId: Int = 0
@State var data = [Person(id: 1, name: "Albert"),
Person(id: 2, name: "Chris"),
Person(id: 3, name: "Mary")]
var body: some View {
List($data) { $person in
Text(person.name)
.frame(maxWidth: .infinity, alignment: .leading)
.overlay(alignment: .leading) {
if person.id == editingId {
TextField("", text: $person.name)
.textFieldStyle(.roundedBorder)
.onSubmit { editingId = 0 }
}
}
.onTapGesture { editingId = person.id }
}
}
}
How can I prevent a button activation when its row is selected?
Original Question: Is it possible to prevent a Button
inside a List
item from being activated when the list row is selected? Right now, if you have multiple Buttons
they will all be triggered when the row is selected, so can’t really have a secondary button in a list row.
Answer: Explicitly setting the buttonStyle
of the nested buttons will stop the List
from capturing the event.
Which one I choose? List(items) { … } or List { ForEach(items) { … } }
Original Question: When to iterate a collection directly from:
List(items) { items in
...
}
vs.
List {
ForEach(items) { items in
...
}
}
Answer: Fundamentally these should provide the same behavior, in almost all use cases this would be a stylistic choice over which to use. The former works great if you know you’ll only ever have that single array of items in your list. The latter is where I generally start because it’s less things to change if I want to provide content from multiple sources into my list.
How can we sort a List?
Original Question: What is a good way to have a custom order of elements in a list so the user can change the order (using a stable ID)?
Answer: A List
or ForEach
will preserve the same order as used in the collection passed into them. The identity of each element should be independent of that order — even if a collection is reordered, each element should maintain the same ID.
If you maintain a stable identity like that, then you should be able to reorder the collection no problem in response to user actions
How can we bypass system styling, for example, with List sections headers?
Original Question: What is the best practice for bypassing the system styling for a component?
For example, in a Section
header in a grouped List
, SwiftUI automatically dims the content and capitalizes all text (as you would expect in a text-only section header for UIKit). Is there a built-in way to bypass this dimming and capitalization?
Answer: Many of the customizations you see are part of the default styling that lets you have the most natural feeling UI by default. However, most of these default stylings should be overridable by using the same modifiers you would to get that style. For example, if you want to remove the default capitalization of the section header, you can use the text case modifier:
Section {
// ...
} header: {
Text("My Header")
.textCase(nil)
}
The same goes for other customization points like foreground style, font, etc.
NavigationView
Performance
Are NavigationLinks “lazy” in iOS 15? ♦️ ⭐️
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.
Does AsyncImage support caching? ♦️
AsyncImage
uses the shared URLSession
, and so uses the shared URLCache
. There’s currently no support for customizing the cache.
If that’s something you’d like support for though, feel free to file feedback with that request
Does using long strings as ID present a problem for performance? ⭐️
Original Question: If the only reasonable property for establishing stable identity in a model from, say, an existing JSON API, is a string that might be quite long (maybe it’s more of a description than a name), is that large ID enough of a potential performance problem that we should consider modifying the model/API to add a more compact ID? Thanks much!
Answer: Great question! As with any performance problem, it’s best to measure first before over-optimizing. Long strings can often be expensive, so it might make sense to optimize the identifier, but I’d recommend measuring first.
Are there any combinations of parameters in .frame(maxWidth:…) more performant than others? ⭐️
Original Question: When using View.frame(minWidth:, idealWidth, maxWidth:…)
are there any combinations of parameters that are more performant that others? Any combination to avoid?
Answer: There isn’t really any performance difference between any combination of parameters. You should just use the appropriate values to get the layout you’re looking for.
Follow-Up Question: what’s the best way to enforce a specific size? min + max? or ideal? or all?
Answer: You would want to use the other version of .frame
for that which just takes a width and a height. View.frame(width:height:)
Sample code in the Advanced Graphics section of Whats New in SwiftUI is too slow? Why? ♦️ ⭐
Original-Answer: Dumb question here… but I was just playing around with the code in the Advanced Graphics section of Whats new in SwiftUI. Taylor showed a symbols browser…. I copied the code and when I run the performance is really bad… he showed the smooth gesture with the fisheye and the timeline view animation… on my side it is super jerky with seconds delay to render… is that right?
Answer: Sorry about that! That’s a known issue in beta 1. As a quick workaround to see the same smooth effect today, you can manually cache the resolved images.
One thing you could try is moving that resolution to be outside of the inner for loop, so it only happens once. (cheating since for the code snippet it only uses the swift bird rather than every symbol like my demo had).
Do we incur in a performance penalty for using ViewBuilders? ⭐️
Original-Question: If we break our Views into separate some View
properties to help readability, is there much cost to marking those other properties as ViewBuilders
to get the nicer syntax? Is that something we need to worry about?
Answer: Nope, in fact, we encourage you to do so! Using the @ViewBuilder
syntax helps nudge you towards structuring your code in a way that SwiftUI can make use of intelligently, so using it in more places is never a problem. Check out the talk, Demystify SwiftUI for more on this!
Animating changes of the data set on List views is very slow. Is this expected? ♦️ ⭐️
Original Question: I noticed that while using Combine and lists (say local search combined with remote results). Lists have a really, REALLY hard time keeping up with animated updates. I found the only reliable way to force correct data representation is to use .id(UUID())
and turn off animations. Is this somewhat expected?
Answer: We worked really hard on improving List
performance in iOS 15, macOS Monterey, and aligned releases. Please try there and let us know if you’re still seeing issues. If you aren’t doing so already, it also may be good to debounce the queries.
Is GeometryReader bad for performance? ⭐️
Original Question: Are GeometryReaders
really bad in terms of performance? I’m getting the feeling that they should be avoided, but I don’t know if it’s because they’re inefficient, because they’re “breaking” the layout, or because other solutions may exist (like anchored preferences) and be better suited for the purpose?
Answer: Just like any tool, GeometryReader
has a time and place where it is correct to use. There aren’t any particular performance pitfalls I’d call out with them, but they shouldn’t be used as a hammer.
Presenting Views
What is the proper way to dismiss a .fullscreenCover?
Original Question: What is the proper way to dismiss a .fullscreenCover when it is being presented based off an item: setting the item to nil or dismissing using the presentationMode?
Answer: That really depends on what is doing the dismissing. They both have the same effect in setting the item binding back to nil. It is more about where you are driving the dismissal state from. If you’re dismissing from within the sheet’s content, then using the presentation mode will be more flexible because that will work no matter what is controlling the presentation.
There is also a new environment property introduced this year that can accomplish this as well, which is dismiss
How to avoid error “Attempt to present <X> on <Y> which is already presenting <Z>”?
Original Question: Is there a suggested way or best practice to show a UIDocumentPickerViewController
in a SwiftUI sheet without triggering an “Attempt to present <X> on <Y> which is already presenting <Z>”?
Answer: You should check out the .fileImporter
modifier if you haven’t already (doc here). If you are encountering a specific issue that you think may be a bug, please file a Feedback report!
What’s the recommended practice for custom sheet presentations? ⭐️ 💬
Original Question: What’s the recommended practice for custom sheet presentations? SwiftUI’s .sheet
only supports the standard card-style modal presentation. It doesn’t have the flexibility of e.g. UIKit’s transitioning delegates and presentation controllers to let us size and position modally presented controllers.
Answer: Thanks for the question. For custom presentations, I’ve had good luck putting the presentation content in an overlay, then using an offset modifier to shift it off screen. To bring it on screen, I use State to zero out the offset. If the state is updated inside withAnimation to transition is animated.
Follow-Up Question: That content won’t be lazy then, right? It’ll be computed in advance and off-screen.
Answer: You could conditionalize the content too. An offscreen empty view would be essentially free. As would an offscreen view with opacity of 0. You might need to play with the transitions to get the appearance just right.
Follow-Up Question: And it’s essentially free because the framework is smart enough to not bother rendering content that’s invisible?
Answer: Yep!
Comments: For a hero animation approach, check the example project from my article matchedEffect Part 1
Refreshable
Can the new .refreshable API be used with custom refresh controls/spinners? ♦️ ⭐️ 💬
Take a look at the <strong>EnvironmentValues.refresh</strong>
property. The refreshable modifier sets this property up with closure provided to the modifier. You can query this property to hook up to your own UI.
Comments: An example was requested, but unfortunately none was given. After superficially looking into it, it seems the List view refreshable appearance is not customizable. On iOS, it will always be a standard progress view on top, and refresh will trigger with a swipe gesture (although additional triggers may be added by calling the EnvironmentValues.refresh
action).
The answer, I think, is aiming at making other views refreshable, but using custom refresh triggers and appearance.
Is there a way to make refreshable work with LazyVGrid? ♦️ 💬
Refreshable is currently only supported by List
.
Comments: If I understand this correctly, this is not entirely accurate. According to the previous question, it is possible to implement refreshable with custom views. What is unique to Lists, is the pull to refresh gesture and progress view on top.
This means a refreshable action can be associated to just any view, by putting it into the environment (via the .refreshable()
modifier). Then you must implement the UI yourself, and when the user triggers the refresh, call the action you obtain from EnvironmentValues.refresh
.
Does refreshable() work with ScrollView? ♦️
Original Question: Refreshable doesn’t work with scroll view. Is it a bug or desired behaviour?
Answer: Currently refreshable only supports List
. Please file a feedback request if you have other use-cases you’d like to see supported.
Searching
Using @Environment(.isSearching), how would multiple navigation bar in an app work? ♦️
The isSearching
environment property is tied to the searchable modifier that sets it up. If you have the following:
NavigationView {
ContentView()
DetailView()
}
.searchable(text: $text)
Then you could query the isSearching
property inside of ContentView
or DetailView
. If you have the following:
NavigationView {
ContentView()
DetailView()
.searchable(text: $text)
}
Then you could only query it inside of DetailView
. Similarly, if you have the following:
NavigationView {
ContentView()
MiddleView()
.searchable(text: $text)
DetailView()
.searchable(text: $text)
}
Then you could query isSearching
in either MiddleView
or DetailView
and the property would relate to the enclosing searchable modifier.
Is .searchable for local data only, or can it be used to query services? ♦️
Searchable specifies that the view supports search. You can implement that search however works best for your app.
For example, you could kick off a query using the bound search term, then update the results when your query completes.
How would one go about adding the .searchable modifier to a PDFView? ♦️
On iOS, the searchable modifier works with a navigation view to render its search field. On macOS, it works with a navigation view or the root view of your scene. If you combine a PDFView
with a navigation view, things should hopefully work as you expect. If not, please do file a feedback with some more details on what you would like to achieve.
SF Symbols
Can we use the new buttons with non-SF single-color graphics? ♦️
Yep, in fact the new buttons can have a label of any view, including shapes and more! 😀
The one thing to be careful of with custom images is that they use template rendering if you want the standard foreground styling within the button (otherwise, they’ll be the exact color of the image’s pixels).
Does using more SF Symbols have an impact on total app size? Or they all are stored in the OS?
These are part of the OS, so you can feel free to go wild with all the symbols you want with no impact to app size.
Is there a limit to where SFSymbols change size?
Original Question: Is there a limit to where SFSymbols change size? I put them as items in a TabView but it doesn’t seem like they are scaling with dynamic text…
Answer: TabViews do have a standard symbol size across all apps and how far they scale.
Follow-Up Question: Does that mean they don’t scale as much as other places?
Answer: Right, other elements could end up scaling more, which is expected.
Follow-Up Question: As a UI element I sort of wanted it to scale more. Is there a better way to do that. I’d rather not leave SF Symbols.
Answer: If you have a more custom UI element where you want that scaling, you could build that! Especially with the new .bar
material, you could still get a similar material background behind that custom bottom bar.
It ends up being the same API, just using .background(.bar)
instead of .thinMaterial
More information about materials: Add rich graphics to your SwiftUI app
Text
Are paragraph styles supported by AttributedStrings in Text? ♦️
Original Question: Is it possible to specify paragraph styles AttributedStrings used by SwiftUI Text
views? I don’t see any mention of paragraph style in the SwiftUI AttributeScope
(but it’s there for the UIKit scope)
Answer: SwiftUI currently supports all of the attributes in the SwiftUI scope and some in the nested Foundation scope, the supported Foundation attributes are link
and inlinePresentationIntent
. So it’s not possible to specify paragraph styles
Can the new AtrributedString be used to provide accessibility notations in SwiftUI? ♦️
Original Question: Can the new AtrributedString
be used to provide accessibility notations in SwiftUI like NSAttributedString
s can in UIKit? Should we report any attributes from NSAttributedString
we need on AttributedString
?
Answer (engineer #1): AttributedString
should have the full suite of accessibility attributes and they should now be standardized across platforms! But if you see any we missed, please do file Feedback so we can fix any oversights.
Thanks for the question. We’re super excited about the AttributedString support!
Localization, accessibility, formatting, and morphology agreement in one tidy package.
Follow-Up Question: I was looking for pronunciation control but couldn’t find it. Does it have a different name?
Answer (engineer #2): Those are not yet available. But we are very aware of the issue 😉
In the meanwhile, there is some new AX API that can be applied to Text
itself, such as speechSpellsOutCharacters
, accessibilityTextContentType
, and more.
Markdown text in a variable won’t work. Is it a bug? ♦️ 💬
Original Question: When I put Markdown-formatted text as a String
literal in a Text
, it formats with nice attribution, but when I pass it in from a variable, it doesn’t. Is this a bug, or have I misunderstood something about how Texts take Markdown-formatted Strings?
struct ContentView: View {
var text: AttributedString = "**Hello**, `world`! Visit our [website](https://www.capitalone.com))."
var body: some View {
VStack {
Text("**Hello**, `world`! Visit our [website](https://www.capitalone.com)).")
Text(text)
}
}
}
The first Text
renders nicely with bold, code and a link, while the second doesn’t – it just shows the Markdown annotations in the string directly.
Answer: I think that’s a bug in beta 1. You might try Text("\(text)")
to trigger the use of a different initializer.
Comments: If you have a string literal and you want to avoid markdown treatment, use Text(verbatim:)
WatchKit
When using the WatchKit way to get text input, do we still get the text input improvements from SwiftUI? (e.g. remembering the user choice of scribble vs. voice input)
If you don’t pass any suggestions to the WatchKit API, then yes, you get all of the new behavior.
Window Management
What is the recommended way to open a View in a new window in SwiftUI for macOS? (1)
We don’t have much API in this area at the moment – using a NavigationLink in a commands context will open a window with the destination view.
Follow-Up Question: We did a convoluted combination of URLs and handlesExternalEvents
to trigger a WindowGroup
which seems to work?!? Not sure if that’s a best practice though – was hard to find details on this.
Answer: Yes, that works due to the default behavior of the handlesExternalEvents
, which is to create a new window for the matched scene.
Certainly any feedbacks filed here would be much appreciated, and can help inform our APIs in this area.
What is the recommended way to open a View in a new window in SwiftUI for macOS? (2) ⭐️
Original Question: Is there any way to open additional window from SwiftUI Life cycle? I mean call additional WindowGroup
. For example Inspector panel.
Answer: Hi – we currently do not have any API for this, though we’d love if you could file a feedback with any specifics you have for your use case. One option for something like an inspector panel is that you could use NSApplicationDelegateAdaptor
, and open your window by communicating with that. A secondary WindowGroup
might not be the best match for something like an inspector panel, since WindowGroup
supports multiple windows, and will should up in the File -> New menu.
Oh, I should mention that the window created via the delegate adaptor would be an NSWindow
instance, with an NSHostingView
as the contentView
.
Follow-Up Question: Thank you! Previously you suggest to use NavigationLink
in a commands context to open a new window. I have tried and it works. It looks a little tricky. Its good solution or is it better to avoid it?
Answer: Yes, that is also supported – we will open a new window with the destination view as the content. This could also be a good fit for your case, since it will only ever make one window for that destination (it will be brought to the front if its already open and the user elects the menu item again).
Follow-Up Question: And is there any way to make WindowGroup
open only single window? Or this is the only way CommandGroup(replacing: .newItem, addition: {})
Answer: No, the design of WindowGroup
is to support multiple windows on platforms that support it (iPadOS and macOS). What you have there will effectively remove the menu item. The problem with this approach is that if the user closes the window, they will not have an easy way to get it back, short of clicking the dock icon. If your app is one that makes sense to only have one window, we would certainly love a feedback about that, though.
Follow-Up Question: One more question about using NavigationLink
. It works when I put it into .commands
. It is possible to add it to toolbar. I was try this:
.toolbar(content: {
NavigationLink("Test", destination: Text("Hello, world!").padding())
})
But the button is disabled in the toolbar.
Answer: I do not believe we support that, though feedbacks are always welcome for this as well. 🙂
Is there a way for a view to know when the window it is hosted in is key or not on macOS?
Could you elaborate some on what you’re trying to achieve? There is the controlActiveState
property on the Environment
, but there is also focusedValue(:<em>_:_</em>)
and @FocusedValue
which are used in the context of the key window on macOS.
What is the handlesExternalEvents modifier? ⭐️
This modifier allows you to specify a Scene
to be used when external data is sent to the app – a URL
or an NSUserActivity
. For example, on macOS, when a url comes in that matches the pattern, we will either create a new window for that Scene
, or use an existing one, if a view has been modified with handlesExternalEvents
to prefer that incoming value.
Follow-Up Question: About that. In my case I always get a new window when I use handlesExternalEvents
. How I can tell system o use already existed window and not to create another one?
WindowGroup {
ContentView2()
}
.handlesExternalEvents(marching: ["texturl://open"]
Answer: You can apply the view modifier with the preferring:
parameter – if an existing window prefers the incoming value, we will use that rather than creating a new one.
Depending on your case, you may wish to use this in tandem with onOpenURL
.
The handlesExternalEvents
modifiers are a way to tell the system which Scene
to choose, if you have more than one. onOpenURL
will get the actual data, though.
For SwiftUI macOS document apps, is there a recommended way to save and restore window size and position?
Hi, when state restoration is not enabled on macOS, this is expected behavior at the moment. We’d welcome a feedback for this, though. If you could include any information about your use case as well, that’d be very helpful. Thanks!
Summary
Congratulations for reaching the end of this long post. I hope you enjoyed the SwiftUI Digital Lounge content.
Please feel free to comment below, and follow me on twitter if you would like to be notified when new articles come out. Until next time!
Thank you for this 🙏
About the AnyView discussion what do you think “..okay if used in situations where the wrapped view will rarely or never change.” means ? What should rarely change, the view identity or the view state ?
e.g. : Changing the erased view from a Rectangle to a Circle or changing from a red Rectangle to a green one.
Hi Pierre, I think he’s referring to the identity, not its state. AnyView erases the type, so changing the view from Rectangle to Circle, changes the type. Changing the color, it does not.
That’s what I hopped for.
Thank you Javier.
Hello, Thanks for this compilation!
I’m the one who asked the question “frame() or Spacer()?”.
There is a little misunderstanding about .frame(maxWidth:) I mean. In your example, what I said is something like:
But It doesn’t affect the final conclusion you are saying. So, anyway 😉
BTW, I really learned A LOT from SwiftUI-Lab! Especially the three articles about PreferenceKey.
Thank you for this incredible blog!
Hi Lynn! Thanks for the question. When I was going through it, I had to stop to think… Since there wasn’t an example, I though it was a good idea to find a case where one would expect both to behave identically, but didn’t. I realized it wasn’t probably what the poster (you ;-), had in mind… but better share than not 😉
Thank you for compiling all this information! I’m sure I will be referring to it in the future.
Javier, thank you
I admire your enthusiasm to carry the word of knowledge about SwiftUI
Wow my question is there!
Javier thank you for compile and organize this.
Q: Does SwiftUI have an equivalente to UIView’s drawHierarchy to render the view to an image?
A: I write a Package for Screenshot the View for SwiftUI. https://github.com/rushairer/ScreenshotableView
Thank you for this site and the articles.
Especially this page on the content of the Digital Lounges.
I have personally been occupied by many things other, and is only beginning to catch up with WWDC21 and the new features of IOS15.
This is invaluable.
Thank you
Regards
Lars