WWDC 23 之后的 SwiftUI 有哪些新功能
前言
WWDC 23 已經(jīng)到來,SwiftUI 框架中有很多改變和新增的功能。在本文中將主要介紹 SwiftUI 中數(shù)據(jù)流、動畫、ScrollView、搜索、新手勢等功能的新變化。
數(shù)據(jù)流
Swift 5.9 引入了宏功能,成為 SwiftUI 數(shù)據(jù)流的核心。SwiftUI 不再使用 Combine,而是使用新的 Observation 框架。Observation 框架為我們提供了 Observable 協(xié)議,必須使用它來允許 SwiftUI 訂閱更改并更新視圖。
@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 協(xié)議。相反,可以使用 @Observable 宏來標(biāo)記你的類型,它會自動為符合 Observable 協(xié)議。也不再需要 @Published 屬性包裝器,因為 SwiftUI 視圖會自動跟蹤任何可觀察類型的可用屬性的更改。
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()
}
}
}
}
以前,有一系列的屬性包裝器,如 State、StateObject、ObservedObject 和 EnvironmentObject,你應(yīng)該了解何時以及為何使用它們。
現(xiàn)在,狀態(tài)管理變得更加簡單。對于值類型(如字符串和整數(shù))和符合 Observable 協(xié)議的引用類型,只需使用 State 屬性包裝器。
struct FavoriteProductsView: View {
let store: Store
var body: some View {
List(store.favorites, id: \.self) { product in
Text(verbatim: product)
}
}
}
在上面的示例中,有一個接受 Store 類型的視圖。在之前的 SwiftUI 框架版本中,應(yīng)該使用 @ObservedObject 屬性包裝器來訂閱更改?,F(xiàn)在不需要了,因為 SwiftUI 視圖會自動跟蹤符合 Observable 協(xié)議的類型的更改。
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)
}
}
還可以使用 Environment 屬性包裝器與 environment 視圖修飾符配對,將可觀察類型放入 SwiftUI 環(huán)境中。不需要使用 @EnvironmentObject 屬性包裝器或 environmentObject 視圖修飾符。同樣的 Environment 屬性包裝器現(xiàn)在適用于可觀察類型。
struct BindanbleViewExample: View {
@Bindable var store: Store
var body: some View {
List($store.products, id: \.self) { $product in
TextField(text: $product) {
Text(verbatim: product)
}
}
}
}
每當(dāng)需要從可觀察類型中提取綁定時,可以使用新的 Bindable 屬性包裝器。
動畫
動畫始終是 SwiftUI 框架中最重要的部分。在 SwiftUI 中輕松實現(xiàn)任何動畫,但之前的框架版本缺少一些現(xiàn)在具有的功能。
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")
}
}
}
}
如上例所示,我們有了新版本的 withAnimation 函數(shù),允許提供動畫完成處理程序。這是一個很好的補(bǔ)充,現(xiàn)在您可以構(gòu)建階段性動畫。
enum Phase: CaseIterable {
case start
case loading
case finish
var offset: CGFloat {
// Calculate offset for the particular phase
switch self {
case start: 100.0
case loading: 0.0
case finish: 50.0
}
}
}
struct PhasedAnimationExample: View {
@State private var value = false
var body: some View {
PhaseAnimator(Phase.allCases, trigger: value) { phase in
LoadingView()
.offset(x: phase.offset)
} animation: { phase in
switch phase {
case .start: .easeIn(duration: 0.3)
case .loading: .easeInOut(duration: 0.5)
case .finish: .easeOut(duration: 0.1)
}
}
}
}
SwiftUI 框架引入了新的 PhaseAnimator 視圖,它遍歷階段序列,允許為每個階段提供不同的動畫,并在階段更改時更新內(nèi)容。還有 KeyframeAnimator 視圖,可以使用關(guān)鍵幀來實現(xiàn)動畫。
ScrollView
今年 ScrollView 有了很多優(yōu)秀的新增功能。首先,可以使用 scrollPosition 視圖修飾符來觀察內(nèi)容偏移量。
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)
}
}
如上例所示,使用 scrollPosition 視圖修飾符將內(nèi)容偏移量綁定到一個狀態(tài)屬性上。每當(dāng)用戶滾動視圖時,它會通過設(shè)置第一個可見視圖的標(biāo)識來更新綁定。還可以通過編程方式滾動到任何視圖,但是,應(yīng)該使用 scrollTargetLayout 視圖修飾符來告訴 SwiftUI 框架在哪里查找標(biāo)識以更新綁定。
struct ContentView: View {
var body: some View {
ScrollView {
ForEach(1..<100, id: \.self) { number in
Text(verbatim: number.formatted())
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
}
}
可以通過使用 scrollTargetBehavior 視圖修飾符來更改滾動行為。它允許在滾動視圖中啟用分頁。
搜索
與搜索相關(guān)的視圖修飾符也有一些很好的新增功能。例如,可以通過編程方式聚焦到搜索字段。
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)
}
}
}
如上例所示,可以使用可搜索視圖修飾符的 isPresented 參數(shù)來顯示/隱藏搜索字段。還可以使用 searchScopes 視圖修飾符的 activation 參數(shù)來定義范圍的可見性邏輯。
新手勢
新增的 RotateGesture 和 MagnifyGesture 使我們能夠跟蹤視圖的旋轉(zhuǎn)和放大。
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 類型,當(dāng)需要顯示空視圖時可以使用它。示例如下:
struct ProductsView: View {
@State private var store = Store()
var body: some View {
List(store.products, id: \.self) { product in
Text(verbatim: product)
}
.background {
if store.products.isEmpty {
ContentUnavailableView("Products list is empty", systemImage: "list.dash")
}
}
.task {
if store.products.isEmpty {
await store.fetch()
}
}
}
}
還有新增了新的視圖修飾符,允許調(diào)整列表中的間距??梢允褂?nbsp;listRowSpacing 和 listSectionSpacing 視圖修飾符來設(shè)置列表中所需的間距。EnvironmentValues 結(jié)構(gòu)體包含了一系列與最新平臺更新相關(guān)的新屬性,例如 isActivityFullscreen 和 showsWidgetContainerBackground。Swift Charts 也具有可滾動和可動畫的功能。
#Preview {
ContentView()
}
還有一個新的 Preview 宏,可以讓我們輕松地為 UIKit 和 SwiftUI 構(gòu)建預(yù)覽,只需幾行代碼。
總結(jié)
SwiftUI 框架中有許多小的新增功能,我們將會繼續(xù)分享。希望能幫到你。