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
    }
}
相关推荐
彧azz4 小时前
算法设计与分析:贪心与动态规划
数据结构·学习·算法·贪心算法·动态规划
一条小小yu6 小时前
基于数据库唯一索引 + 过期时间的分布式锁
学习
传奇开心果编程6 小时前
【ArkUI 练中学】第15课:UI 界面设计与实战
学习·ui·华为·harmonyos
黑化旺仔6 小时前
【OC】KVO
macos·ios·objective-c·cocoa
zjxtxdy6 小时前
SPI通信协议
笔记·单片机
传奇开心果编程8 小时前
【springboot基础语法学与练】第 1 课:从零开始
java·spring boot·后端·学习
扶风ff8 小时前
练题簿小程序:家庭在线练题,课后复习更简单
学习·小程序
传奇开心果编程8 小时前
【Flutter入门练中学】第2课:布局系统
学习·flutter·ui
HouWan8 小时前
SwiftUI 小技巧:如何读取 SwiftUI Font 的详细信息
ios·swiftui·apple
彧azz8 小时前
Linux 网络编程学习总结
linux·网络·笔记·学习·面试