ios(swiftui) 属性包装器详解

目录

[1. @State](#1. @State)

[2. @Binding](#2. @Binding)

[3. @ObservedObject 和@Published](#3. @ObservedObject 和@Published)

[4. @StateObject](#4. @StateObject)

[5. @EnvironmentObject和@Environment](#5. @EnvironmentObject和@Environment)

[6. @AppStorage](#6. @AppStorage)


在 SwiftUI 中,属性包装器用于增强和管理视图的状态,以及处理视图与数据模型之间的绑定和交互。下面是一些常见的属性包装器:

1. @State

使用@State属性包装器的变量是私有的,并且仅在创建它们的视图中使用。当用户与界面交互引起状态改变时,SwiftUI会重新绘制依赖这些状态的视图。这是响应式编程思想在SwiftUI中的表现。

这里有一个简单的例子,演示如何使用@State

Swift 复制代码
import SwiftUI

struct ContentView: View {
    // 使用@State修饰的计数器变量
    @State private var counter = 0

    var body: some View {
        VStack {
            Text("你点击了 \(counter) 次")
            Button("点击我") {
                // 因为counter是@State变量,改变它的值将会重新绘制视图
                self.counter += 1
            }
        }
    }
}

在这个例子中,每当counter变量的值改变时(例如,用户每次点击按钮时),SwiftUI都会重新调用body属性,因此文本显示的计数器值会更新。

2. @Binding

允许视图共享并能够双向绑定到外部的状态。这常用于将父视图的状态传递给子视图。

@Binding 是一个属性包装器,它允许子视图共享父视图的状态。@Binding 为子视图提供对父视图的某个状态的引用,这样一来,子视图可以读取这个状态并能够在发生更改时更新它,而不是维护它自己的状态副本。

当你使用 @Binding 时,它实际上是对一个 @State@ObservedObject@EnvironmentObject 或其他具有可观察状态的源的引用。

以下是一个简单的例子说明如何使用 @Binding

首先,假设你有一个父视图,使用 @State 管理一个布尔值 isOn

Swift 复制代码
struct ParentView: View {
    @State private var isOn = false

    var body: some View {
        VStack {
            Toggle("Switch", isOn: $isOn)
            ChildView(isOn: $isOn) // 传递 @State 给子视图
        }
    }
}

接着,你有一个子视图 ChildView,需要使用从父视图传递的 isOn 状态:

Swift 复制代码
struct ChildView: View {
    @Binding var isOn: Bool // 使用 @Binding 接收父视图的状态

    var body: some View {
        Text(isOn ? "It's on" : "It's off")
    }
}

在这个例子中,子视图 ChildView 可以直接显示 isOn 的状态,也能够在不同的视图层级之间共享和修改这个状态,如果 isOn 在子视图中被修改,父视图中的 @State 也会相应更新。

3. @ObservedObject@Published

@Published 通常用于 ObservableObject 里的属性。当给标记为 @Published 的属性赋值时,这个改动会发布出去,所有观察者都可以接收到这个改变。

@ObservedObject@Binding 类似,但是用于当数据模型遵守 ObservableObject 协议时。它会使得视图自动更新以响应可观察对象中发生的变化。

声明一个外部来源的可观察对象。当这个可观察对象发生变化时,使用 @ObservedObject 标记的视图会被重新绘制。这使得在SwiftUI中实现数据的双向绑定和状态管理变得更加简单和直观。

这里有一些基础的概念和使用示例:

可观察对象 (Observable Object)

可观察对象通常是遵循了 ObservableObject 协议的类,并且通过 @Published 属性包装器来声明那些当变化时需要通知视图重新渲染的属性。

Swift 复制代码
import Combine
import SwiftUI

class ExampleModel: ObservableObject {
    @Published var score = 0
}

使用 @ObservedObject

在SwiftUI视图中使用 @ObservedObject 来观察这些对象的变化:

Swift 复制代码
struct ExampleView: View {
    @ObservedObject var model: ExampleModel

    var body: some View {
        Text("Score: \(model.score)")
            .onTapGesture {
                model.score += 10
            }
    }
}

在这个例子中,每次点击文本时,模型的 score 属性会增加10。由于 score被标记为 @Published,且 ExampleModel 遵循了 ObservableObject,所以每次 score 改变时,使用了 @ObservedObjectExampleView 都将重新绘制,反映出新的分数。

注意事项

  • 当使用 @ObservedObject 时,你需要确保这个对象在视图更新时不会被销毁或者重新创建,否则会丢失其状态。这通常意味着这个对象是由外部传递给视图的,或者在视图的父级或共享环境(如使用 EnvironmentObject)中保存。
  • 对于视图自己的内部状态,你通常会使用 @State 或者 @StateObject 作为属性包装器。

@StateObject vs @ObservedObject

不要混淆 @ObservedObject@StateObject

  • @StateObject 用于视图对数据所有权的声明(它负责创建这个对象),并且它会在视图重新绘制时保持对象的生命周期。它在SwiftUI 2.0中引入,用来取代在视图体内部初始化 @ObservedObject 的用法。
  • @ObservedObject 通常是从父视图或其他部分的应用程序传递过来的,并且视图不负责管理其生命周期。

在实践中,你需要根据实际的数据所有权和生命周期管理要求来选择使用 @StateObject 还是 @ObservedObject

4. @StateObject

  • 在 SwiftUI 2.0 中引入,用来初始化可观察对象,并且拥有对该对象的所有权。也就是说它会构建并保持对象直到视图的整个生命周期结束。

@StateObject@ObservedObject 类似,都用于引用遵守 ObservableObject 协议的类实例。这种遵守 ObservableObject 协议的类会发布对其属性的更改,这样 SwiftUI 就可以在这些属性变化时更新 UI。

两者之间的区别在于它们的用途和生命周期:

  • @StateObject 应当用于创建和拥有对应的 observable object,即表示该视图负责初始化这个对象,并且是这个对象的"源头"或首个拥有者。这保证了状态对象不会因为视图的重新渲染而被重新创建。

  • @ObservedObject 应当用于那些已经由其他部分创建的 observable objects。其实际上是对从外部传入的状态对象的引用,意味着它不负责该对象的生命周期。

在以下情形下使用 @StateObject

Swift 复制代码
import SwiftUI

class ExampleModel: ObservableObject {
    @Published var count: Int = 0
    // Other properties and methods
}

struct ExampleView: View {
    @StateObject var model = ExampleModel()

    var body: some View {
        // UI elements that use 'model'
        Text("Count: \(model.count)")
            .onTapGesture {
                model.count += 1
            }
    }
}

这个例子展示了如何在视图中创建一个 @StateObject 以存储和管理状态。在视图的生命周期中,ExampleModel 对象将保持活动并不会因为视图的重建而被销毁或重置。使用这种方式,你可以确保状态的一致性并避免不必要的对象重建

5. @EnvironmentObject和@Environment

@EnvironmentObject可以从环境中获取共享的数据模型,这个属性包装器不负责创建对象,而是假定共享的对象已经被其它某部分的代码添加到环境中去了

@Environment 允许视图访问从 iOS 提供的环境值,例如 @Environment(\.presentationMode) 可以访问视图的表现模式。

@EnvironmentObject@Environment 都是用于数据传递和状态共享的属性包装器,不过它们在使用中有一些区别。

(1)@EnvironmentObject

@EnvironmentObject 用于向视图的层次结构中传递共享的数据对象。你可以在应用的任意位置注入这个共享的数据对象,让其他需要这些数据的视图可以直接访问它,而不需要通过视图参数来逐层传递。这增加了数据共享的便捷性,尤其是在大型项目中。它通常用于类似于全局状态或者应用中的共享数据模型。

使用@EnvironmentObject时,首先需要在某个视图之外的地方创建一个可观察对象。该对象需要遵守ObservableObject协议。然后,在视图层次结构的上游 somewhere(比如在顶级视图或者场景代理中) 你需要将这个对象作为环境对象注入。在需要访问这些数据的视图中,你使用@EnvironmentObject来声明这个依赖,然后 Swift UI会自动为你提供这个对象。

示例代码:

Swift 复制代码
class SharedData: ObservableObject {
    @Published var value: String = "Hello, World!"
}

struct ContentView: View {
    // 注入环境对象
    @EnvironmentObject var sharedData: SharedData

    var body: some View {
        Text(sharedData.value)
    }
}

let sharedData = SharedData()
let contentView = ContentView().environmentObject(sharedData)

如果尝试访问未注入的@EnvironmentObject,应用会崩溃。

(2)@Environment

@Environment用于读取从环境中传递下来的值,如系统设置、接口样式、布局方向等。相比@EnvironmentObject@Environment更多用于访问由 SwiftUI 框架维护的预设值,而不是自定义的可观测对象。一个常见的使用场景是,读取系统的颜色方案(.colorScheme),或者是当前的时间区域(.timeZone)。

示例代码:

Swift 复制代码
struct ContentView: View {
    // 从环境中读取值
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        Text("The current color scheme is \(colorScheme == .dark ? "Dark" : "Light")")
    }
}

在这个例子中,我们没有注入任何自定义对象到环境中。相反,我们直接访问了 SwiftUI 环境中的预设值。

两者虽然看起来类似,但根据使用场景的不同,开发者可以选择最适合的一个。@EnvironmentObject 更适合那些全局共享状态的情景,而@Environment更适合需要访问由系统维护的环境值。

6. @AppStorage

-在 SwiftUI 2.0 中引入,用于简单的数据持久化,当读写 UserDefaults 时自动同步视图。

@AppStorage 是一个 Swift 属性包装器(property wrapper),提供了一种将用户默认设置或应用设置存储在 UserDefaults 中的便捷方式。使用 @AppStorage, 你可以创建一个绑定到 UserDefaults 中具体键的属性,当该属性的值发生变化时,UserDefaults 会自动更新,反之亦然。

在 SwiftUI 中,@AppStorage 的使用十分普遍,特别是用来响应某些设置或偏好的变化,并据此更新UI。这种数据持久化的方式适用于存储少量的用户配置信息,例如:标记应用是否为首次启动、用户的暗黑模式偏好、或者任何小型配置数据。

这里是一个基本的使用示例:

Swift 复制代码
import SwiftUI

struct ContentView: View {
    // 使用 @AppStorage 监视对应的 UserDefaults 键值对,当值变化时自动更新视图。
    @AppStorage("isDarkMode") private var isDarkMode = false

    var body: some View {
        VStack {
            Text(isDarkMode ? "Dark Mode is ON" : "Dark Mode is OFF")
            
            // 切换按钮可以更改 @AppStorage 绑定的值
            Button("Toggle Dark Mode") {
                isDarkMode.toggle()
            }
        }
    }
}

在上面的例子中,isDarkMode 属性绑定到了 UserDefaults 中的 "isDarkMode"键。当你点击按钮切换 isDarkMode 的值时,这个值会自动保存到 UserDefaults并在下次应用启动时保留。同时 UI 也会响应这个值的变化并立即更新。这种简单的数据绑定方法让你能够不必直接操作 UserDefaults API 而轻松保存和访问用户设置。

官网:SwiftUI | Apple Developer Documentation

相关推荐
502胶水2052 小时前
腾讯地图异步调用
开发语言·ios·swift
刘小哈哈哈5 小时前
iOS UITableView自带滑动手势和父视图添加滑动手势冲突响应机制探索
macos·ios·cocoa
分享者花花5 小时前
恢复出厂设置后如何从 iPhone 恢复数据
windows·macos·ios·智能手机·excel·cocoa·iphone
1024小神5 小时前
SwiftUI中List的liststyle样式及使用详解添加、移动、删除、自定义滑动
ios·swiftui·swift
Lik10247 小时前
ReactNative如何实现沉浸式状态栏及渐变色Header【兼容Android和iOS】
android·react native·ios
Geeker557 小时前
适用于 Windows的 5 个最佳 PDF 转 Word 转换器
ios·智能手机·pdf·电脑·word·手机·iphone
勤劳兔码农8 小时前
iOS开发新手教程:Swift语言与Xcode工具链
ios·xcode·swift
<花开花落>17 小时前
iOS App 测试环境升级,遇到的问题以及解决方法
macos·ios·appium
cpluser17 小时前
在 VS Code 中自动化 Xcode 项目编译和调试
macos·ios·自动化·apple vision pro·xcode
鹿屿二向箔17 小时前
iOS开发-Xcode
macos·ios·xcode