SwiftUI:修复自定义径向布局中重叠视图的.onHover问题
我正在为macOS开发一款应用,其中包含一个由数量可变的梯形组成的径向布局视图。每个梯形都封装成独立的TrapezoidView(附带专属信息与交互逻辑),通过ForEach循环创建,并使用自定义旋转修饰符.rotated()进行旋转——这个修饰符会重新计算视图的边界框,使其贴合旋转后视图的实际绘制位置。核心代码大致如下:
@State var displayList = [itemModel] RadialLayout { ForEach(0 ..< displayList.count, id: \.self) { i in TrapezoidView(/* ... */) .rotated((360/appList.count)*i) .onHover { isHovered in // 处理hover的逻辑 } } }
目前遇到的问题是:.onHover修饰符使用的边界框远大于梯形本身,大量项目展示时边界框会重叠,导致hover行为异常。
想请教:
- SwiftUI中视图的边界框是否必须是与坐标轴平行的矩形,还是可以使用旋转后的形状?
- 如果不能用旋转后的形状,该如何解决hover异常的问题?我曾考虑过像
.contentShape()处理触摸手势那样,让SwiftUI仅基于梯形形状触发.onHover,但没找到相关方法。另外,是否应该直接把TrapezoidView向中心外移来避免重叠?但这样会牺牲可用性。
关于边界框的本质
SwiftUI中视图的命中测试边界框默认是与坐标轴平行的轴对齐矩形——哪怕你用旋转修饰符改变了视图的视觉位置,底层的命中区域还是旋转后视图的轴对齐包围盒,没法直接用旋转后的不规则形状作为命中区域。
解决hover异常的具体方法
你提到的.contentShape()其实完全可行,关键是要注意使用顺序和参数:
给TrapezoidView绑定精确的形状命中区域
先把你绘制梯形的逻辑封装成一个Shape结构体,然后将其作为.contentShape()的参数,并且确保这个修饰符放在旋转修饰符之前,这样形状会跟着视图一起旋转,命中区域就能和视觉上的梯形完全匹配:// 封装梯形形状 struct TrapezoidShape: Shape { func path(in rect: CGRect) -> Path { var path = Path() // 这里实现你绘制梯形的具体Path逻辑 path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) path.addLine(to: CGPoint(x: rect.minX + 20, y: rect.minY)) path.addLine(to: CGPoint(x: rect.maxX - 20, y: rect.minY)) path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) path.closeSubpath() return path } } // 使用时的顺序很重要 TrapezoidView(/* ... */) .contentShape(TrapezoidShape()) // 先绑定形状 .rotated((360/appList.count)*i) // 再执行旋转 .onHover { isHovered in // 处理hover逻辑 }验证命中区域(可选)
如果你不确定命中区域是否正确,可以临时给contentShape添加边框,比如.contentShape(TrapezoidShape()).border(Color.red),或者用Xcode的「Debug View Hierarchy」查看视图的命中测试区域。不推荐外移视图的方案
把视图向中心外移确实能避免重叠,但会浪费界面空间,降低可用性,属于治标不治本的方法,优先用contentShape的方案解决。
内容的提问来源于stack exchange,提问作者undecimal

