Swift 疑难杂想

@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)持有这个类实例。

避坑小贴士

  1. 不要init 里直接修改 @State / @StateObject 包装的值,SwiftUI 会忽略这次写入。
  2. 不要@StateObject 用在"不是真正拥有者"的视图里,否则可能出现"同一模型被重复创建"或"野指针"问题;子视图只观察时用 @ObservedObject
  3. 不要ObservableObject 里手动调用 objectWillChange.send(),除非你有特殊时机需求;@Published 已经帮你做了。
  4. 不要 把值类型(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))   // ← 语义:把整体裁成圆角矩形
相关推荐
lancoff4 小时前
#1 onLongPressGesture
ios·swiftui
u***u6853 天前
后端在消息队列中的可靠性保证
swiftui·ar·rocketmq
东坡肘子5 天前
当 Android 手机『强行兼容』AirDrop -- 肘子的 Swift 周报 #113
android·swiftui·swift
汉秋5 天前
SwiftUI 最新数据模型完整解析:@Observable、@State、@Bindable(iOS17+ 全新范式)
swiftui·swift
非专业程序员6 天前
iOS 实现微信读书的仿真翻页
ios·swiftui·swift
非专业程序员Ping6 天前
iOS 实现微信读书的仿真翻页
ios·swiftui·swift
lancoff7 天前
#3 Creating Shapes in SwiftUI
ios·swiftui