SwiftUI中,包裝器詳細(xì)說(shuō)明
下面羅列17個(gè)包裝器的說(shuō)明。
@AppStorage 從 UserDefaults 讀取和寫(xiě)入值。
@Binding 引用另一個(gè)視圖擁有的值類型數(shù)據(jù)。在本地更改綁定也會(huì)更改遠(yuǎn)程數(shù)據(jù)。
@Environment 允許我們從系統(tǒng)讀取數(shù)據(jù),例如配色方案,可訪問(wèn)性選項(xiàng)和特征集,但是如果需要,您可以在此處添加自己的密鑰。
@EnvironmentObject 讀取我們放入環(huán)境中的共享對(duì)象。
@FetchRequest 為特定實(shí)體啟動(dòng)核心數(shù)據(jù)獲取請(qǐng)求。
@FocusedBinding 旨在監(jiān)視鍵窗口中的值,例如當(dāng)前選定的文本字段。
@FocusedValue 是 @FocusedBinding 的簡(jiǎn)單版本,不會(huì)為您解開(kāi)綁定值。
@GestureState 存儲(chǔ)與當(dāng)前正在進(jìn)行的手勢(shì)關(guān)聯(lián)的值,例如您滑動(dòng)了多遠(yuǎn),除非在手勢(shì)停止時(shí)它將重置為其默認(rèn)值。
@Namespace 創(chuàng)建一個(gè)動(dòng)畫(huà)名稱空間,以允許匹配的幾何效果,該效果可以由其他視圖共享。
@NSApplicationDelegateAdaptor 用于創(chuàng)建類并將其注冊(cè)為 macOS 應(yīng)用程序的應(yīng)用程序委托。
@ObservedObject 引用符合 ObservableObject 協(xié)議的外部類的實(shí)例。
@Published 附加到 ObservableObject 內(nèi)的屬性,并告訴 SwiftUI 它應(yīng)該在更改此屬性時(shí)刷新使用此屬性的所有視圖。
@ScaledMetric 會(huì)讀取用戶的動(dòng)態(tài)類型設(shè)置,并根據(jù)您提供的原始值向上或向下縮放數(shù)字。
@SceneStorage 使我們可以保存和還原少量數(shù)據(jù)以進(jìn)行狀態(tài)還原。
@State 允許我們?cè)谝晥D本地操作少量的值類型數(shù)據(jù)。
@StateObject 用于存儲(chǔ)符合 ObservableObject 協(xié)議的引用類型數(shù)據(jù)的新實(shí)例。
@UIApplicationDelegateAdaptor 用于創(chuàng)建一個(gè)類并將其注冊(cè)為iOS應(yīng)用程序的應(yīng)用程序委托。

