Swift学习笔记42-SwiftUI的属性包装器(讲解+面试)

属性包装器(SwiftUI)-含例子

属性包装器的本质是对属性的访问逻辑(如getter/setter中的存储、计算、校验、通知)抽离并封装成一个独立的类型,从而在不同地方通过@属性包装器名,来实现代码的高级复用。

@MainActor

是全局执行者的修饰符,它确保修饰的代码、类或属性必须在主线程上运行。

就比如在ViewModel前面可以使用@MainActor确保刷新UI操作在主线程上。

swift 复制代码
@MainActor
class StepCounterViewModel: ObservableObject {}
@State

@State修饰值类型(区别于引用类型)私有变量,当@State修饰的变量改变时,SwiftUI会重新绘制当前视图。一般都和@Binding一起使用,@State在父视图使用,@Binding在子视图使用。

*其实使用@State和@Binding的两个视图本质上是没有任何关系的,只是通过@State和@Binding把变量联系起来了

swift 复制代码
struct MyView: View {
    @State private var text = ""        // 你持有的数据源
    @State private var isOn = false      // 你持有的数据源
    
    var body: some View {
        VStack {
            // TextField 内部用的就是 @Binding,你传进去 $text
            TextField("请输入", text: $text) 
            
            // Toggle 内部用的也是 @Binding,你传进去 $isOn
            Toggle("开关", isOn: $isOn)     
        }
    }
}
@Binding

@Binding建立读写连接传递数据,在子视图修改@Binding 的值,父视图的数据也会随之改变(比如用@State修饰的变量)。

举例:把isChildOn和isOn沟通起来了

swift 复制代码
import SwiftUI

// 父视图(拥有真正的存钱罐)
struct ParentView: View {
    // 1️⃣ 用 @State 创建真正的存钱罐
    @State private var isOn: Bool = false 
    
    var body: some View {
        VStack(spacing: 20) {
            // 父视图读取展示
            Text("父视图显示:\(isOn ? "🟢 开" : "🔴 关")")
            
            Divider()
            
            // 2️⃣ 调用子视图,用 $isOn 把"操控手柄"传过去
            ChildSwitchView(isChildOn: $isOn)
        }
        .padding()
    }
}

// 子视图(只拿手柄,不存钱)
struct ChildSwitchView: View {
    // 3️⃣ 用 @Binding 接住手柄
    @Binding var isChildOn: Bool 
    
