developer.apple.com/tutorials/S…
developer.apple.com/documentati…
Framework integration
UIKit integration
Displaying SwiftUI views in UIKit
Using SwiftUI with UIKit
// Stores the hosting controllers that each display a SwiftUI heart health alert view.
private var hostingControllers = [UIHostingController<HeartHealthAlertView>]()
// Creates a hosting controller for each heart health alert.
private func createHostingControllers() {
for alert in alerts {
let alertView = HeartHealthAlertView(alert: alert)
let hostingController = UIHostingController(rootView: alertView)
// Set the sizing options of the hosting controller so that it automatically updates the
// intrinsicContentSize of its view based on the ideal size of the SwiftUI content.
hostingController.sizingOptions = .intrinsicContentSize
hostingControllers.append(hostingController)
}
}
// Embeds each hosting controller's view in a stack view, and adds the stack view to this view controller's view.
private func setUpStackView() {
// Collect the view from each hosting controller, as well as the summary label.
var views: [UIView] = hostingControllers.map { $0.view }
views.append(summaryLabel)
// Add each hosting controller as a child view controller.
hostingControllers.forEach { addChild($0) }
// Create a stack view that contains all the views, and add it as a subview to the view
// of this view controller.
let spacing = 10.0
let stackView = UIStackView(arrangedSubviews: views)
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .equalSpacing
stackView.spacing = spacing
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
stackView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: spacing)
])
// Notify each hosting controller that it has now moved to a new parent view controller.
hostingControllers.forEach { $0.didMove(toParent: self) }
}
This sample code project is associated with WWDC22 session 10072: Use SwiftUI with UIKit.
present
add
UIHostingController
A UIKit view controller that manages a SwiftUI view hierarchy.
www.swift.org/getting-sta…
id()
The fade happens because SwiftUI sees the background color, icon, and text changing, so it removes the old views and replaces it with new views. Earlier I made you create an inner VStack
to house those three views, and now you can see why: we’re going to tell SwiftUI that these views can be identified as a single group, and that the group’s identifier can change over time.
To make that happen, we need to start by defining some more program state inside our view. This will be the identifier for our inner VStack
, and because it will change as our program runs we’ll use @State
. Add this property next to selected
:
@State private var id = 1
Tip: That’s more local view state, so it’s good practice to mark it with private
.
Next, we can tell SwiftUI to change that identifier every time our button is pressed, like this:
Button("Try again") {
withAnimation(.easeInOut(duration: 1)) {
selected = activities.randomElement() ?? "Archery"
id += 1
}
}
.buttonStyle(.borderedProminent)
Finally, we can use SwiftUI’s id()
modifier to attach that identifier to the whole inner VStack
, meaning that when the identifier changes SwiftUI should consider the whole VStack
as new. This will make it animate the old VStack
being removed and a new VStack
being added, rather than just the individual views inside it. Even better, we can control how that add and remove transition happens using a transition()
modifier, which has various built-in transitions we can use.
So, add these two modifiers to the inner VStack
, telling SwiftUI to identify the whole group using our id
property, and animate its add and removal transitions with a slide:
.transition(.slide)
.id(id)
Press Cmd+R to run your app one last time, and you should see that pressing “Try Again” now smoothly animates the old activity off the screen, and replaces it with a new one. It even overlaps animations if you press “Try Again” repeatedly!
其中id是什么意思
在 SwiftUI 中,id
修饰符(Modifier)用于标识视图的唯一性。在你提供的代码中,id
的作用非常重要,它与视图的过渡动画(transition)密切相关。
具体作用:
-
强制视图重新创建:
当id
的值改变时,SwiftUI 会认为这是一个全新的视图,而不是原有视图的更新。这会触发旧视图的移除和新视图的创建,从而确保过渡动画(.transition(.slide)
)生效。 -
控制视图的生命周期:
如果没有id
,SwiftUI 可能会复用原有的视图(仅更新内容),这样.transition
可能不会触发。通过改变id
,你明确告诉 SwiftUI:“这是一个不同的视图,请用动画过渡到新视图。”
代码中的具体应用:
@State private var id = 1 // 初始 id 值
// 在按钮点击时改变 selected 和 id
Button("Try again") {
withAnimation {
selected = activities.randomElement() ?? "Archery"
id += 1 // 每次点击增加 id,强制视图重新创建
}
}
// 视图使用 id 修饰符
VStack { ... }
.transition(.slide) // 指定过渡动画
.id(id) // 绑定 id 值
为什么需要 id
?
- 如果只是改变
selected
,SwiftUI 可能会直接更新Text
和Image
的内容,而不会触发.slide
过渡动画。 - 通过改变
id
,你强制 SwiftUI 将整个VStack
视为一个新视图,从而播放移除旧视图和插入新视图的动画。
总结:
id
在这里的作用是强制视图重新创建,以确保过渡动画正确运行。这是一种常见的 SwiftUI 动画技巧,尤其在需要完全替换视图时非常有用。