这是一篇来自 Majid 的文章的翻译,并且添加了部分内容,便于了解。
本文介绍了现在已知的部分 SwiftUI 新特性。因为今天是 WWDC 的第一天,现在官方只放出了 Platforms State of the Union 这一支技术视频,因而本文只掩盖到了视频内提到的所有新特性。后续新特性会在之后几天中连续介绍(假如有时间的话)。
本文中呈现的新 API 需求调配 Xcode 15 beta 运用,并且部分 API 还没有实装。
WWDC 23按期而至,许多内容也随之更新并被添加到 SwiftUI 结构中。阅览本文,你能够了解到 SwiftUI 结构第5次迭代中新增的几个最重要的特性。
数据流
与 UIKit 不同,SwiftUI 运用新的调查结构(Observation framework)作为其数据流。调查结构供给了一个 Observable
协议,咱们经过它来订阅更改和更新 SwiftUI 视图。不过之前 SwiftUI 的数据流 API 比较杂乱,需求了解的细节许多,上手本钱颇高。
不过跟着 Swift 5.9 引入了宏特性,Observation framework 运用宏优化了许多 API ,精简和躲藏了部分逻辑。
@Observable
现在不需求让类(ViewModel)遵循 Observable
协议,直接运用 @Observer
宏符号就行,这个宏会主动让该类遵循 Observable
协议。现在也不需求给特点前加 @Published
,因为 SwiftUI 视图会主动盯梢任何可调查类型的可用特点的更改。
// before
final class Store: ObservableObject {
@Published var products: [String] = []
@Published var favorites: [String] = []
func fetch() async {
try? await Task.sleep(nanoseconds: 1_000_000_000)
// load products
products = [
"Product 1",
"Product 2"
]
}
}
// now
@Observable
final class Store {
var products: [String] = []
var favorites: [String] = []
func fetch() async {
try? await Task.sleep(nanoseconds: 1_000_000_000)
// load products
products = [
"Product 1",
"Product 2"
]
}
}
此处开发人员还提到:
Observable lets SwiftUI track access at a per-field level, so your view’s body is only re-evaluated when the specific properties used by your view change. If you modify a field not used by your view, no invalidation will happen at all.
新的监听方式会让 UI 刷新的范围限定在更改正的特点上,因而不会呈现之前一个 ViewModel 中的某一个特点更改,所有引证到 ViewModel 的 View 都会刷新的问题,算是大大提升了优化功率。
@State
此前咱们有许多特点包装器,比如 State
、 StateObject
、 ObservedObject
和 EnvironmentObject
,开发者需求清晰在什么时分运用什么类型的包装器。现在,状况管理变得更简略了。关于值类型(String
、Int
等)以及契合 Observable
协议的引证类型(如 ViewModel ),无脑运用 @State
符号就行了。
struct ProductsView: View {
// @State private var text = ""
// @StateObject private var store = Store()
let store = Store()
var body: some View {
List(store.products, id: .self) { product in
Text(verbatim: product)
}
.task {
if store.products.isEmpty {
await store.fetch()
}
}
}
}
因为 @State
完全抹平了 State
、 StateObject
、 ObservedObject
和 EnvironmentObject
的差异,因而 @State private var store = Store()
这段代码定义的 store
不仅仅是一个 StateObject
,也是 EnvironmentObject
。简略来讲,之前 Apple 把杂乱的内部实现露出出来的,可是从新版 SwiftUI 开端,你不需求再了解这些细节,只需求了解一种状况类型 State
即可。既然如此,咱们能够玩一个小技巧。下面的代码, store
不仅仅是 ProductsView
内部的状况类,也被 .environment(store)
直接变为了 EnvironmentObject
注入到了所有子视图了。EnvironmentViewExample
就能够经过 Environment API 拿到 store
实例。
struct EnvironmentViewExample: View {
@Environment(Store.self) private var store
var body: some View {
Button("Fetch") {
Task {
await store.fetch()
}
}
}
}
struct ProductsView: View {
@State private var store = Store()
var body: some View {
List(store.products, id: .self) { product in
Text(verbatim: product)
}
.task {
if store.products.isEmpty {
await store.fetch()
}
}
.toolbar {
NavigationLink {
EnvironmentViewExample()
} label: {
Text(verbatim: "Environment")
}
}
.environment(store)
}
}
@ObservedObject
这个比如中,咱们有一个视图,它有一个从外部传进来的参数 store
。在此之前,咱们需求运用 @ObservedObject
特点包装器来符号它,以订阅它的更改。现在不需求了,因为 SwiftUI 视图会主动盯梢契合 Observable
协议的类型的更改。
struct FavoriteProductsView: View {
// @ObservedObject let store: Store
let store: Store
var body: some View {
List(store.favorites, id: .self) { product in
Text(verbatim: product)
}
}
}
@Bindable
struct BindanbleViewExample: View {
@Bindable var store: Store
var body: some View {
List($store.products, id: .self) { $product in
TextField(text: $product) {
Text(verbatim: product)
}
}
}
}
关于双向绑定(Binding),之前只能将基础类型包装成 Binding 传递给子视图,现在新增了一个 @Bindable
,能够用来符号引证类型,这样就能直接把 ViewModel 传递给子视图了。
动画
动画是 SwiftUI 结构中最重要的部分,在 SwiftUI 中给任何东西添加动画都是垂手可得的。此次更新为 SwiftUI 动画添加了一些新特性。
withAnimation completion
正如比如中看到的,withAnimation API 新增了 completion 回调,便利咱们对动画进行操控。
struct AnimationExample: View {
@State private var value = false
var body: some View {
Text(verbatim: "Hello")
.scaleEffect(value ? 2 : 1)
.onTapGesture {
withAnimation {
value.toggle()
} completion: {
print("Animation have finished")
}
}
}
}
PhaseAnimator
SwiftUI 结构引入了新的 PhaseAnimator 类,答应为每个阶段供给不同的动画,并在阶段更改时更新内容。这个其实属于一个工具类,便利咱们做一些拥有不同状况或阶段的动画。没有这个类也能做,便是稍微麻烦点。
enum Phase: CaseIterable {
case start
case loading
case finish
}
struct PhasedAnimationExample: View {
@State private var value = false
var body: some View {
PhaseAnimator(Phase.allCases, trigger: value) { phase in
switch phase {
case .start:
StartPhaseView()
.onTapGesture {
value.toggle()
}
case .loading:
LoadingPhaseView()
case .finish:
FinishPhaseView()
}
} animation: { phase in
switch phase {
case .start: .easeIn(duration: 0.3)
case .loading: .easeInOut(duration: 0.5)
case .finish: .easeOut(duration: 0.1)
}
}
}
}
KeyframeAnimator
本年 SwiftUI 也带来了关键帧动画,应该能在动画自由度上再上一层楼。可是现在没有看到相关 API,官方演示的 demo 是 MapKit 中的运用案例。
ScrollView
ScrollView 本年有很好的补充。首先,咱们能够运用 scrollPosition
视图修饰符来操控和调查内容偏移。
struct ContentView: View {
@State private var scrollPosition: Int? = 0
var body: some View {
ScrollView {
Button("Scroll") {
scrollPosition = 80
}
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
}
.scrollTargetLayout()
}
.scrollPosition(id: $scrollPosition)
}
}
scrollTargetBehavior
新增 scrollTargetBehavior
, 支撑分页模式。
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(1..<100, id: .self) { number in
Text(verbatim: number.formatted())
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
}
}
查找
现在能够运用 searchable
视图修饰符的 isPresent
参数来显现/躲藏查找字段。还能够运用 searchScope 来限定查找范围。
struct ProductsView: View {
@State private var store = Store()
@State private var query = ""
@State private var scope: Scope = .default
var body: some View {
List(store.products, id: .self) { product in
Text(verbatim: product)
}
.task {
if store.products.isEmpty {
await store.fetch()
}
}
.searchable(text: $query, isPresented: .constant(true), prompt: "Query")
.searchScopes($scope, activation: .onTextEntry) {
Text(verbatim: scope.rawValue)
}
}
}
手势
新增 RotateGesture
和 MagnifyGesture
, 答应咱们盯梢视图的旋转和放大。
struct RotateGestureView: View {
@State private var angle = Angle(degrees: 0.0)
var rotation: some Gesture {
RotateGesture()
.onChanged { value in
angle = value.rotation
}
}
var body: some View {
Rectangle()
.frame(width: 200, height: 200, alignment: .center)
.rotationEffect(angle)
.gesture(rotation)
}
}
其他改善
ContentUnavailableView
新增 ContentUnailableView
视图,能够在需求的时分显现空视图,展现一个图片和一行文字。算是一个很小的改善吧。
struct ProductsView: View {
ContentUnavailableView("Products list is empty", systemImage: "list.dash")
}
#Preview
新增 #Preview
宏,替代原来杂乱的 preview 代码。不过 preview 模版代码基本上都是新建 View 的时分模板主动生成的,影响不大。
// before
struct PlayerView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// now
#Preview {
ContentView()
}
其他
新增一对新的视图修饰符,答应咱们调整列表中的距离。您能够运用 listRowSpaces
和 listSectionSpaces
视图修饰符来设置列表中所需的距离。
EnvironmentValues
结构包括一系列与最新渠道更新相关的新特点,比如 isActivityFullscreen 和 showsWidgetContainerBack。
Swift Charts 支撑滚动。
SF Symbols 添加动画作用。
总结
每一年的 SwiftUI 迭代都会让我感觉,这些新特性分明应该早就有才对。像是 @State
这样的能力,假如一开端就有,会大大下降 SwiftUI 学习的门槛。不过这次 API 上的改动许多都依赖于 Swift 宏,而宏直到 4.9 才正式发布,也算是能够了解吧。不过依照这样的节奏下去,不知道何年马月才能真实大面积用上 SwiftUI 。听说本年 Platforms State of the Union
上一共就提了 3 次 UIKit,其间一次还是讲的 #Preview
支撑预览 UIKit 。看来咱们将会在很长一段时间内卡在两代技术中间。或许,投入另一个新技术:realityOS?