普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月1日iOS

当 Android 手机『强行兼容』AirDrop - 肘子的 Swift 周报 #113

作者 Fatbobman
2025年12月1日 22:00

AirDrop 让使用者可以在各种不同类似的苹果设备上高效、无损的传输数据,它一直是苹果生态的专属且核心功能。但,这种情况现在出现了“奇怪”的变化。几天前,谷歌宣布在 Pixel 10 中,在没有苹果的参与下,为 Quick Share 提供了 AirDrop 的兼容机制,实现了安卓手机与苹果手机基于 AirDrop 的无线互通。

iOS深入理解事件传递及响应

作者 Haha_bj
2025年12月1日 13:59

一、事件传递

事件传递相关的两个方法

// 哪个视图响应事件返回哪个  
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;     
// 点击位置是否在当前视图范围  
-(BOOL)pointInside(CGPoint)point withEvent:(UIEvent *)event; 

图片.png 如图,View A中包含View B1、View B2,View B2中包含View C1,View C2既包含View C1的一部分,又包含View B2的一部分,View C1中包含View D。当点击View C2的空白区域时,系统如何找到事件响应者为View C2?

(1)事件传递流程

当用户点击屏幕的某个位置,该事件会被传递给UIApplicationUIApplication又传递给当前的UIWindow,UIWindow会通过hitTest:WithEvent:方法返回响应的视图。hitTest:WithEvent:方法内部通过pointInside:withEvent:方法判断点击point是否在当前UIWindow范围内,如果在,则会遍历其中的所有子视图SubViews来查找最终响应此事件的视图,遍历方式为倒序遍历,即最后添加到UIWindow的视图最优先被遍历到,依次遍历,可以看作是递归调用。每个UIView中又都会调用其对应hitTest:WithEvent:方法,最终返回响应视图hit,如果hit有值,则hit视图就作为该事件的响应视图被返回,如果hit没有值,但在当前UIWindow范围内,则当前UIWindow作为事件的响应视图。

图片.png

(2)hitTest:WithEvent:系统内部实现

首先在hitTest:WithEvent:方法内部先判断当前视图的hidden属性、是否可交互、透明度是否大于0.01。如果该视图不同时满足上述3个条件,则返回nil,当前视图不作为事件的响应视图,当前视图的父视图继续遍历其他的子视图;如果该视图没有隐藏、用户可交互、透明度大于0.01,则会通过pointInside:WithEvent:方法判断点击的点是否在当前视图范围内,如果不在,则同样返回nil,当前视图仍不作为事件的响应者;如果在,则会通过倒序遍历当前视图的子视图,调用其子视图对应的hitTest:WithEvent:方法,如果某个视图返回了事件响应视图,则该返回的视图被作为事件的响应者,反之则继续遍历判断。如果遍历完后没有任何视图响应此事件,因为此事件点击的范围在当前视图范围内,则将当前视图作为事件响应者返回。

图片.png

二、视图事件响应

上述讲述了视图事件的传递流程,当视图事件传递后,最终事件由谁来响应呢,这就涉及视图的响应链、响应链的机制和流程。 如图,页面存在一个UILabel一个UITextField、一个UIButton,实线箭头表示下一个响应者。

图片.png

视图事件响应链相关的方法

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

例如,当点击View C2的空白处时,事件由谁来响应呢?首先由View C2接收事件,如果它不处理,就会把事件传递给View B2,如果View B2还不响应这个事件,View B2会通过响应链将事件传递给它的父视图View A,如果还不响应,则会沿着响应链一直向上传递,直到传递到UIApplicationDelegate仍然不对事件进行处理,则会忽略此事件

图片.png

SwiftUI 最新数据模型完整解析:@Observable、@State、@Bindable(iOS17+ 全新范式)

作者 汉秋
2025年12月1日 11:22

自 iOS 17 起,SwiftUI 引入了 全新的 Observation 模型

它用三个核心工具彻底重塑了数据管理方式:

  • @Observable —— 定义可观察的状态模型

  • @State —— 持有模型实例,等价于旧时代的 @StateObject

  • @Bindable —— 在视图中实现对 Observable 模型的双向绑定

如果你还在用 ObservableObject、@Published、@StateObject、@ObservedObject、@EnvironmentObject,是时候升级了:新范式更简单、更 Swift、更高性能。

本文将系统梳理 SwiftUI 最新的数据管理体系。


🧱 一、旧数据体系的问题

iOS 16 及以前,我们管理状态基本依赖:

  • ObservableObject

  • @Published

  • @StateObject

  • @ObservedObject

  • @EnvironmentObject

这些机制的问题:

  • 装饰器太多,容易混乱

  • 生命周期容易搞错(尤其是 @StateObject vs @ObservedObject)

  • @Published 对属性执行全局广播,性能不够优雅

  • 环境写法不够类型安全

新模型的目标:让 SwiftUI 更简单、更自动、更智能。


