SwiftUI-Model data

管理您的应用程序用于驱动其界面的数据。

SwiftUI为用户界面设计提供了一种声明性方法。当您编写视图层次结构时,您还会指示视图的数据依赖性。当数据因外部事件或用户执行的操作而发生变化时,SwiftUI会自动更新界面的受影响部分。因此,该框架会自动执行视图控制器传统上所做的大部分工作。

image.png

该框架提供了工具,如状态变量和绑定,用于将应用程序的数据连接到用户界面。这些工具可帮助您为应用程序中的每条数据维护单一的真相来源,部分原因是减少了您编写的胶水逻辑量。选择最适合您需要执行的任务的工具:

  • 通过将值类型包装为State属性,在视图中本地管理瞬态UI状态。
    *使用ObservedObject属性包装器连接到符合ObservableObject协议的外部引用模型数据。使用EnvironmentObject属性包装器访问环境中的可观察对象。使用StateObject在视图中直接实例化可观察对象。
  • 使用Binding属性包装器共享对真理源的引用,如状态或可观察对象。

利用财产包装

SwiftUI实现许多数据管理类型,如State和Binding,作为Swift属性包装器。通过将带有包装器名称的属性添加到属性的声明中来应用属性包装器。

@State private var isVisible = true // Declares isVisible as a state variable.

该属性获得包装器指定的行为。SwiftUI中的状态和数据流属性包装器监视数据的更改,并根据需要自动更新受影响的视图。当您直接引用代码中的属性时,您将访问包装值,对于上述示例中的is状态属性,该属性是存储的布尔值。

if isVisible == true {
    Text("Hello") // Only renders when isVisible is true.
}

或者,您可以通过在属性名称前加上美元符号($)来访问属性包装符的预计值。SwiftUI状态和数据流属性包装器投射Binding,这是与包装值的双向连接,允许另一个视图访问和突变单个真实源。

Toggle("Visible", isOn: $isVisible) // The toggle can update the stored value.

管理用户界面状态

将特定于视图的数据封装在应用程序的视图层次结构中,以使您的视图可重用。

struct State
一种可以读取和写入由SwiftUI管理的值的属性包装器类型。
struct Binding
可以读取和写入真理源拥有的值的属性包装器类型。

管理应用程序中的模型数据

在应用程序的数据模型和视图之间建立连接。

struct StateObject
实例化可观察对象的属性包装类型。
struct ObservedObject
订阅可观察对象的属性包装类型,并在可观察对象发生变化时使视图无效。
protocol ObservableObject
在对象更改之前发出的发布者的对象类型。

响应数据更改

func onChange<V>(of: V, perform: (V) -> Void) -> some View
当指定值发生变化时执行操作。
func onReceive<P>(P, perform: (P.Output) -> Void) -> some View
添加一个操作,当此视图检测到给定发布者发出的数据时要执行。

在整个应用程序中分发模型数据

func environmentObject<T>(T) -> some View
向视图子层次结构提供Observable。
struct EnvironmentObject
由父视图或祖先视图提供的可观察对象的属性包装类型。

管理动态数据

protocol DynamicProperty
存储变量的接口,用于更新视图的外部属性。

管理用户界面状态

将特定于视图的数据封装在应用程序的视图层次结构中,以使您的视图可重用。

将数据存储为需要数据的视图的最小共同祖先,以建立跨视图共享的单一真相源。通过Swift属性提供数据为只读,或通过绑定创建与状态的双向连接。SwiftUI监视数据的更改,并根据需要更新任何受影响的视图。


image.png

不要使用状态属性进行持久存储,因为状态变量的生命周期反映了视图生命周期。相反,使用它们来管理仅影响用户界面的瞬态状态,例如按钮的突出显示状态、过滤器设置或当前选择的列表项。在准备更改应用程序的数据模型之前,您可能还会发现这种存储在原型时很方便。

管理可变值为状态

