了解SwiftUI中的属性包装器

本文的翻译是专门为“ 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就会重新创建ProductsViewFilterView作为其子级。

@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通过ViewenvironmentObject修饰符。 这样,我们可以使用@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包装器标记属性,我们可以访问并订阅对系统范围设置的更改。 一旦LocaleCalendarColorScheme系统发生更改, SwiftUI就会重新创建我们的CalendarView

结论


今天我们讨论了SwiftUI提供的Property Wrappers。 @State@Binding@EnvironmentObject@ObservedObjectSwiftUI开发中发挥着巨大作用。 感谢您的关注!

Source: https://habr.com/ru/post/zh-CN475078/


All Articles