SwiftUI动态检测子视图位置变化:判断矩形何时越过lineView
无需绝对位置,实时检测SwiftUI视图交叉的方案
要实现不依赖绝对位置、实时检测移动矩形越过lineView的需求,可以通过PreferenceKey传递视图几何信息结合GeometryReader来实现,核心思路是获取两个视图在同一坐标系下的Frame并实时对比。
实现步骤
- 定义两个
PreferenceKey,分别用于传递矩形和lineView的Frame信息 - 给目标视图添加背景用的GeometryReader,将自身Frame通过PreferenceKey传递出去
- 在父视图中监听Frame变化,实时对比两个视图的位置关系,判断是否越过
修改后的完整代码
import SwiftUI // 用于传递lineView的Frame struct LineFrameKey: PreferenceKey { static var defaultValue: CGRect = .zero static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() } } // 用于传递移动矩形的Frame struct BlockFrameKey: PreferenceKey { static var defaultValue: CGRect = .zero static func reduce(value: inout CGRect, nextValue: () -> CGRect) { value = nextValue() } } struct ColorView: View { private let blockWidth = 40.0 private let blockHeight = 40.0 @State private var blockYPos = 0.0 private let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() // 存储两个视图的Frame,用于实时对比 @State private var lineFrame: CGRect = .zero @State private var blockFrame: CGRect = .zero // 标记是否已经越过lineView,避免重复触发逻辑 @State private var hasCrossedLine = false var body: some View { GeometryReader { geometry in ZStack { // 移动的矩形:添加GeometryReader传递自身Frame Rectangle() .frame(width: blockWidth, height: blockHeight) .position(x: geometry.size.width/2, y: blockYPos) .background( GeometryReader { geo in Color.clear // 获取相对于ZStack的本地坐标Frame .preference(key: BlockFrameKey.self, value: geo.frame(in: .local)) } ) VStack { HStack { scribble Spacer() close } .font(.title) .frame(height: 40) palleteView // lineView:添加GeometryReader传递自身Frame lineView Spacer() } } .background { Color.yellow.ignoresSafeArea() } .onAppear { blockYPos = geometry.size.height - blockHeight } .onReceive(timer) { _ in // 未越过时才继续移动 if !hasCrossedLine { blockYPos -= 5 } } // 监听lineView的Frame变化 .onPreferenceChange(LineFrameKey.self) { newFrame in lineFrame = newFrame } // 监听矩形Frame变化,实时判断是否越过 .onPreferenceChange(BlockFrameKey.self) { newFrame in blockFrame = newFrame // 逻辑说明:矩形向上移动,当矩形的顶部(minY)小于等于lineView的底部(maxY)时,视为越过 // 可根据实际需求调整判断条件(比如判断矩形底部与line顶部的关系) let blockTop = blockFrame.minY let lineBottom = lineFrame.maxY if blockTop <= lineBottom && !hasCrossedLine { hasCrossedLine = true print("矩形已越过lineView") // 这里可以添加越过触发后的逻辑,比如停止定时器 timer.upstream.connect().cancel() } } } } private var lineView: some View { Capsule() .frame(height: 2) .foregroundStyle(Color.red) .padding(.horizontal) .background( GeometryReader { geo in Color.clear // 获取相对于ZStack的本地坐标Frame .preference(key: LineFrameKey.self, value: geo.frame(in: .local)) } ) } private var close: some View { Image(systemName: "xmark.circle.fill") .padding(.trailing, 8) } private var scribble: some View { Image(systemName: "scribble.variable") .padding(.leading, 8) } private var palleteView: some View { HStack(alignment: .center) { Color.green.frame(width: blockWidth, height: blockHeight) Color.blue.frame(width: blockWidth, height: blockHeight) Color.orange.frame(width: blockWidth, height: blockHeight) Color.indigo.frame(width: blockWidth, height: blockHeight) Color.mint.frame(width: blockWidth, height: blockHeight) } } }
关键说明
- 使用
.local坐标系:确保两个视图的Frame基于同一个父容器(ZStack)计算,避免全局坐标系带来的安全区、屏幕尺寸等干扰 - PreferenceKey的作用:在SwiftUI视图层级中安全传递几何数据,无需硬编码绝对位置
- 状态标记
hasCrossedLine:防止重复触发越过逻辑,可根据需求调整重置逻辑
内容的提问来源于stack exchange,提问作者batman
相关产品推荐
相关产品推荐