如果视图需要存储可以修改的数据,请使用State属性包装器声明一个变量。例如,您可以在播客播放器视图中创建一个isPlaying Boolean,以跟踪播客的运行时间:

struct PlayerView: View {
    @State private var isPlaying: Bool = false
    
    var body: some View {
        // ...
    }
}

将属性标记为状态告诉框架管理底层存储。您的视图通过使用属性名称读取和写入状态的wrappedValue属性中的数据。更改值时,SwiftUI会更新视图中受影响的部分。例如,您可以在PlayerView中添加一个按钮,该按钮在点击时切换存储值,并根据存储值显示不同的图像:

Button(action: {
    self.isPlaying.toggle()
}) {
    Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}

通过将状态变量声明为私有来限制状态变量的范围。这确保了变量仍然封装在声明它们的视图层次结构中。

声明Swift属性以存储不可变值

要为视图提供视图不会修改的数据,请声明一个标准的Swift属性。例如,您可以扩展播客播放器,以具有包含剧集标题和节目名称字符串的输入结构:

struct PlayerView: View {
    let episode: Episode // The queued episode.
    @State private var isPlaying: Bool = false
    
    var body: some View {
        VStack {
            // Display information about the episode.
            Text(episode.title)
            Text(episode.showTitle)

            Button(action: {
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
}

虽然剧集属性的值是Player的常量,但它不需要在此视图的父视图中是常数。当用户在父剧集中选择不同的情节时,SwiftUI会检测到状态变化,并使用新输入重新创建Player

通过绑定共享状态访问权限

如果视图需要与子视图共享状态控制,请在子视图中声明一个属性,使用Binding属性包装器。绑定代表对现有存储的引用,为基础数据保留单一的真相来源。例如,如果您将播客播放器视图的按钮重构为名为Play的子视图,您可以将其绑定到is属性:

struct PlayButton: View {
    @Binding var isPlaying: Bool
    
    var body: some View {
        Button(action: {
            self.isPlaying.toggle()
        }) {
            Image(systemName: isPlaying ? "pause.circle" : "play.circle")
        }
    }
}

如上所述,您通过直接引用属性来读取和写入绑定的包装值,就像状态一样。但与国家属性不同,绑定没有自己的存储。相反,它引用存储在其他地方的状态属性,并提供与该存储的双向连接。

当您实例化Play时,通过用美元符号($)作为前缀,为父视图中声明的相应状态变量提供绑定:

struct PlayerView: View {
    var episode: Episode
    @State private var isPlaying: Bool = false
    
    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle)
            PlayButton(isPlaying: $isPlaying) // Pass a binding.
        }
    }
}

$前缀要求为其projected提供包装属性,对于状态来说,该属性是与底层存储的绑定。同样,您可以使用$前缀从绑定中获取绑定,允许您通过任意数量的视图层次结构传递绑定。

您还可以获得对状态变量中作用域值的绑定。例如,如果您在播放器的父视图中将episode声明为状态变量,并且剧集结构还包含您想要通过切换控制的布尔值,那么您可以参考$episode.is来获得与剧集最喜欢的状态的绑定:

struct Podcaster: View {
    @State private var episode = Episode(title: "Some Episode",
                                         showTitle: "Great Show",
                                         isFavorite: false)
    var body: some View {
        VStack {
            Toggle("Favorite", isOn: $episode.isFavorite) // Bind to the Boolean.
            PlayerView(episode: episode)
        }
    }
}

动画状态转换

当视图状态更改时,SwiftUI会立即更新受影响的视图。如果要平滑视觉过渡,可以告诉SwiftUI通过在对withAnimation(_:_:)函数的调用中包装触发它们的状态更改来设置它们的动画。例如,可以设置由isPlaying 布尔值控制的更改的动画:

withAnimation(.easeInOut(duration: 1)) {
    self.isPlaying.toggle()
}

通过在动画函数的尾随闭包中更改is,您可以告诉SwiftUI为任何依赖于包装值的内容添加动画,例如按钮图像上的缩放效果:

Image(systemName: isPlaying ? "pause.circle" : "play.circle")
    .scaleEffect(isPlaying ? 1 : 1.5)

SwiftUI使用您指定的曲线和持续时间,或者在您没有提供合理的默认值,随着时间的推移在给定的1和1.5值之间转换缩放效果输入。另一方面,图像内容不受动画的影响,即使相同的布尔值决定了要显示的系统图像。这是因为SwiftUI无法在两个字符串pause.circle和play.circle之间以有意义的方式增量过渡。

您可以将动画添加到状态属性中,或者像上面的例子一样,添加到绑定中。无论哪种方式,SwiftUI都会为底层存储值更改时发生的任何视图更改添加动画效果。例如,如果您在动画块位置上方的视图层次结构级别为Player添加背景颜色,SwiftUI也会将其动画化:

VStack {
    Text(episode.title)
    Text(episode.showTitle)
    PlayButton(isPlaying: $isPlaying)
}
.background(isPlaying ? Color.green : Color.red) // Transitions with animation.

当您想将动画应用于特定视图,而不是在状态变化触发的所有视图中时,请改用theanimationanimation(_:value:)视图修饰符。

管理应用程序中的模型数据

在应用程序的数据模型和视图之间建立连接。

您通常使用独立于应用程序用户界面和其他逻辑的数据模型在应用程序中存储和处理数据。分离促进了模块化,提高了可测试性,并更容易推理您的应用程序的工作原理。

传统上,您使用视图控制器在模型和用户界面之间来回移动数据,但SwiftUI会为您处理大部分同步。要在数据更改时更新视图,您可以使数据模型类可观察对象,发布其属性,并使用特殊属性声明它们的实例。为了确保用户驱动的数据更改流回模型,您将用户界面控件绑定到模型属性。协同工作,这些功能可帮助您维护数据的单一真实来源。

使模型数据可观察

要使模型中的数据更改对SwiftUI可见,请为模型类采用Observable协议。例如,您可以创建一个可观察对象的Book类:

class Book: ObservableObject {
}

系统会自动推断类的Object相关类型,并合成所需的object方法,该方法发出已发布属性的更改值。要发布属性,请将Published属性添加到属性的声明中:

class Book: ObservableObject {
    @Published var title = "Great Expectations"
}

当您不需要时,避免已发布财产的开销。仅发布可以更改且对用户界面很重要的属性。例如,Book类可能有一个identifier属性,初始化后永远不会更改:

class Book: ObservableObject {
    @Published var title = "Great Expectations"

