本文的翻译是专门为“ iOS Developer”课程的学生准备的。 高级课程v 2.0。”
上周,我们开始了有关SwiftUI框架的一系列新
文章 。 今天,我想通过谈论SwiftUI中的
Property Wrappers来继续这个话题。 SwiftUI为我们提供了
@State
,
@Binding
@State
,
@Binding
@State
,
@Binding
@EnvironmentObject
和
@Environment
包装器。 因此,让我们尝试了解它们之间的区别以及何时,为什么以及应该使用哪一个。
物业包装
SE-0258中描述了属性包装器(以下
称为 “属性包装器”)。 主要思想是用逻辑包装属性,这些逻辑可以提取到单独的结构中以在代码库中重用。
@State
是一个包装器,我们可以用来指示
View
的状态。 SwiftUI会将其存储在
View
结构外部的特殊内部存储器中。 只有链接的
View
可以访问它。 随着
@State
属性值的更改,SwiftUI会重建
View
以说明状态更改。 这是一个简单的例子。
struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { Button( action: { self.showFavorited.toggle() }, label: { Text("Change filter") } ) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } }
在上面的示例中,我们有一个带有按钮和产品列表的简单屏幕。 只要单击按钮,它就会更改state属性的值,并且SwiftUI会重建
View
。
@绑定
@Binding
提供对值类型的引用访问。 有时,我们需要使他的孩子可以访问我们的
View
状态。 但是我们不能只接受并传递该值,因为它是一个值类型,而Swift将传递此值的副本。 这就是
@Binding
属性的包装。
struct FilterView: View { @Binding var showFavorited: Bool var body: some View { Toggle(isOn: $showFavorited) { Text("Change filter") } } } struct ProductsView: View { let products: [Product] @State private var showFavorited: Bool = false var body: some View { List { FilterView(showFavorited: $showFavorited) ForEach(products) { product in if !self.showFavorited || product.isFavorited { Text(product.title) } } } } }
我们使用
@Binding
标记
showFavorited
内的
FilterView
属性。 我们还使用特殊的
$
字符传递锚链接,因为如果没有
$
Swift,它将传递值的副本,而不是传递锚链接本身。
FilterView
可以
FilterView
中读取和写入
showFavorited
属性的值,但不能使用此绑定跟踪更改。 一旦
FilterView
更改了
showFavorited
属性的值,SwiftUI就会重新创建
ProductsView
和
FilterView
作为其子级。
@ObservedObject
@ObservedObject
工作方式与
@State
类似,但主要区别是我们可以将其拆分为几个独立的
View
,它们可以订阅并观看此对象的更改,并且一旦更改出现,
SwiftUI
重建与该对象关联的所有视图。 。 让我们来看一个例子。
import Combine final class PodcastPlayer: ObservableObject { @Published private(set) var isPlaying: Bool = false func play() { isPlaying = true } func pause() { isPlaying = false } }
这里有
PodcastPlayer
类,我们的应用程序的屏幕在它们之间共享。 当应用程序播放播客片段时,每个屏幕都应显示一个浮动的暂停按钮。
SwiftUI
使用
@Published
包装器跟踪对
ObservableObject
的
@Published
,并且在将属性标记为
@Published
更改后,
SwiftUI
重建与此
PodcastPlayer
关联的所有
PodcastPlayer
。 在这里,我们使用
@ObservedObject
包装器将
EpisodesView
绑定到
PodcastPlayer
类
struct EpisodesView: View { @ObservedObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } } }
@EnvironmentObject
与其将
ObservableObject
通过
View
的init方法传递,还可以将其隐式嵌入到
View
层次结构的
Environment
。 这样,我们使当前
Environment
所有子视图都可以访问此
ObservableObject
。
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let window = UIWindow(frame: UIScreen.main.bounds) let episodes = [ Episode(id: 1, title: "First episode"), Episode(id: 2, title: "Second episode") ] let player = PodcastPlayer() window.rootViewController = UIHostingController( rootView: EpisodesView(episodes: episodes) .environmentObject(player) ) self.window = window window.makeKeyAndVisible() } } struct EpisodesView: View { @EnvironmentObject var player: PodcastPlayer let episodes: [Episode] var body: some View { List { Button( action: { if self.player.isPlaying { self.player.pause() } else { self.player.play() } }, label: { Text(player.isPlaying ? "Pause": "Play") } ) ForEach(episodes) { episode in Text(episode.title) } } } }
如您所见,我们必须将
PodcastPlayer
通过
View
的
environmentObject
修饰符。 这样,我们可以使用
@EnvironmentObject
包装器
PodcastPlayer
进行定义,从而轻松访问
@EnvironmentObject
。
@EnvironmentObject
使用动态成员搜索功能在
Environment
查找
PodcastPlayer
类的实例,因此您无需通过
EpisodesView
init方法将其传递。 环境是
将依赖项注入
SwiftUI的正确方法。
@环境
如上一章所述,我们可以将自定义对象转移到
SwiftUI内的“
Environment
View
层次结构中。 但是
SwiftUI已经有一个包含系统范围设置的环境。 我们可以使用
@Environment
包装器轻松访问它们。
struct CalendarView: View { @Environment(\.calendar) var calendar: Calendar @Environment(\.locale) var locale: Locale @Environment(\.colorScheme) var colorScheme: ColorScheme var body: some View { return Text(locale.identifier) } }
通过使用
@Environment
包装器标记属性,我们可以访问并订阅对系统范围设置的更改。 一旦
Locale ,
Calendar或
ColorScheme系统发生更改,
SwiftUI就会重新创建我们的
CalendarView
。
结论
今天我们讨论了
SwiftUI提供的Property Wrappers。
@State
,
@Binding
,
@EnvironmentObject
和
@ObservedObject
在
SwiftUI开发中发挥着巨大作用。 感谢您的关注!