    var body: some View {
        VStack {
            Text("子视图控制区域")
            
            Button("子视图控制开关") {
                // 4️⃣ 改动顺着 @Binding 管道反向传回父视图,父视图的 @State 改变,整个 UI 刷新!
                isChildOn.toggle() 
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(10)
    }
}
@StateObject

@StateObject修饰ObservableObject的类实例的作用是让它始终被持有,保证UI刷新时不被销毁。(就是把它固定住)

让ViewModel始终被View持有

swift 复制代码
struct ContentView: View {
    // 1️⃣ @StateObject: 在 ViewModel 首次被创建的视图中持有它,绑定生命周期
    @StateObject private var profileVM = UserProfileViewModel()
    }
@ObservedObject

@ObservedObject和@StateObject不同,它只接收外部传进来的ViewModel,而不持有它的生命周期,只负责监听

举例:使用@ObservedObject来监听ViewModel的变化(数据存在ViewModel里)

然后就可以直接使用ViewModel中变量

swift 复制代码
struct ChildDetailView: View {
    // 4️⃣ @ObservedObject: 不持有生命周期,纯接收外部传进来的 ViewModel 引用
    @ObservedObject var viewModel: UserProfileViewModel
    
    var body: some View {
        VStack(spacing: 20) {
            Text("子详情页")
                .font(.title2)
                .bold()
            
            Text("从父视图同步过来的积分: \(viewModel.score)")
                .font(.title3)
                .foregroundColor(.blue)
    }
@AppStorage

@AppStorege的作用是快速把某个属性,如isDarkMode写入UserDefaults,Key为"isDarkMode",下次打开,或者去到其他页面也会保留这个属性,比如这里可以用来控制白天或者黑夜模式。

@AppStorage("isDarkMode") private var isDarkMode: Bool = false

设置整个View的主题偏好,将其与isDarkMode绑定,实现控制。

swift 复制代码
.preferredColorScheme(isDarkMode ? .dark : .light)

然后写开关,将isDarkMode与开关绑定。$表示开关样式随着isDarkMode变化而变化(双向修改数据)。

swift 复制代码
Toggle("开启深色模式", isOn: $isDarkMode)
@Environment

@Environment用于 读取系统预设与系统环境参数,就比如.colorScheme用来读取系统白天与黑夜模式,.dismiss提供关闭当前页面的方法。

可以使用@Environment读取系统的主题状态为colorScheme,再进行对应的操作(比如切换图标等)

然后再读取系统预设的关闭页面方法dismiss,就可以使用。

swift 复制代码
struct ChildDetailView: View {
    // 4️⃣ @ObservedObject: 不持有生命周期,纯接收外部传进来的 ViewModel 引用
    @ObservedObject var viewModel: UserProfileViewModel
    
    // 5️⃣ @Environment: 读取系统级的环境变量(如页面关闭动作 dismiss)
    @Environment(\.dismiss) private var dismiss
    @Environment(\.colorScheme) private var colorScheme
    
    var body: some View {
        VStack(spacing: 20) {
            Text("子详情页")
                .font(.title2)
                .bold()
            
            Text("从父视图同步过来的积分: \(viewModel.score)")
                .font(.title3)
                .foregroundColor(.blue)
            
            Button("在子视图修改积分") {
                viewModel.addScore()
            }
            .buttonStyle(.bordered)
            
            Divider()
            
            Text("当前系统暗黑/亮色状态 (@Environment): \(colorScheme == .dark ? "Dark" : "Light")")
                .font(.caption)
                .foregroundColor(.secondary)
            
            Button("关闭当前页面 (@Environment 之 dismiss)") {
                dismiss() // 调用系统环境提供的推出页面方法
            }
            .foregroundColor(.red)
        }
        .padding()
        .navigationTitle("详情页")
    }
}
@EnviromentObject

在APP的入口创建ViewModel的实例,并通过.environmentObject()方法把这个实例挂载到根视图上,在任意子视图里就可以通过@EnvironmentObject提取这个实例,并使用ViewModel里的方法,非常方便。

举例:

1.这是事先写好的ViewModel,继承ObservableObject 让其可以被@StateObject修饰,始终被ContentView持有

swift 复制代码
class GlobalThemeState: ObservableObject {
    @Published var isVipUser: Bool = true
    
    func getVIP() {
        isVipUser = !isVipUser
    }
}
  1. @main表示APP的入口

在APP入口创建实例并挂载到根视图

swift 复制代码
@main
struct DemoApp: App {
    // 创建全局状态实例
    @StateObject private var globalTheme = GlobalThemeState()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                // 注入到根视图,这样任意子视图都能直接用 @EnvironmentObject 取到
                .environmentObject(globalTheme)
        }
    }
}

在子视图声明就可以直接使用

swift 复制代码
struct ContentView: View {
    // 2️⃣ @EnvironmentObject: 接收注入的全局共享对象
    @EnvironmentObject var globalState: GlobalThemeState
    }

现在就可以写UI样式了

swift 复制代码
Button("更改VIP激活状态 (@StateObject 触发)") {
                    globalState.getVIP()
                }
                .buttonStyle(.borderedProminent)

通过接收的全局对象globalState调用ViewModel中的方法

@Published属于Combine框架

@Published表示通知,使用@Published,对应的ViewModel必须遵循ObservableObject协议;表明这是一个可以被SwiftUI监听的对象。所有监听了这个ViewModel的View,收到通知后,就会自动刷新UI。

举例

swift 复制代码
class UserProfileViewModel: ObservableObject {
    @Published var name: String = "张三"
    @Published var score: Int = 100
    
    func addScore() {
        score += 10
    }
}
相关推荐
自小吃多1 小时前
器件移动、旋转、镜像、对齐、等间距操作笔记
笔记·嵌入式硬件
存在morning2 小时前
【Paimon 学习笔记 三】工作流程:Paimon 的批写、流写、批读与流读
笔记·学习
老当益壮梁奶奶3 小时前
Linux软件编程学习笔记(八):进程间通信详解(1)
linux·c语言·笔记·学习·算法
weixin_466068115 小时前
《十分钟冥想》精读笔记(上):每天10分钟,给大脑做一次“系统减负”
笔记
凯尔萨厮5 小时前
Java学习笔记十(注解)
java·笔记·学习
JoannaJuanCV5 小时前
VLM学习-SFT(监督微调)
深度学习·学习·机器学习·大模型·视觉大模型·vlm·视觉编码器
程序员大雄学编程6 小时前
微积分43. 无穷积分入门:从概念到Python实战可视化
开发语言·python·学习·微积分
一条破秋裤7 小时前
STM32 学习笔记:GPIO 输出实验——LED 闪烁、流水灯与蜂鸣器
笔记·stm32·学习
2601_962073977 小时前
【MySQL】全面学习数据库查询技巧:查询指令深度学习指南
数据库·学习·mysql