    let identifier = UUID() // A unique identifier that never changes.
}

您仍然可以在用户界面中显示标识符,但由于它没有发布,SwiftUI知道它不必监视该特定属性的更改。

监控可观测对象的变化

要告诉SwiftUI监视可观察对象,请将ObservedObject属性添加到属性的声明中:

struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        Text(book.title)
    }
}

您可以将观察到的对象的单个属性传递给子视图,如上所示。当数据发生变化时,例如当您从磁盘加载新数据时,SwiftUI会更新所有受影响的视图。您还可以将整个可观察对象传递给子视图,并在视图层次结构的级别之间共享模型对象:

struct BookView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        BookEditView(book: book)
    }
}

struct BookEditView: View {
    @ObservedObject var book: Book

    // ...
}

在视图中实例化模型对象

SwiftUI可能会随时创建或重新创建视图,因此使用一组给定的输入初始化视图始终导致相同的视图,这一点很重要。因此,在视图中创建观察到的物体是不安全的。相反,SwiftUI为此提供了State属性。您可以通过以下方式在视图中安全地创建Book实例:

struct LibraryView: View {
    @StateObject private var book = Book()
    
    var body: some View {
        BookView(book: book)
    }
}

状态对象的行为类似于观察到的对象,除了SwiftUI知道为给定的视图实例创建和管理单个对象实例,无论它重新创建视图多少次。您可以在本地使用对象,或将状态对象传递到另一个视图的观察对象属性中,如上例所示。

