@State, @StateObject, @Published
@State
swift
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("点了 (count) 次") // 2. 读值
Button("+1") {
count += 1 // 3. 改值 → 自动刷新界面
}
}
.font(.largeTitle)
}
}
@State 是 SwiftUI 里最常用的属性包装器之一。
注意事项
- 只能用于 当前 View 内部 的私有可变状态。
- 当
@State的值改变时,SwiftUI 会 自动重新计算 body,把最新数据画到屏幕上。
@StateObject
swift
import SwiftUI
import Combine
// 1. 先写一个可观察的模型
class TimerModel: ObservableObject {
@Published var seconds = 0 // 2. 发布变化
private var cancellable: AnyCancellable?
init() {
cancellable = Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { _ in
self.seconds += 1
}
}
}
// 3. 视图里"创建并持有"这个模型
struct TimerView: View {
@StateObject private var model = TimerModel() // ← 关键:@StateObject
var body: some View {
Text("(model.seconds) 秒")
.font(.largeTitle)
}
}
@StateObject 也是属性包装器,专门用来 创建并持有 一个 ObservableObject 实例。
注意事项
- 对象里的
@Published属性一旦变化,所有用到它的视图自动刷新。 - 只有第一次初始化时才会真正创建;后面 SwiftUI 重绘视图时不会反复 new 出新对象。
@Published
@Published 不是给 View 用的属性包装器,而是 写在 ObservableObject 里的"广播器" 。只要这个属性值一变,立刻通知所有正在监听它的视图。
注意事项
- 只能用在 ObservableObject 协议 的类里。
- 标记为
@Published的属性,SwiftUI 会自动生成objectWillChange发布事件。 - 视图那一端用
@StateObject或@ObservedObject拿到这个对象后,就能 实时响应 这些变化。
Q&A
Q:@State 只能用在集成了 View 的struct或者class 中?
A:@State 只能出现在 SwiftUI 的 View 结构体(或极少数自己实现 View 协议的类)里 ,而且必须是 当前视图私有的可变状态。
Q:@state只能修饰一些基础类型,无法修饰复杂的引用类型等
A:@State 完全可以修饰"复杂"的引用类型(class、嵌套 struct、数组、字典、自定义模型......),只要它满足两点:
- 类型遵守
Equatable(SwiftUI 靠它 diff,官方文档推荐,不遵守也能跑,但可能多刷)。 - 你在 View 里 把整个实例换掉(值语义)------SwiftUI 只关心"旧值 vs 新值"有没有变化,不关心内部字段。
Q:就是说比如我定义了一个 @State var person = Person(name: "Xiaomi") ,然后当我更换引用对象比如,person = Person(name:"Sam")也会触发界面的刷新?
A:没错,会刷新------前提是你把"整颗对象"换掉
Q:那么如果我想让类的成员可视化到界面上,并实时显示最新的值,那么我就得遵守 ObservableObject 协议 ,并且使用 @Published 修饰对应的成员?
A:是的,想让"类的某个成员"一变化就自动刷新界面,必须同时满足:
- 类遵守
ObservableObject协议; - 那个成员用
@Published修饰; - 视图通过
@StateObject(或@ObservedObject)持有这个类实例。
避坑小贴士
- 不要 在
init里直接修改@State/@StateObject包装的值,SwiftUI 会忽略这次写入。 - 不要 把
@StateObject用在"不是真正拥有者"的视图里,否则可能出现"同一模型被重复创建"或"野指针"问题;子视图只观察时用@ObservedObject。 - 不要 在
ObservableObject里手动调用objectWillChange.send(),除非你有特殊时机需求;@Published已经帮你做了。 - 不要 把值类型(struct)也加上
@Published,它照样工作,但会失去"共享引用"的意义,容易陷入"改了副本、界面没反应"的错觉。
界面背景色
Q&A
Q:最新的swift ui语法中,如何设置界面的背景色,并且忽略安全边界? Color.blue.ignoreSageAre() 这么写是不是不是最佳开发实践了?
A: 你写的 ignoreSageAre() 拼写错了,应该是
.ignoresSafeArea()(iOS 14+ 的名字,以前叫 .edgesIgnoringSafeArea(.all))。
另外,直接把 Color.blue 当根视图 虽然能跑,但属于"偷懒写法"------
一旦后面要加别的控件,就得再包 ZStack,不如一开始就写成"背景层 + 内容层"分离的形式,可读性、可维护性都更好。
推荐写法(iOS 14+ 官方风格):
swift
struct ContentView: View {
var body: some View {
ZStack {
Color.blue // 1. 背景层
.ignoresSafeArea() // 2. 忽略安全边界
Text("Hello, SwiftUI") // 3. 内容层
.foregroundColor(.white)
}
}
}
Q:我记得还可以直接 ZStack{..}.background(.blue).ignore啥的,是不是也可以?
A:可以,但顺序得换一下:
background 只是给 ZStack 刷一层颜色,本身不会自动撑满屏幕 ,所以要把 .ignoresSafeArea() 贴在真正"能撑满"的那个视图(也就是 Color.blue)上,而不是贴在 ZStack 后面。
scss
ZStack {
Text("Hello, SwiftUI").foregroundColor(.white)
}
.background(.blue.ignoresSafeArea()) // iOS 15+ 语法糖
Q:和Color.blue.ignoresSafeArea() 这种方式比,那种方式更好一些?
A:两种写法在最终视觉效果上几乎一样:整屏蓝底、无视安全区。差异主要在可读性、扩展性、习惯上,而不是性能。
Color.blue.ignoresSafeArea() 是最佳实践。
两种写法在最终视觉效果上几乎一样:整屏蓝底、无视安全区。
差异主要在可读性、扩展性、习惯上,而不是性能。
- 快速原型、单页极简界面:用
.background(Color.blue.ignoresSafeArea())完全没问题。 - 正式项目、多人协作、后续会迭代:优先 ZStack 里单独放
Color层,扩展、阅读都更舒服。
字体颜色
Q&A
Q:最新的swift ui 中 .fontColor 好像已经不推荐了?
A:在 SwiftUI 里并没有 .fontColor 这个修饰器,从 iOS 13 开始官方就统一用
.foregroundColor(_:) 或 iOS 15 起更通用的 .foregroundStyle(_:) 来给 Text、Image、Shape 等设置"前景色"。
swift
Text("Hello")
.foregroundColor(.red) // iOS 13+ 专用颜色
// 或者
Text("Hello")
.foregroundStyle(.red) // iOS 15+ 接受任何 ShapeStyle(纯色、渐变、材质)
圆角
Q&A
Q:Text("CLICK HERE") .foregroundStyle(.white) .padding() .background(.black).cornerRadius(8) 这么设置圆角好像也不推荐了在新的swiftui中
A:cornerRadius(8) 并没有被废弃,代码现在依旧能编译、能跑;
但在 iOS 17 的 Apple Sample 和 WWDC 视频里,官方更推荐用 .clipShape(.rect(cornerRadius: 8)) (或者 .clipShape(RoundedRectangle(cornerRadius: 8)))来裁切,语义更清晰------"先画完再裁",而不是"把边框圆角当成修饰器"。
swift
// 1. 老写法(依旧可用)
Text("CLICK HERE")
.foregroundStyle(.white)
.padding()
.background(.black)
.cornerRadius(8)
// 2. iOS 17 推荐写法
Text("CLICK HERE")
.foregroundStyle(.white)
.padding()
.background(.black)
.clipShape(.rect(cornerRadius: 8)) // ← 语义:把整体裁成圆角矩形