translationRandom Lessons from the SwiftUI Digital Lounge
It is recommended to read the code landscape
This article has been published once before, but the translation of that version is often flawed and ungrammatical.
This edition reorganizes the translation, and enables the new typesetting style.
One of the highlights of this year’s WWDC was the introduction of the Digital Lounge. Unfortunately, good things are often short-lived.
Many people are unable to participate in the channel for various reasons, including lack of time, failed registration and so on. I personally haven’t been able to follow it because of my daily schedule. The channel’s offline, but I took notes. Fortunately, the host said the questions and answers asked on the channel could be shared.
With that in mind, I’ve catalogued and compiled SwiftUI Digital Lounge’s questions and responses, and added comments to some of them. For ease of browsing, I have reduced the descriptions of many of the questions to one or two sentences, while also keeping a record of the original questions.
If a problem relates to a new feature introduced by WWDC 21, I tag it with ♦️.
I also use the ⭐️ tag for those issues that are of particular interest to me, for several reasons:
- Apple’s response provided fresh information
- Apple’s response confirms something we’ve suspected for a long time, and it’s not documented
- Apple’s response confirms patterns we already use in the community that are not currently officially used by Apple
- The question touches on something that is rarely mentioned but worth noting
- Apple’s response offers some insight into the inner workings of the framework
- Or just something that makes me stop and think 🤔
With that out of the way, let’s get to the questions and answers.
Translator’s note: Due to its long length, the translation will be published in three articles on the official account.
animation
Can views be animated from one location to another? Like changing the scene of the parent view? 💬
Of course, you can go to take a look at the matchedGeometryEffect (), this is the last SwiftUI version 2.0 API, the introduction of link: developer.apple.com/documentati… 🙂
MatchGeometryEffect modifier the author has written a series of articles explaining it in detail.
Can text font size changes be animated? ♦ ️ 💬
Full question: Is there a way to animate text size changes? Because size and background color can now transition smoothly, the font will jump directly without interpolation.
Answer: Great feedback, we will review the requirement.
If you’re interested in an existing solution, check out the AnimatableFontModifier in Fruta’s Example project, which uses explicit font size as animatable data, using scenes as a smooth transition effect for ingredient cards on the main view and detail view. This implementation is sufficient for Fruta, given the limited number of use cases. Link: developer.apple.com/documentati…
The author also has a series of blog posts on advanced animation techniques, one of which specifically mentions AnimatableModifier (Swift garden also published a translation of this blog – “SwiftUI advanced animation – part3: AnimatableModifier “for interested readers)
Can I use multiple TimelineViews scheduled with animation at the same time? ♦ ️ ⭐ ️
** Complete question: Is it safe to use multiple TimelineViews scheduled at animation frequency at the same time? Or is this equivalent to instantiating multiple Cadisplaylinks? I was thinking that CADisplayLink has best practices for reuse as much as possible.
** Replies: ** Sure! You can use as many TimelineViews as you want to make your interface behave the way you want. But be careful not to make too many variations every time the timeline is updated.
AppKit/UIKit
How do we access the AppKit/UIKit API under SwiftUI? 💬
Sometimes things can only be done by accessing the underlying UI/NSViewController or UI/NSWindow, which requires some “sleight-of hand” snooping under the surface of SwiftUI. Does SwiftUI provide some mechanism to make this kind of operation configurable or clearer?
Although the question went unanswered. The conversation around this issue suggests that there is no intention of providing such a mechanism. They encourage feedback on missing features, rather than prioritizing snoop tricks.
I wish there were, but I can understand why we won’t see it implemented. Because once we adopt a strategy of looking inside to implement something, our code will be exposed to the risk of version incompatibilities because Apple will likely change the internal workings of views in the future.
Is there an API in SwiftUI like drawHierarchy in UIView for drawing a view into an image?
SwiftUI doesn’t have an API to support this, but with UIHostingController we can wrap the SwiftUI view and then use drawHierarchy on the HostingController view to achieve this goal.
How do I control the ideal size of UIViewRepresentable?
Original question: ** How do I control the ideal size of a UIViewRepresentable view? I’ve had a lot of trouble getting the automatic size of the wrapped view, especially when the wrapped view is UIStackView. Is there a recommended way to get the proper automatic size? So I don’t have to rely too much on fixedSize?
You can try implementing intrinsicContentSize for your view.
Can UIHostingController be used with AnyView? ⭐ ️
** I have a framework that emits a SwiftUI view to the main app via UIHostingController. This view handles everything it uses internally, so all types are internal. The only public method is to give out UIHostingController. To implement isolation maintenance, I do this: return UIHostingController(rootView: AnyView(SchedulesView(Store: Store))). Is this a correct use of AnyView?
Yes, this usage is fine, especially if AnyView is used at the most basic level of the view hierarchy rather than for dynamic implementation.
There are other ways you can encapsulate the exact hosting controller type, such as returning an upcast or custom protocol type:
-
The UIViewController type instead of the actual UIHostingController<.. > type return
-
Create an EXACT API of the type the client expects to return, and then return that type
Alternatively, you can wrap your Hosting Controller with a container UIViewController, which has the added benefit of removing the dependency of the calling module on SwiftUI.
Why does UIViewRepresentable update once after the makeUIView and before the entire TLEUIView?
The update function may be called for a number of reasons. UIView is called at least once when it exists, and may be called many times before it is obsolete. So you can’t rely on the frequency of this update call.
Additional question: * * * * I implemented a UIViewDiffableRepresentable, it follows Hashable agreement will be effective after the update check each attribute, in order to prevent the trigger unnecessary overhead heavy logic updateUIView calls. Is this over-optimized? Is there a more sensible way?
** Answer: ** This is really over-optimized. The framework only calls updateUIView when properties outside the Representable structure actually change, and you can safely trust it with updates.
When creating UIViewRepresentable, is it dangerous for coordinators to hold UIViews passed in through updateUIView()? ⭐ ️
This is safe because your Coordinator will be created before any view is built — so ina makeUIView you can have a Coordinator hold a reference to the view.
Did in SwiftUI 2 can convert old AppDelegate/SceneDelegate life cycle method?
Yes, you can use in the App UIApplicationDelegateAdaptor attribute wrapper, such as UIApplicationDelegateAdaptor var myDelegate: MyAppDelegate.
SwiftUI will instantiate a UIApplicationDelegate for you and call its methods back and forth as an AppDelegate. In addition, you can return by configurationForConnectingSceneSession custom scene delegate, SwiftUI will be instantiated and according to the way of SceneDelegate callback method.
Backward compatibility
Can you tell us how existing SwiftUI code can inherit the features of the new version? I want to support both iOS 14 and iOS 15 with one set of code. 💬
Most new features cannot be released backwards to earlier versions of the system. You can check whether a feature is available by:
if #available(iOS 15.*) {
.
} else {
// Rollback policies for earlier versions
}
Copy the code
That said, there are some features that can be released backwards. For example, passing the bindings to collections directly to List and ForEach and then fetching the bindings ForEach element:
ForEach($elements) { $element in
.
}
Copy the code
This feature can be released backwards to earlier versions that support SwiftUI.
WWDC21 also mentions a feature that allows for backward publishing, which is enum-like style.
“Decrypt SwiftUI” mentions using @ViewBuilder to eliminate the use of AnyView. Is this only available on iOS 15? ♦ ️
No, in fact this method can be published backwards to any version that supports SwiftUI.
Programming strategy
Is there a scenario in SwiftUI where views can only be constructed using AnyView and no other alternative? ⭐ ️
There are quite a few questions about whether AnyView can be used. If you can avoid using AnyView, we recommend that you do so by, for example, using @ViewBuilder or generics to pass the view.
However, we offer AnyView because we understand that there are certain scenarios that can’t be solved any other way, or that AnyView works on balance.
Here’s a small rule: If the wrapped view changes little or nothing, AnyView is fine. However, using AnyView in scenarios that switch back and forth between different states can cause performance problems. This is because in order to manage the process, SwiftUI takes on extra loads.
Which is evaluated first, the body of the Child or the Parent? ⭐ ️
In the ‘Decrypt SwiftUI’ Dependency Graph section, the video mentions two views that rely on the same Dependency and need to generate a new body. If one of the views is a child of the other, which view’s body is evaluated first?
The parent view becomes the body, and then recursively iterates through all its child views.
If we do not want to use AnyView, how do we pass a View to ViewModifier? ⭐ ️ 💬
** I created a ViewModifier that adds custom mode overlays to the view. The effect is similar to that of sheet. Is there a way to pass a view to the ViewModifier as a constructor parameter without resorting to AnyView? I want to be able to pass the actual content of the overlay directly into the constructor.
** You can do this by implementing your own ViewModifier with a generic parameter, such as struct MyModifier
: ViewModifier {… }, and then declare a property like var content: C:
Here’s a complete example:
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}}}Copy the code
How to choose Group and ViewBuilder?
Group {if whatever {… } else {… }} and viewBuilder.buildbLock (pageInfo == nil? Ither (first: EmptyView()) : Ither (second: renderPage)). Both of these can be written to conditionally build views. Which is better?
** Group is better for this scenario.
** Additional question: ** Is this for readability? Or will one perform better than the other?
** Answer: ** Mainly for readability — usually we don’t recommend calling the result Builder implementation directly and let the compiler handle them.
Can we use.id() to keep views equivalent? ⭐ ️
** If we apply the same ID to views in a conditional build, does SwiftUI treat them as the same view?
var body: some View {
if isTrue {
Text("Hello")
.id(viewID)
} else {
Text("World")
.id(viewID)
}
}
Copy the code
** Reply: ** No, they will be two different views.
This is because the body is an implicit ViewBuilder. If you are not using a ViewBuilder, such as the above code in another common property, then they will be the same view. Or, you could write:
var body: some View {
Text(isTrue ? "Hello" : "World").id(viewID)
}
Copy the code
** Additional questions: ** Are there other examples of implicit ViewBuilder besides body?
Yes, such as ViewModifier body function, View Style makeBody, Preview providers, etc.
** Additional questions: ** So should we try to avoid conditionalization in View Builder?
** replies: ** of course not. Conditionalization exists for a reason, but overuse should be avoided.
Are there any scenarios where we should use Hashable in preference to Identifiable?
If you only need to identify a single value, then Identifiable is for this purpose, which means that only the ID attribute requires Hashable, not the entire type.
For styles, how do I implement conditionalization? ♦ ️
** How do we conditionally set different modifiers, such as list styles?
List {
.
}.listStyle(isIpad ? .sidebar : .insets)
Copy the code
The style in SwiftUI is static and cannot be changed at runtime. The branch statement in the above scenario would be more appropriate. However, you should first consider whether a style change is really necessary — a single style is usually the right choice.
If you’re looking for some kind of dynamic mechanism, give us feedback.
Is there a best practice for applying modifier to SwiftUI view conditional applications?
Is there a best practice for applying modifier to SwiftUI view conditionals? I myself implemented a. If Modifier that refreshes the entire view when the state changes 🙁
** can consider using an inert modifier, if any modifier lacks an inert version, please feedback to us.
Can I Group more than 10 Tablecolumns when using the new SwiftUI Table view? ♦ ️ ⭐ ️ 💬
Yes, of course you can do that, just like we did with the view!
There is no longer a limit on the number of objects in the @viewBuilder.
Answer: @ViewBuilder has not changed this year, the number of elements it can build is still limited. But groups and nested Builders can help you combine many views.
After testing, I found that the ForEach and control flow statements did not work with TableColumnBuilder. This is a shame, because I can see a lot of scenarios that require this. I have provided feedback on this issue: FB9189673 (ForEach) and FB9189678 (Control flow).
How do I get around AnyView when I’m using Core Data in UIHostingController? ⭐ ️ 💬
** When using UIHostingController and Core Data in SwiftUI, how can we avoid the problem of having to use AnyView because the Environment Modifier changes the view type? The code is as follows:
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(a) {
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}}Copy the code
** That’s a great question! There is a solution, but there will be too many square brackets… In the class MyHostingController: In UIHostingController, MyView is not actually our target type, All you need is MyView (). The environment (. ManagedObjectContext, persistentContainer. ViewContext) type, Its full form is ModifiedContent
(… Part is too long, omitted here). What I usually do is copy this type and declare a top-level typealias: typealias MyModifiedView = ModifiedContent
, where the type on the right is copied from the error message. So you can write the code as class MyHostingController: UIHostingController.
The solution recommended by the reply was previously working. However, since swifTC now reports some Views instead of specific types, this is no longer possible.
An alternative is to print the specific type first:
let rootView = MyView().environment(\.managedObjectContext, persistentContainer.viewContext) print("\ [type(of: rootView))") Copy the code
But that’s not enough. You have to cast the type, or it won’t compile:
rootView as! MyModifiedView Copy the code
The complete code is as follows:
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(a) { super.viewDidLoad() let persistentContainer = NSPersistentContainer(name: "MyStore") let rootView = MyView().environment(\.managedObjectContext, persistentContainer.viewContext) let hostingController = MyHostingController(rootView: rootView as! MyModifiedView) // more stuff}}Copy the code
If SE-309 were in effect and applied to views, would there be a substantial difference in View equivalence between a View and an AnyView? ♦ ️ ⭐ ️
Original question: The SE-309 enables us to incorporate an association type into an enumerated Type, assuming that it covers the View type. By then, is there any difference in View equivalence between an Existential View and an Existential View?
** I like this proposal! I can’t comment on the specifics of the implementation, but AnyView erases more information than An Interface, so An Interface will still be the boundary of differentiation.
Now that we have tasks, are there any other scenarios where we should use onAppear instead of Task? ♦ ️ ⭐ ️
Answer (Engineer #1) : onAppear() is still available. There is no need to update the previous code. I think task() provides a broader solution, even for short synchronous tasks, because it allows you to upgrade to asynchronous tasks when needed in the future.
** Additional questions: ** So, do you always use task() in new code, or does onAppear() still have its own place? I might think onAppear is sort of deprecated, right?
** I personally would always use Task (), but some people might like the symmetry of onAppear and onDisppear().
** Additional question: ** Is there a difference between the two?
Reply (Engineer #1) : Task () cancels asynchronous tasks when onDisappear and no new tasks are triggered.
** Reply (Engineer #2) : ** In general we avoid deprecating apis unless they are really harmful. As mentioned earlier, onAppear has more limitations than Task, so I would recommend using Task in the new code. But anyway, onAppear is harmless.
For more articles, please follow the public account “Swift Garden”