虽然SwiftUI不会在视图中重新创建状态对象,但它确实为每个视图实例创建了不同的对象实例。例如,以下代码中的每个Library都会获得一个唯一的Book实例:

VStack {
    LibraryView()
    LibraryView()
}

您还可以在顶级App实例或应用程序的Scene实例中创建状态对象。例如,如果您定义一个名为Library的可观察对象来保存图书阅读器应用程序的书籍集合,您可以在应用程序的顶层结构中创建单个库实例:

@main
struct BookReader: App {
    @StateObject private var library = Library()

    // ...
}

在整个应用程序中共享对象

如果您有一个数据模型对象,希望在整个应用程序中使用,但不希望将其传递到多个层次结构层,则可以使用environmentObject(_:)视图修改器将该对象放入环境中:

@main
struct BookReader: App {
    @StateObject private var library = Library()
    
    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

然后,您应用修饰符的视图的任何后代视图都可以通过声明具有Environment属性的属性来访问数据模型实例:

struct LibraryView: View {
    @EnvironmentObject var library: Library
    
    // ...
}

如果您使用环境对象,您可以将其添加到应用程序层次结构顶部的视图中,如上所示。或者,您可以将其添加到视图层次结构中子树的根视图中。无论哪种方式,请记住将其添加到使用该对象的任何视图的预览提供程序中,或者具有使用该对象的后代:

struct LibraryView_Previews: PreviewProvider {
    static var previews: some View {
        LibraryView()
            .environmentObject(Library())
    }
}

使用绑定创建双向连接

当您允许用户更改用户界面中的数据时,请使用对相应属性的绑定。这确保了更新自动流回数据模型。您可以通过在对象名称前加上美元符号($)来获得对观察到的对象、状态对象或环境对象属性的绑定。例如,如果您让用户通过在BookView添加Text来编辑图书的标题,则为文本字段添加图书title属性的绑定:

struct BookEditView: View {
    @ObservedObject var book: Book
    
    var body: some View {
        TextField("Title", text: $book.title)
    }
}

绑定将视图元素连接到基础模型,以便用户直接对模型数据进行更改。

State

一种可以读取和写入由SwiftUI管理的值的属性包装器类型。

@frozen @propertyWrapper struct State<Value>

使用状态作为您存储在视图层次结构中的给定值类型的单一真值源。通过将@State属性应用于属性声明并提供初始值,在anApp、SceneView创建状态值。将状态声明为私有状态,以防止在成员初始化器中设置它,这可能会与SwiftUI提供的存储管理冲突:

struct PlayButton: View {
    @State private var isPlaying: Bool = false // Create the state.

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") { // Read the state.
            isPlaying.toggle() // Write the state.
        }
    }
}

SwiftUI管理物业的存储。当值更改时,SwiftUI会更新视图层次结构中依赖于该值的部分。要访问状态的基础值,可以使用其wrappedValue属性。然而,作为快捷方式,Swift允许您通过直接引用状态实例来访问包装的值。上面的示例通过直接引用属性来读取和写入isPlaying状态属性的包装值。

在需要访问值的视图层次结构中的最高视图中将状态声明为私有。然后与任何也需要访问的子视图共享状态,要么直接用于只读访问,要么作为读写访问的绑定。您可以从任何线程安全地突变状态属性。

笔记
如果您需要存储引用类型,如类的实例,请改用State

与子视图共享状态

如果您将状态属性传递给子视图,SwiftUI会在容器视图中值更改时随时更新子视图,但子视图无法修改值。要使子视图能够修改状态的存储值,请转而传递Binding。您可以通过访问状态的projectedValue获得对状态值的绑定,您可以通过在属性名称前加上美元符号($)来获得该值的绑定。