🚀 二、@Observable:新时代核心

新系统中的任何可观察模型,只要声明:

@Observable
class UserModel {
    var name = "HanQiu"
    var age = 23
}

不再需要:

  • ObservableObject

  • @Published

  • 手动发布变更

所有存储属性都是可观察的,SwiftUI 会精确追踪变化来源。


🧩 三、@State取代@StateObject

在旧时代,创建页面级别持久的模型需要:

@StateObject var vm = UserModel()

在新系统中:

@State var vm = UserModel()

是的, @State 自动完成以前 @StateObject 的作用

  • 保持引用类型实例生命周期

  • 在视图重建中保持稳定

  • 触发视图刷新

只要你的模型是 @Observable 的,就可以用 @State 持有。


🧠 四、那@ObservedObject呢?—— 不需要了

旧写法(子视图):

struct ProfileView: View {
    @ObservedObject var vm: UserModel
}

新写法:

struct ProfileView: View {
    var vm: UserModel
}

SwiftUI 会自动观察视图中“被使用的属性”。

你不需要告诉它“这个对象可观察”,它本身就知道(因为模型是 @Observable)。


🌿 五、环境注入方式的升级

旧写法:

@EnvironmentObject var settings: SettingsModel

新写法更强、更明确:

注入

struct AppRoot: View {
    @State var settings = SettingsModel()

    var body: some View {
        MainView()
            .environment(settings)
    }
}

获取

@Environment(SettingsModel.self) var settings

减少误用,也更符合 Swift 语言本身的表达。


⭐ 六、重点:@Bindable的出现解决了什么?

@Observable 模型虽然自动可观察,但 UI 控件(如 TextField)需要 双向绑定

TextField("Name", text: $vm.name)

新模型中,属性只是普通 stored property,不是 Published,不具备 Binding 能力。

于是 Swift 引入:

✔@Bindable

为 View 提供 绑定视角的模型访问


🧲 七、@Bindable的标准用法

模型

@Observable
class UserModel {
    var name = ""
    var age = 18
}

视图(可编辑 UI)

struct EditUserView: View {
    @Bindable var user: UserModel

    var body: some View {
        Form {
            TextField("Name", text: $user.name)
            Stepper("Age: \(user.age)", value: $user.age)
        }
    }
}

只需标记 @Bindable,模型属性即可自动得到 $binding。


🧩 八、为什么不是所有时候都用@Bindable?

是否需要取决于:

情况 是否需要 @Bindable
仅用于展示,不会修改模型 ❌ No
需要用 TextField / Toggle / Stepper 修改模型 ✔ Yes
子视图要修改父模型 ✔ Yes
完全只读视图 ❌ No

越“表单”风格的页面,越需要 @Bindable。


🚦 九、@Bindable的局部绑定写法(推荐技巧)

你也可以只在 body 内使用 Bindable:

var body: some View {
    @Bindable var b = user   // 局部绑定

    VStack {
        TextField("Name", text: $b.name)
        Stepper("Age: \(b.age)", value: $b.age)
    }
}

不会污染结构体属性定义,适合仅局部可编辑的 UI。


🧭 十、三者关系总结(最重要)

@Observable   —— 使模型可观察
@State        —— 在 View 中持有模型(生命周期 = 旧 @StateObject@Bindable     —— 提供绑定能力,允许 UI 修改模型

一个“完整数据流”的表达式:

@Observable 定义状态 → @State 持有 → @Bindable 编辑 → SwiftUI 自动刷新


🧪 十一、完整示例:新 Paradigm 最佳实践

@Observable
class ProfileModel {
    var name = "HanQiu"
    var level = 1
}

struct ProfileView: View {
    @State var profile = ProfileModel()

    var body: some View {
        VStack {
            Text("Name: \(profile.name)")
            Text("Level: \(profile.level)")

            EditSection(profile: profile)
        }
    }
}

struct EditSection: View {
    @Bindable var profile: ProfileModel

    var body: some View {
        VStack {
            TextField("Name", text: $profile.name)
            Stepper("Level: \(profile.level)", value: $profile.level)
        }
        .padding()
    }
}

无需 @Published,不用 @StateObject,不需要 @ObservedObject。

SwiftUI 的数据管理彻底简化。


🧾 十二、迁移指南(旧 → 新)

旧 API 新 API
ObservableObject @Observable
@Published 不需要
@StateObject @State
@ObservedObject 删除,直接传模型
@EnvironmentObject .environment(model) + @Environment(Model.self)
双向绑定属性 使用 @Bindable

🎉 总结

SwiftUI 从 iOS17 开始进入 Observation 时代

  • @Observable → 自动观察

  • @State → 管理模型生命周期

  • @Bindable → 构建表单/编辑 UI 的关键

  • 更少的装饰器

  • 更精准的性能优化

  • 更符合 Swift 语言设计哲学

如果你写 SwiftUI,这套新范式未来几年都会是主流。


❌
❌