不同平台不同框架
UIApplicationDelegate
协议)func application(_:didFinishLaunchingWithOptions:) -> Bool {
// 1. 创建 UIWindow
window = UIWindow(frame: UIScreen.main.bounds)
// 2. 设置根视图控制器
window?.rootViewController = UINavigationController(rootViewController: MainVC())
// 3. 激活窗口
window?.makeKeyAndVisible()
return true
}
“AppDelegate 应仅处理应用级基础设施,业务逻辑应交给视图控制器”
AppDelegate and the App Life Cycle
UIView
-> UIResponder
)rootViewController
:设置应用内容入口(必须项)makeKeyAndVisible()
:激活窗口并显示UIWindowScene
// 窗口释放时自动销毁视图树
window = nil // 释放所有子视图和控制器
“UIWindow 是应用内容的基础容器,但不直接参与内容绘制”
Managing UIWindow
pushViewController(_:animated:) // 压入新控制器
popViewController(animated:) // 弹出当前控制器
topViewController
访问当前控制器viewControllers
数组管理整个栈loadView() // 创建视图(禁止直接调用)
viewDidLoad() // 视图加载完成(一次性初始化)
viewWillAppear() // 视图即将显示(数据刷新)
viewDidLayoutSubviews() // 布局完成
viewDidDisappear() // 视图消失(资源释放)
view
属性:延迟加载(首次访问时调用 loadView()
)didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
if !isViewLoaded { // 视图未显示时释放资源
cleanUpResources()
}
}
UIResponder
,可处理触摸事件addSubview(_:) // 添加子视图
removeFromSuperview() // 移除视图
// 声明约束(官方推荐)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: superview.topAnchor, constant: 20)
])
draw(_ rect: CGRect) // 自定义绘制(避免频繁调用)
setNeedsLayout() // 标记需要重新布局
setNeedsDisplay() // 标记需要重绘
控制反转(IoC):
viewDidLoad
)响应式组合:
// 典型组合示例
let tabBarVC = UITabBarController()
let navVC = UINavigationController(rootViewController: FeedVC())
tabBarVC.addChild(navVC) // 组合导航控制器到标签栏
显式依赖传递:
生命周期协同:
UIWindow
隐藏时:
viewWillDisappear()
→ viewDidDisappear()
组件 | 释放规则 | 典型错误 |
---|---|---|
UIViewController |
从导航栈弹出时自动释放 | 循环引用(闭包/委托) |
UIView |
从父视图移除时引用计数-1 | 强引用子视图数组 |
UIWindow |
AppDelegate 释放时自动销毁 | 全局强引用未置 nil |
单一职责:
UIView
只负责显示和布局UIViewController
只管理界面逻辑约定优于配置:
supportedInterfaceOrientations
)性能优先:
view
属性)UITableViewCell
reuseIdentifier)安全访问:
DispatchQueue.main.async
)navigationController?.popViewController()
)“UIKit 的核心目标是提供高性能的界面基础设施,同时保持开发者的控制权”
– UIKit Fundamentals
通过这种分层设计,UIKit 在灵活性和性能之间取得了平衡,成为 iOS 生态的基石框架。
基于 Apple 官方文档:SwiftUI Documentation, 看左边的结构层次
SwiftUI 的设计建立在以下根本性原则上:
官方文档强调:“SwiftUI helps you build great-looking apps across all Apple platforms with the power of Swift — and as little code as possible.”
SwiftUI Overview
// 命令式 (UIKit)
button.frame = CGRect(x: 20, y: 20, width: 100, height: 44)
button.setTitle("Submit", for: .normal)
view.addSubview(button)
// 声明式 (SwiftUI)
Button("Submit") {
submitAction()
}
.frame(width: 100, height: 44)
.padding(20)
核心差异:
数据驱动原则:
struct ProfileView: View {
var body: some View {
VStack {
ProfileHeader() // 子组件
StatsView() // 子组件
PostFeed() // 子组件
}
}
}
组合优势:
组件 | 功能 | 示例 |
---|---|---|
Text |
文本显示 | Text("Hello") |
Image |
图像显示 | Image("logo") |
Button |
交互按钮 | Button(action: {}) { Text("OK") } |
TextField |
文本输入 | TextField("Name", text: $name) |
容器 | 布局方向 | 示例 |
---|---|---|
VStack |
垂直排列 | VStack { ... } |
HStack |
水平排列 | HStack { ... } |
ZStack |
重叠布局 | ZStack { ... } |
List |
可滚动列表 | List(items) { item in ... } |
Grid |
网格布局 | LazyVGrid(columns: [...]) { ... } |
属性包装器 | 用途 | 生命周期 |
---|---|---|
@State |
视图私有状态 | 视图生命周期 |
@Binding |
双向数据绑定 | 父视图传递 |
@ObservedObject |
外部可观察对象 | 手动管理 |
@StateObject |
视图持有的可观察对象 | 视图生命周期 |
@EnvironmentObject |
全局共享数据 | 应用生命周期 |
根据 Apple 官方文档,SwiftUI 采用革命性的声明式 UI 范式,其核心设计哲学围绕以下几个基本原则:
### 核心设计哲学
1. **声明式语法**
"Describe what your UI should do, not how it should do it"
- 告别命令式布局代码(frame/constraints)
- 状态变化时自动更新视图
```swift
// 声明式计数器示例
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
单一数据源 (Single Source of Truth)
@State
, @ObservedObject
, @EnvironmentObject
确保数据一致性组合优于继承
struct CardView: View {
var body: some View {
RoundedRectangle(cornerRadius: 12)
.fill(.background)
.overlay(Text("Content"))
}
}
跨平台一致性
@main struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
WindowGroup
:多窗口应用DocumentGroup
:文档型应用ImmersiveSpace
:visionOS 沉浸式空间机制 | 使用场景 | 示例 |
---|---|---|
@State |
视图私有状态 | 按钮点击状态 |
@Binding |
父子视图双向绑定 | 开关控件状态同步 |
@ObservedObject |
外部可观察对象 | ViewModel 数据管理 |
@Environment |
系统级全局设置 | 深色模式/字体大小 |
@AppStorage |
UserDefaults 持久化 | 用户偏好设置 |
Core Data |
复杂数据模型持久化 | 数据库管理 |
基础架构:
View
├── Primitive Views (Text, Image)
├── Layout Containers (HStack/VStack)
├── Control Views (Button, Toggle)
└── Styleable Views (List, Form)
关键特性:
Text("Hello")
.font(.title)
.foregroundStyle(.blue)
.padding()
Button("Submit") { ... }
.buttonStyle(.borderedProminent)
布局类型 | 特点 | 示例 |
---|---|---|
自动布局 | 基于约束的响应式布局 | padding() , frame() |
堆栈布局 | HStack/VStack/ZStack | 线性排列元素 |
网格布局 | LazyVGrid/LazyHGrid | 瀑布流展示 |
自定义布局 | Layout 协议 | 实现特殊排列逻辑 |
列表和表格 | List/Table | 数据集合展示 |
布局示例:
VStack(alignment: .leading) {
Text("Title").font(.headline)
LazyVGrid(columns: [GridItem(), GridItem()]) {
ForEach(items) { item in
ItemView(item)
}
}
}
.scrollContentBackground(.hidden) // iOS 16+
实际应用:
// 手势组合
Rectangle()
.gesture(
TapGesture()
.onEnded { /* 点击 */ }
)
.simultaneousGesture(
DragGesture()
.onChanged { /* 拖拽 */ }
)
// 键盘事件
TextField("Search", text: $query)
.onSubmit {
search()
}
Image(decorative: "ornament")
.accessibilityHidden(true)
Button("Submit") { ... }
.accessibilityLabel("提交订单")
Text("Content")
.font(.system(size: 16))
.dynamicTypeSize(...DynamicTypeSize.xxxLarge)
VStack {
Text("Total")
Text("$100")
}
.accessibilityElement(children: .combine)
混合开发模式:
let vc = UIHostingController(rootView: SwiftUIView())
present(vc, animated: true)
struct MapView: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView()
}
func updateUIView(_ uiView: MKMapView, context: Context) {}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 15 Pro")
.preferredColorScheme(.dark)
}
}
@LibraryContentProvider
struct CustomViews {
@LibraryItemBuilder
var views: [LibraryItem] {
LibraryItem(CardView(), title: "Custom Card")
}
}
debugPerformance
环境变量struct ProductListView: View {
@StateObject var viewModel = ProductViewModel()
var body: some View {
NavigationStack {
List {
ForEach(viewModel.products) { product in
NavigationLink(value: product) {
ProductRow(product: product)
}
}
}
.navigationTitle("Products")
.searchable(text: $viewModel.searchText)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(action: viewModel.showFilters) {
Label("Filter", systemImage: "slider.horizontal.3")
}
}
}
.refreshable {
await viewModel.loadProducts()
}
}
.navigationDestination(for: Product.self) { product in
ProductDetailView(product: product)
}
.sheet(isPresented: $viewModel.showingFilters) {
FilterView(filters: $viewModel.filters)
}
}
}
struct HealthDashboard: View {
@Environment(\.healthStore) var healthStore
@State private var metrics: [HealthMetric] = []
var body: some View {
ScrollView {
VStack(spacing: 20) {
// 1. 关键指标卡片
HealthSummaryCard(metrics: metrics)
// 2. 趋势图表
HealthTrendChart(data: metrics)
.frame(height: 300)
// 3. 活动环
ActivityRingsView()
.frame(width: 200, height: 200)
}
.padding()
}
.task {
metrics = await healthStore.loadTodayMetrics()
}
// 深色模式适配
.background(Color(.systemGroupedBackground))
}
}
struct ImmersiveGallery: View {
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@State private var selectedArtwork: Artwork?
var body: some View {
VStack {
Text("Art Gallery")
.font(.extraLargeTitle)
ScrollView(.horizontal) {
HStack {
ForEach(Artwork.samples) { artwork in
ArtworkThumbnail(artwork: artwork)
.onTapGesture { selectedArtwork = artwork }
}
}
}
}
.onChange(of: selectedArtwork) { _, newValue in
if let artwork = newValue {
openImmersiveSpace(id: "gallerySpace") {
ImmersiveArtView(artwork: artwork)
}
}
}
}
}
声明式图形 API:
// iOS 17+ 声明式动画
Circle()
.phaseAnimator([0, 1]) { content, phase in
content.offset(y: phase * 20)
}
跨平台组件统一:
// 统一 macOS/iOS 组件
#if os(macOS)
.controlSize(.large)
#else
.controlSize(.regular)
#endif
性能优化提升:
AI 集成支持:
// 未来可能的 AI 集成
Image("landscape")
.generativeFill(using: "mountain range at sunset")
SwiftUI 通过声明式语法、响应式数据流和统一跨平台架构,实现了 Apple 的"一次编写,随处运行"愿景。随着每年更新,它正逐步成为 Apple 生态系统的首选 UI 框架,同时保持与 UIKit/AppKit 的无缝互操作性。
跨平台策略:
.toolbar()
(iOS), .menuBar()
(macOS)UIViewControllerRepresentable
struct PostView: View {
@StateObject var viewModel: PostViewModel
var body: some View {
VStack(alignment: .leading) {
PostHeader(user: viewModel.post.user)
AsyncImage(url: viewModel.post.imageURL) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.aspectRatio(contentMode: .fit)
PostActions(
isLiked: $viewModel.isLiked,
likeCount: viewModel.likeCount
)
}
}
}
struct PostActions: View {
@Binding var isLiked: Bool
let likeCount: Int
var body: some View {
HStack {
Button(action: toggleLike) {
Image(systemName: isLiked ? "heart.fill" : "heart")
.foregroundColor(isLiked ? .red : .primary)
}
Text("\(likeCount) likes")
.font(.caption)
}
}
func toggleLike() {
isLiked.toggle()
}
}
graph LR
A[ProductDB] --> B(CartViewModel)
B -->|@Published| C[CartView]
C -->|@Binding| D[CartItemView]
D -->|action| B
class CartViewModel: ObservableObject {
@Published var items: [CartItem] = []
func addItem(_ product: Product) {
// 业务逻辑...
}
}
struct CartView: View {
@StateObject var viewModel = CartViewModel()
var body: some View {
List {
ForEach(viewModel.items) { item in
CartItemView(item: item)
}
CheckoutSection(total: viewModel.total)
}
.environmentObject(viewModel)
}
}
struct CartItemView: View {
@EnvironmentObject var cart: CartViewModel
let item: CartItem
var body: some View {
HStack {
Text(item.product.name)
Spacer()
Button("Remove") {
cart.removeItem(item)
}
}
}
}
struct HealthDashboard: View {
@State private var steps: Int = 0
@State private var heartRate: Int = 72
@State private var animatedValues = false
var body: some View {
VStack {
AnimatedRing(value: CGFloat(steps)/10000,
label: "Steps: \(steps)")
.onAppear {
withAnimation(.easeInOut(duration: 1.5)) {
steps = 8560
animatedValues = true
}
}
BPMIndicator(bpm: heartRate)
}
}
}
struct AnimatedRing: View {
var value: CGFloat
var label: String
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20)
.opacity(0.3)
Circle()
.trim(from: 0, to: value)
.stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round))
.rotationEffect(.degrees(-90))
.animation(.spring(), value: value)
Text(label)
}
.padding(40)
}
}
@Environment(\.colorScheme) var colorScheme
@Environment(\.sizeCategory) var sizeCategory
根据官方文档和 WWDC 更新:
###一、事件传递本质区别:流程方向与目标
维度 | 事件传递(Event Delivery) | 事件响应(Event Handling) |
---|---|---|
核心定义 | 确定“事件该由哪个视图接收”的过程(从外向内寻找目标) | 确定“事件如何被处理”的过程(从内向外寻找处理者) |
流程方向 | 从顶层容器(UIApplication →UIWindow )向下到具体视图 |
从具体视图向上到顶层容器(视图→父视图→…→UIApplication ) |
核心目标 | 找到事件的“最佳响应者”(初始接收者) | 找到能处理事件的响应者(可能是初始响应者或其上级) |
关键作用 | 定位事件发生的具体视图,为后续处理做准备 | 执行事件对应的业务逻辑(如点击按钮、滑动视图) |
UIApplication
接收事件 → 传递给UIWindow
。UIWindow
通过hitTest(_:with:)
从根视图开始递归遍历子视图,检查每个视图是否可接收事件(需满足isUserInteractionEnabled=true
、isHidden=false
、alpha>0.01
)。hitTest(_:with:)
:核心入口,决定事件传递的终点。point(inside:with:)
:判断触摸点是否在视图范围内,供hitTest
调用。touchBegan
等方法)。action
(如按钮点击)、重写触摸方法、使用手势识别器等。touchesBegan/Ended/Moved(_:with:)
。motionBegan/Ended(_:with:)
。UIGestureRecognizer
的回调。action
方法;若未设置action
,事件可能传递给父视图,由父视图处理点击逻辑。hitTest(_:with:)
:返回nil
可阻止事件传递给当前视图的子视图;返回特定视图可强制事件传递给该视图。point(inside:with:)
:返回false
可让当前视图“忽略”触摸点,事件传递给父视图。hitTest
使其返回父视图,实现“点击按钮区域实际触发父视图事件”。super
:阻止事件向上传递(如子视图处理事件后,父视图无法接收)。UIResponder
的next
属性手动传递事件:next?.touchesBegan(...)
。touchesBegan
,实现“点击任何子视图都触发控制器的逻辑”。事件传递层次
以下是在实际开发中如何准确搭配使用事件传递相关方法的详细解析,结合具体案例说明:
hitTest(_:with:)
+ point(inside:with:)
协作关系:
flowchart TD
A[hitTest 调用] --> B[point(inside:判断点是否在视图内]
B -- 是 --> C[遍历子视图递归调用 hitTest]
B -- 否 --> D[返回 nil]
C --> E[返回最顶层符合条件的视图]
实际案例:自定义可穿透视图
class TransparentButton: UIButton {
// 扩大点击区域
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// 将点击区域扩大20点
let expandedBounds = bounds.insetBy(dx: -20, dy: -20)
return expandedBounds.contains(point)
}
// 允许事件穿透到特定视图
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let hitView = super.hitTest(point, with: event) else { return nil }
// 如果点击的是标记为穿透的视图
if hitView.accessibilityIdentifier == "passthroughView" {
return nil // 允许事件穿透
}
return hitView
}
}
协作优先级:
实际案例:绘图板应用
class DrawingCanvas: UIView {
// 1. 原始触摸处理 - 用于精确绘制
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else { return }
startNewPath(at: point)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self) else { return }
addPointToPath(at: point)
}
// 2. 手势识别器 - 处理缩放/旋转等高级手势
func setupGestures() {
// 双指缩放
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch))
addGestureRecognizer(pinch)
// 长按调色板
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(showColorPicker))
longPress.minimumPressDuration = 0.5
addGestureRecognizer(longPress)
}
// 解决手势冲突:优先处理绘图
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
// 当正在绘画时禁用缩放
if isDrawing && gestureRecognizer is UIPinchGestureRecognizer {
return false
}
return true
}
}
class VideoPlayerView: UIView {
// MARK: - 事件传递配置
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 优先检测控制按钮
for control in controls {
let controlPoint = convert(point, to: control)
if control.point(inside: controlPoint, with: event) {
return control.hitTest(controlPoint, with: event)
}
}
// 其他区域传递到视频层
return videoLayerView.hitTest(convert(point, to: videoLayerView), with: event)
}
// MARK: - 手势识别
func setupGestures() {
// 单击显示/隐藏控制栏
let tap = UITapGestureRecognizer(target: self, action: #selector(toggleControls))
addGestureRecognizer(tap)
// 双击全屏
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(toggleFullscreen))
doubleTap.numberOfTapsRequired = 2
addGestureRecognizer(doubleTap)
// 解决单击双击冲突
tap.require(toFail: doubleTap)
// 滑动手势控制进度/音量
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
pan.delegate = self
addGestureRecognizer(pan)
}
// MARK: - 原始事件处理(设备运动)
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
takeScreenshot() // 摇动手机截图
}
}
}
class GameViewController: UIViewController {
// MARK: - 触摸事件处理
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
// 虚拟摇杆区域
if joystickView.frame.contains(touch.location(in: view)) {
joystickView.touchesBegan(touches, with: event)
return
}
// 技能按钮区域
for skillButton in skillButtons {
if skillButton.frame.contains(touch.location(in: view)) {
skillButton.touchesBegan(touches, with: event)
return
}
}
// 其他区域触发普通攻击
player.attack()
}
// MARK: - 手势识别
func setupGestures() {
// 三指下滑保存游戏
let swipeDown = UISwipeGestureRecognizer(target: self, action: #selector(saveGame))
swipeDown.direction = .down
swipeDown.numberOfTouchesRequired = 3
view.addGestureRecognizer(swipeDown)
// 捏合手势调整镜头
let pinch = UIPinchGestureRecognizer(target: self, action: #selector(adjustCameraZoom))
view.addGestureRecognizer(pinch)
}
// MARK: - 设备运动事件
override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
player.useSpecialAbility() // 摇动手机释放大招
}
}
// MARK: - 响应链事件传递
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
guard let key = presses.first?.key else { return }
switch key.keyCode {
case .keyboardEscape:
showPauseMenu()
case .keyboardSpace:
player.jump()
default:
super.pressesBegan(presses, with: event)
}
}
}
优先使用手势识别器:
UIGestureRecognizer
实现谨慎扩展 hitTest:
事件传递层次:
hitTest(_:with:)
与 point(inside:with:)
的实际开发应用解析在 iOS 事件传递机制中,hitTest(_:with:)
和 point(inside:with:)
是确定事件目标视图的核心方法。以下结合实际开发场景,通过代码示例演示其准确使用方式及底层逻辑。
hitTest(_:with:)
:事件传递的“决策枢纽”
UIWindow
接收到事件时,会先调用根视图的 hitTest
方法,逐层向下寻找目标视图。point(inside:with:)
:视图命中的“基础检测器”
hitTest
的核心判断依据。hitTest
会先调用 point(inside:with:)
确认当前视图是否包含触摸点,再决定是否向子视图传递事件。// 简化版 hitTest 底层逻辑(实际由系统调用)
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. 检查当前视图是否可接收事件
if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
return nil
}
// 2. 检查触摸点是否在当前视图范围内
if !point(inside: point, with: event) {
return nil
}
// 3. 从后往前遍历子视图(确保先检查上层子视图)
for subview in subviews.reversed() {
// 转换触摸点到子视图坐标系
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView // 子视图命中,返回子视图
}
}
// 4. 没有子视图命中,当前视图自身为最佳响应者
return self
}
关键逻辑:系统从根视图开始,先判断自身是否可接收事件,再递归检查子视图(从后往前,确保上层视图优先),最终返回最内层的命中视图。
// 需求:让某个视图不接收触摸事件,点击时穿透到下层视图
class TransparentView: UIView {
// 重写 hitTest 直接返回 nil,阻止事件传递到当前视图
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return nil // 不处理事件,传递给父视图
}
}
// 使用方式
let transparentView = TransparentView()
parentView.addSubview(transparentView)
// 点击 transparentView 区域时,事件会穿透到其下层视图
// 需求:在父视图中,无论触摸点在哪里,事件都传递给特定子视图
class CustomParentView: UIView {
let targetSubview: UIView // 目标子视图
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 直接返回目标子视图,忽略实际触摸位置
return targetSubview
}
}
// 需求:让圆形按钮的点击区域为圆形(而非矩形 bounds)
class CircleButton: UIButton {
// 重写 point(inside:with:) 实现圆形命中检测
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// 计算触摸点到圆心的距离
let center = bounds.center
let radius = min(bounds.width, bounds.height) / 2
let distance = sqrt(
pow(point.x - center.x, 2) +
pow(point.y - center.y, 2)
)
return distance <= radius // 距离小于半径时命中
}
}
效果:即使点击矩形按钮的角落,只要在圆形范围内就会触发点击事件,提升用户体验。
// 在视图中重写 hitTest 和 point(inside:),打印调用日志
class DebugView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print("hitTest called on \(self) at point \(point)")
let result = super.hitTest(point, with: event)
print("hitTest result: \(result ?? self)")
return result
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let result = super.point(inside: point, with: event)
print("point(inside:) on \(self) returns \(result)")
return result
}
}
通过日志可观察事件传递路径,例如:
hitTest called on at point (150.0, 200.0)
point(inside:) on returns true
hitTest called on at point (150.0, 160.0)
point(inside:) on returns true
hitTest called on at point (50.0, 60.0)
point(inside:) on returns true
hitTest result:
hitTest
时必须调用正确的 super
方法,或在合适时机返回视图,否则会导致崩溃。hitTest
重写(如大量数学计算)可能影响滑动流畅度,需尽量简化逻辑。hitTest
递归会产生额外开销,应避免不必要的视图嵌套。在 SwiftUI 中,事件传递逻辑被框架封装,开发者无需直接操作 hitTest
,而是通过 ZStack
、overlay
等布局控件控制视图层级优先级。例如:
// SwiftUI 中通过 ZStack 控制事件优先级(上层视图优先接收事件)
ZStack {
Button("下层按钮") { print("下层点击") }
Button("上层按钮") { print("上层点击") } // 优先接收事件
}
底层仍依赖 hitTest
机制,但 SwiftUI 自动处理了视图层级与命中检测,降低了开发复杂度。
hitTest
和 point(inside:)
是 iOS 事件传递的底层核心,理解其工作原理后,可在 UIKit 开发中精准控制事件目标,解决视图穿透、自定义点击区域等复杂需求。在实际使用中,应优先考虑系统默认行为,仅在必要时重写这些方法,以保持代码的简洁性和性能。