例如,您可以从上述示例中的播放按钮中删除is状态,而是让按钮绑定:

struct PlayButton: View {
    @Binding var isPlaying: Bool // Play button now receives a binding.

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}

然后,您可以定义一个播放器视图,该视图声明状态,并使用美元符号前缀创建与状态绑定:

struct PlayerView: View {
    @State private var isPlaying: Bool = false // Create the state here now.

    var body: some View {
        VStack {
            PlayButton(isPlaying: $isPlaying) // Pass a binding.

            // ...
        }
    }
}

State一样,将State声明为私有,以防止在成员初始化器中设置它,这可能会与SwiftUI提供的存储管理冲突。与状态对象不同,始终通过在状态声明中提供默认值来初始化状态,如上述示例所示。状态仅用于视图及其子视图的本地存储。

创建一个状态

init(wrappedValue: Value)
创建一个存储初始包装值的状态属性。
init(initialValue: Value)
创建一个存储初始值的状态属性。
init()
创建没有初始值的状态属性。当值符合Expressible时可用。

获得价值

var wrappedValue: Value
状态变量引用的基础值。
var projectedValue: Binding<Value>
对状态值的绑定。

Binding

可以读取和写入真理源拥有的值的属性包装器类型。

@frozen @propertyWrapper @dynamicMemberLookup struct Binding<Value>

使用绑定在存储数据的属性和显示和更改数据的视图之间创建双向连接。绑定将属性连接到存储在其他地方的真相源,而不是直接存储数据。例如,在播放和暂停之间切换的按钮可以使用Binding属性包装器创建对其父视图属性的绑定。

struct PlayButton: View {
    @Binding var isPlaying: Bool

    var body: some View {
        Button(isPlaying ? "Pause" : "Play") {
            isPlaying.toggle()
        }
    }
}

父视图声明一个属性来保持播放状态,使用State属性包装器指示此属性是值的真值来源。

struct PlayerView: View {
    var episode: Episode
    @State private var isPlaying: Bool = false

    var body: some View {
        VStack {
            Text(episode.title)
                .foregroundStyle(isPlaying ? .primary : .secondary)
            PlayButton(isPlaying: $isPlaying) // Pass a binding.
        }
    }
}

当Player初始化Play时,它会将其状态属性的绑定传递到按钮的绑定属性中。将$前缀应用于属性包装值返回其projected,对于状态属性包装器,该值返回对值的绑定。
每当用户点击Play时,Player都会更新其isPlaying的状态。

创建绑定

init?(Binding<Value?>)
通过将基本值投影到未包装的值来创建绑定。
init<V>(Binding<V>)
通过将基本值投影到可选值来创建绑定。
init<V>(Binding<V>)
通过将基值投影到可散列值来创建绑定。
init(projectedValue: Binding<Value>)
从另一个绑定的值创建绑定。
init(get: () -> Value, set: (Value, Transaction) -> Void)
创建一个具有从绑定值读取的闭包的绑定,以及在写入绑定值时应用事务的闭包。
init(get: () -> Value, set: (Value) -> Void)
创建一个带有读写绑定值的闭包的绑定。
static func constant(Value) -> Binding<Value>
创建具有不可变值的绑定。

获得价值

var wrappedValue: Value
绑定变量引用的底层值。
var projectedValue: Binding<Value>
返回绑定的绑定值的投影。
subscript<Subject>(dynamicMember _: WritableKeyPath<Value, Subject>) -> Binding<Subject>
返回对给定键路径的结果值的绑定。

应用动画

func animation(Animation?) -> Binding<Value>
指定绑定值更改时要执行的动画。

应用交易

func transaction(Transaction) -> Binding<Value>
指定绑定的事务。
var transaction: Transaction
绑定的交易。

StateObject

实例化可观察对象的属性包装类型。

