本文的翻译是专门为“ 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开发中发挥着巨大作用。 感谢您的关注!