@State、@Binding、@ObservedObject和@EnvironmentObject是幾個(gè)重要的屬性包裝器,它們各自在數(shù)據(jù)管理和視圖更新中扮演著不同的角色。有的配合使用,下面給出幾個(gè)例子介紹下用法
State
@State用于在視圖中聲明可變的狀態(tài)屬性,并自動(dòng)更新視圖。當(dāng)@State修飾的屬性值發(fā)生變化時(shí),SwiftUI會(huì)自動(dòng)重新渲染視圖以反映這些變化。
在這個(gè)例子中,count是一個(gè)@State屬性,它控制著一個(gè)計(jì)數(shù)器的值。當(dāng)按鈕被點(diǎn)擊時(shí),count的值會(huì)增加,并且由于count是@State屬性,SwiftUI會(huì)自動(dòng)更新視圖以顯示新的計(jì)數(shù)值。
struct ContentView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, SwiftUI \(count)")
Button(action: {
count += 1
}, label: {
Text("Increment")
})
}
.padding()
}
}
Binding
@Binding用于在不同視圖之間傳遞和共享可讀寫(xiě)的值。它創(chuàng)建了一個(gè)對(duì)屬性的引用,以便多個(gè)視圖可以共享同一份數(shù)據(jù),并且對(duì)數(shù)據(jù)的更改會(huì)在所有引用的地方生效。
在這個(gè)例子中,ParentView有一個(gè)@State屬性isChildViewVisible,它控制ChildView的可見(jiàn)性。通過(guò)@Binding,ChildView能夠訪問(wèn)并修改isChildViewVisible的值,從而控制自己的顯示狀態(tài)。
struct ParentView: View {
@State private var isChildViewVisible = false
var body: some View {
VStack {
Toggle(isOn: $isChildViewVisible) {
Text("Show Child View")
}
if isChildViewVisible {
ChildView(isVisible: $isChildViewVisible)
}
}
}
}
struct ChildView: View {
@Binding var isVisible: Bool
var body: some View {
Text("Child View")
.padding()
Button(action: {
isVisible = false
}) {
Text("Hide")
}
}
}
StateObject
@StateObject
@StateObject用于在SwiftUI視圖中創(chuàng)建并管理一個(gè)可觀察的對(duì)象(通常是實(shí)現(xiàn)了ObservableObject協(xié)議的類)。這個(gè)對(duì)象會(huì)在視圖的生命周期內(nèi)保持唯一,即使視圖被重新渲染。
在這個(gè)例子中,ViewModel是一個(gè)實(shí)現(xiàn)了ObservableObject協(xié)議的類,它有一個(gè)@Published屬性text。ContentView使用@StateObject來(lái)創(chuàng)建并管理ViewModel的實(shí)例。當(dāng)按鈕被點(diǎn)擊時(shí),text屬性的值會(huì)更新,并且由于ViewModel是可觀察的,ContentView會(huì)自動(dòng)重新渲染以顯示新的文本。
class ViewModel: ObservableObject {
@Published var text = "Hello, World!"
}
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
VStack {
Text(viewModel.text)
Button(action: {
viewModel.text = "Updated Text!"
}) {
Text("Update Text")
}
}
}
}
ObservedObject
@ObservedObject用于觀察引用類型對(duì)象的變化,并在對(duì)象更改時(shí)更新視圖。它通常用于管理外部對(duì)象的狀態(tài),并在該對(duì)象發(fā)生變化時(shí)自動(dòng)刷新視圖。
在這個(gè)例子中,UserData是一個(gè)遵循ObservableObject協(xié)議的類,它有一個(gè)@Published屬性name。ContentView使用@ObservedObject來(lái)觀察UserData對(duì)象,當(dāng)name屬性發(fā)生變化時(shí),ContentView會(huì)自動(dòng)更新以顯示新的名稱。
//必須要集成 ObservableObject
class UserData: ObservableObject {
//需要監(jiān)聽(tīng)的屬性需要 Published
@Published var name: String = ""
}
struct UserDataContentView: View {
@ObservedObject var user = UserData()
var body: some View {
VStack {
//user.name 綁定到TextField上
TextField("Enter your name", text: $user.name)
.textFieldStyle(.roundedBorder)
Text("Hello, \(user.name)!")
}
}
}
AppStorage
@AppStorage 是 SwiftUI 中的一個(gè)屬性包裝器,它允許開(kāi)發(fā)者將屬性的值存儲(chǔ)在 UserDefaults 中,并在應(yīng)用程序的不同啟動(dòng)之間保留這些值。這對(duì)于存儲(chǔ)用戶偏好設(shè)置等非常有用。
聲明和用法
要使用 @AppStorage,你需要將其附加到你的屬性上,并指定一個(gè)鍵(key)和一個(gè)默認(rèn)值。例如:
@AppStorage("user_name") var username: String = "Guest"
在這個(gè)例子中,username 屬性使用 @AppStorage 進(jìn)行存儲(chǔ)和檢索,它的鍵為 "user_name",默認(rèn)值為 "Guest"。如果 UserDefaults 中已經(jīng)存在 "user_name" 這個(gè)鍵對(duì)應(yīng)的值,那么 username 將被初始化為那個(gè)值;如果不存在,則使用默認(rèn)值 "Guest"。
struct ContentView: View {
@AppStorage("user_name") var username: String = "Guest"
var body: some View {
VStack {
Text("Hello, \(username)!")
Button("Change Username") {
username = "NewUser"
}
}
}
}
在這個(gè)示例中,username 屬性使用 @AppStorage 進(jìn)行存儲(chǔ)和檢索。當(dāng)用戶點(diǎn)擊 "Change Username" 按鈕時(shí),username 的值會(huì)更改為 "NewUser",并且在應(yīng)用程序的不同啟動(dòng)之間保留這個(gè)更改。
注意事項(xiàng)
- 數(shù)據(jù)類型匹配:存儲(chǔ)的數(shù)據(jù)類型必須與屬性的聲明類型匹配。@AppStorage 支持基本數(shù)據(jù)類型(如 String、Int、Bool 等),但不能直接用于自定義類或結(jié)構(gòu)體。
- 默認(rèn)值:如果不設(shè)置默認(rèn)值,則變量的類型為可選值類型(如 String?)。
- UserDefaults:@AppStorage 底層使用 UserDefaults 來(lái)存儲(chǔ)數(shù)據(jù)。因此,對(duì) UserDefaults 的操作將直接影響對(duì)應(yīng)的 @AppStorage。
- 數(shù)據(jù)安全性:由于 UserDefaults 的數(shù)據(jù)相對(duì)容易提取,因此不要在其中保存與隱私有關(guān)的重要數(shù)據(jù)。
- 數(shù)據(jù)同步:為了效率的考量,UserDefaults 中的數(shù)據(jù)在發(fā)生變化時(shí)并不會(huì)立即持久化,系統(tǒng)會(huì)在認(rèn)為合適的時(shí)機(jī)才將數(shù)據(jù)保存在硬盤(pán)中。因此,可能發(fā)生數(shù)據(jù)不能完全同步的情況,嚴(yán)重時(shí)有數(shù)據(jù)徹底丟失的可能。
EnvironmentObject
@EnvironmentObject用于在應(yīng)用程序中共享和訪問(wèn)全局?jǐn)?shù)據(jù)。它允許你在應(yīng)用程序的任何視圖中訪問(wèn)和使用共享數(shù)據(jù),而無(wú)需手動(dòng)傳遞它們。
在這個(gè)例子中,UserData是一個(gè)全局的ObservableObject,它存儲(chǔ)了用戶的信息。ContentView使用@EnvironmentObject來(lái)訪問(wèn)UserData對(duì)象,并在視圖中顯示用戶名稱。在MyApp中,userData對(duì)象被創(chuàng)建并通過(guò).environmentObject方法注入到ContentView中,使得ContentView能夠訪問(wèn)到全局的用戶數(shù)據(jù)。
class UserData: ObservableObject {
@Published var name: String = "John"
}
struct ContentView: View {
@StateObject var userData = UserData()
var body: some View {
VStack {
UserDataContentView().environmentObject(userData)
}
.padding()
}
}
struct UserDataContentView: View {
@EnvironmentObject var userData: UserData
var body: some View {
VStack {
Text("Hello, \(userData.name)!")
}
}
}
FocusState
@FocusState用于管理視圖中的焦點(diǎn)狀態(tài)。在需要鍵盤(pán)輸入或特定交互的視圖中,@FocusState可以幫助開(kāi)發(fā)者確定哪個(gè)視圖當(dāng)前具有焦點(diǎn),并相應(yīng)地調(diào)整UI。
由于SwiftUI的TextField默認(rèn)不支持直接的焦點(diǎn)API,上述示例中的.focused($isFocused)是假設(shè)性的,用于說(shuō)明@FocusState的概念。在實(shí)際應(yīng)用中,你可能需要借助UIViewRepresentable或其他技術(shù)來(lái)實(shí)現(xiàn)焦點(diǎn)管理。
struct ContentView: View {
@FocusState var isFocused: Bool
var body: some View {
VStack {
TextField("Type something...", text: .constant(""))
.focused($isFocused)
Button(action: {
isFocused = !isFocused
}) {
Text(isFocused ? "Lose Focus" : "Gain Focus")
}
}
}
}
// 注意:由于TextField在SwiftUI中默認(rèn)不支持直接的focus API,這里使用了偽代碼來(lái)展示概念。
// 在實(shí)際應(yīng)用中,你可能需要使用自定義的UIViewRepresentable來(lái)包裝UIKit的UITextField或使用其他方法來(lái)實(shí)現(xiàn)焦點(diǎn)管理。
Environment
@Environment 屬性包裝器
雖然@Environment屬性包裝器在SwiftUI中不如@EnvironmentObject常見(jiàn),但它用于訪問(wèn)系統(tǒng)級(jí)的環(huán)境值,如布局方向、顏色方案等。這些值由SwiftUI框架自動(dòng)管理,并在需要時(shí)傳遞給視圖。
@Environment是獲取系統(tǒng)環(huán)境變量的。例如isEnabled,editMode,presentationMode,horizontalSizeClass,verticalSizeClass等。
示例(假設(shè)性代碼,因?yàn)锧Environment通常不直接用于訪問(wèn)這些值,而是使用其他方式,如@Environment(.keyPath)):
struct MyView: View {
// 假設(shè)的代碼,實(shí)際中不會(huì)這樣使用@Environment直接訪問(wèn)系統(tǒng)值
// @Environment var layoutDirection: LayoutDirection // 這不是有效的用法
// 正確的用法是使用@Environment和鍵路徑
@Environment(\.layoutDirection) var layoutDirection: LayoutDirection
var body: some View {
// 根據(jù)布局方向調(diào)整UI
if layoutDirection == .leftToRight {
// ...
} else {
// ...
}
}
}
請(qǐng)注意,上面的@Environment var layoutDirection: LayoutDirection示例是假設(shè)性的,因?yàn)锧Environment通常與鍵路徑一起使用(如@Environment(.layoutDirection)),而不是直接聲明變量類型。在SwiftUI中,你更可能會(huì)看到像@Environment(.layoutDirection) var layoutDirection: LayoutDirection這樣的用法。
總的來(lái)說(shuō),@EnvironmentObject是SwiftUI中用于在視圖之間共享可觀察對(duì)象的強(qiáng)大工具,而@Environment(與鍵路徑一起使用時(shí))則用于訪問(wèn)系統(tǒng)級(jí)的環(huán)境值。