@frozen @propertyWrapper struct StateObject<ObjectType> where ObjectType : ObservableObject

使用状态对象作为您存储在视图层次结构中的引用类型的单一真值源。通过将@State属性应用于属性声明并提供符合Observable协议的初始值,在App、Scene或View中创建状态对象。将状态对象声明为私有对象,以防止从成员初始化器设置它们,这可能会与SwiftUI提供的存储管理冲突:

class DataModel: ObservableObject {
    @Published var name = "Some Name"
    @Published var isEnabled = false
}

struct MyView: View {
    @StateObject private var model = DataModel() // Create the state object.

    var body: some View {
        Text(model.name) // Updates when the data model changes.
        MySubView()
            .environmentObject(model)
    }
}

SwiftUI在声明状态对象的容器的生命周期内仅创建一次模型对象的新实例。例如,如果视图的输入发生变化,SwiftUI不会创建新实例,但如果视图的身份发生变化,SwiftUI会创建新实例。当可观察对象的属性更改发布时,SwiftUI会更新任何依赖于这些属性的视图,如上述示例中的Text视图。

笔记
如果您需要存储值类型,如结构、字符串或整数,请改用State属性包装器。

与子视图共享状态对象

可以通过具有ObservedObject属性的属性将状态对象传递到子视图中。或者,通过将environmentObject(_:)修饰符应用于视图,将对象添加到视图层次结构的环境中,如上面代码中的MySubView。然后,可以使用EnvironmentObject属性读取MySubView中的对象或其任何后代:

struct MySubView: View {
    @EnvironmentObject var model: DataModel

    var body: some View {
        Toggle("Enabled", isOn: $model.isEnabled)
    }
}

使用美元符号($)运算符获取对状态对象属性的Binding。当您想创建双向连接时,请使用绑定。在上述代码中,Toggle通过绑定控制模型的is值。

使用外部数据初始化状态对象

当状态对象的初始状态取决于来自其容器外部的数据时,您可以从其容器的初始化器中显式调用对象的初始化器。例如,假设上一个示例中的数据模型在初始化期间输入一个name,并且您希望为该名称使用来自视图外部的值。您可以通过在为视图创建的显式初始化器中调用状态对象的初始化器来执行此操作:

struct MyInitializableView: View {
    @StateObject private var model: DataModel

    init(name: String) {
        // SwiftUI ensures that the following initialization uses the
        // closure only once during the lifetime of the view, so
        // later changes to the view's name input have no effect.
        _model = StateObject(wrappedValue: { DataModel(name: name) }())
    }

    var body: some View {
        VStack {
            Text("Name: \(model.name)")
        }
    }
}

这样做时要小心。SwiftUI仅在给定视图中首次调用其初始化器时初始化状态对象。这确保了即使视图的输入发生变化,对象也能提供稳定的存储。但是,如果您显式初始化状态对象,可能会导致意外行为或不必要的副作用。

在上述示例中,如果Myname输入发生变化,SwiftUI将使用新值重新运行视图的初始化器。然而,SwiftUI仅在您第一次调用状态对象的初始化器时运行您提供给状态对象初始化器的自动关闭,因此模型的存储name值不会改变。

当对象所依赖的外部数据对对象容器的给定实例没有变化时,显式状态对象初始化效果很好。例如,您可以创建两个具有不同常量名称的视图:

var body: some View {
    VStack {
        MyInitializableView(name: "Ravi")
        MyInitializableView(name: "Maria")
    }
}

重要
即使是可配置的状态对象,您仍然将其声明为私有的。这可以确保您不能通过视图的成员初始化器意外设置参数,因为这样做可能会与框架的存储管理冲突并产生意外结果。

通过更改视图身份强制重新初始化

如果您希望SwiftUI在视图输入更改时重新初始化状态对象,请确保视图的身份同时更改。一种方法是将视图的标识绑定到使用id(_:)修饰符更改的值。例如,您可以确保My实例的身份在其name输入更改时发生变化:

MyInitializableView(name: name)
    .id(name) // Binds the identity of the view to the name property.

笔记
如果您的视图出现在For中,它将隐式接收一个使用相应数据元素标识符的id(_:)修饰符。

如果您需要视图根据多个值的变化重新初始化状态,您可以使用Hasher将值组合成单个标识符。例如,当nameis的值发生变化时,如果您想在My中更新数据模型,您可以将两个变量合并到一个散列中:

var hash: Int {
   var hasher = Hasher()
   hasher.combine(name)
   hasher.combine(isEnabled)
   return hasher.finalize()
}

然后将组合散列作为标识符应用于视图:

MyInitializableView(name: name, isEnabled: isEnabled)
    .id(hash)

注意每次输入更改时重新初始化状态对象的性能成本。此外,更改视图身份可能会产生副作用。例如,如果视图的身份同时更改,SwiftUI不会自动为视图内的更改添加动画。此外,更改身份会重置视图持有的所有状态,包括您管理为StateFocusStateGestureState等值。

创建状态对象

init(wrappedValue: () -> ObjectType)
创建一个具有初始包装值的新状态对象。

获得价值

var wrappedValue: ObjectType
状态对象引用的底层值。
var projectedValue: ObservedObject<ObjectType>.Wrapper
创建与其属性绑定的状态对象的投影。

ObservedObject

订阅可观察对象的属性包装类型,并在可观察对象发生变化时使视图无效。

@propertyWrapper @frozen struct ObservedObject<ObjectType> where ObjectType : ObservableObject

当输入是ObservableObject@Observed属性添加到SwiftUIView的参数中,并且您希望当对象的已发布属性发生变化时,视图会更新。您通常这样做是为了将State传递到子视图中。
以下示例将数据模型定义为可观察对象,将视图中的模型实例化为状态对象,然后将实例作为观察对象传递给子视图:

class DataModel: ObservableObject {
    @Published var name = "Some Name"
    @Published var isEnabled = false
}

struct MyView: View {
    @StateObject private var model = DataModel()

    var body: some View {
        Text(model.name)
        MySubView(model: model)
    }
}

struct MySubView: View {
    @ObservedObject var model: DataModel

    var body: some View {
        Toggle("Enabled", isOn: $model.isEnabled)
    }
}

当可观察对象的任何已发布属性发生变化时,SwiftUI会更新任何依赖于对象的视图。子视图还可以更新模型属性,如上述示例中的Toggle,这些属性在整个视图层次结构中传播到其他观察者。
不要为观察到的对象指定默认值或初始值。仅将该属性用于作为视图输入的属性,如上例所示。

创建观察到的对象

init(wrappedValue: ObjectType)
创建一个具有初始包装值的观察对象。
init(initialValue: ObjectType)
创建一个具有初始值的观察对象。

获得价值

var wrappedValue: ObjectType
观察到的对象引用的底层值。
var projectedValue: ObservedObject<ObjectType>.Wrapper
观察到的物体的投影,与其属性建立绑定。
struct Wrapper
底层可观察对象的包装器,可以创建对其属性的绑定。

EnvironmentObject

由父视图或祖先视图提供的可观察对象的属性包装类型。

@frozen @propertyWrapper struct EnvironmentObject<ObjectType> where ObjectType : ObservableObject

每当可观察对象发生变化时,环境对象都会使当前视图无效。如果您将属性声明为环境对象,请务必通过调用其environmentObject(_:)修饰符在祖先视图上设置相应的模型对象。

创建环境对象

init()
创建一个环境对象。

获得价值

var wrappedValue: ObjectType
环境对象引用的底层值。
var projectedValue: EnvironmentObject<ObjectType>.Wrapper
环境对象的投影,使用动态成员查找创建与其属性的绑定。
struct Wrapper
底层环境对象的包装器,可以使用动态成员查找为其属性创建绑定。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容