问题描述
在使用onHover修改鼠标光标样式时,发现有一个视图无法实现鼠标光标样式的修改。
正常的修改鼠标样式视图代码:
ZStack {
Rectangle()
.frame(width: 240,height: 160)
.foregroundColor(isHovering ? Color(hex: "BEE2FF") : Color(hex:"E6E6E6"))
.shadow(color: .gray.opacity(0.6), radius: 2, x: 0, y: 4)
Image("upload")
.resizable()
.scaledToFit()
.frame(width: 150)
}
.onHover(perform: { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
})

无法修改鼠标样式视图的代码:
Image(nsImage: item.image)
.resizable()
.scaledToFit()
.frame(width: 35, height: 35)
// 悬停显示放大按钮
.overlay {
if hoveringIndex == index {
ZStack {
Color.gray.opacity(0.3)
Image(systemName:"plus.magnifyingglass")
.foregroundColor(.white)
}
}
}
.onTapGesture {
// 使用 Quick 预览图片
if let url = saveImageToTempFile(image: item.image) {
previewImage(at: url)
}
}
.onHover { isHovering in
// 当鼠标进入视图区域时 isHovering = true
// 当鼠标离开视图区域时 isHovering = false
hoveringIndex = isHovering ? index : nil
}
.onHover { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
}
.cornerRadius(4)

鼠标并没有变成手指样式。
问题原因
经过排查发现,当隐藏overlay代码后,鼠标恢复设置的样式。

因此,怀疑是鼠标悬浮显示overlay,导致鼠标样式被overlay的视图影响。
尝试将鼠标样式代码放入overlay中:
.overlay {
if hoveringIndex == index {
ZStack {
Color.gray.opacity(0.3)
Image(systemName:"plus.magnifyingglass")
.foregroundColor(.white)
}
.onHover { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
}
}
}
但是,测试发现这样也无法实现鼠标样式的修改。
经过漫长的测试,发现两个场景:
1、隐藏overlay中的Color和Image时,鼠标样式可以正常修改。当Color和Image显示时,鼠标样式就变成普通的箭头样式。
.overlay {
if hoveringIndex == index {
ZStack {
// Color.gray.opacity(0.3)
// Image(systemName:"plus.magnifyingglass")
// .foregroundColor(.white)
}
.onHover { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
}
}
}

2、不使用if hoveringIndex == index判断时,鼠标样式正常显示。
.overlay {
ZStack {
Color.gray.opacity(0.3)
Image(systemName:"plus.magnifyingglass")
.foregroundColor(.white)
}
.onHover { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
}
// if hoveringIndex == index {} 不使用 if 语句,正常显示
}

因此,推断鼠标样式问题的核心原因在于,视图结构在 hoveringIndex 改变时被 重建,导致 NSCursor.set() 或 .push() 被打断。
因为SwiftUI是声明式UI,在控制视图显示/隐藏时:
if hoveringIndex == index { ... }
// 或
.opacity(hoveringIndex == index ? 1 : 0)
这会导致 SwiftUI 认为切换了视图的状态,因此重建(rebuild)或重绘视图树,同时重置了 onHover 中设置的鼠标样式。
尤其是 .opacity() 虽然视觉上只是透明,但它在 SwiftUI 中仍然可能触发 view lifecycle 改变(尤其配合条件判断),会让 onHover 行为中断。
场景复现
1、当鼠标移入时,onHover修改鼠标样式为手指样式。
2、if hoveringIndex == index 为 true,视图结构发生变化(新 overlay 出现)。
3、SwiftUI 重建了视图(包括 layout、render、onHover 逻辑)。
4、原来 onHover { NSCursor.push() } 的状态被刷新,系统恢复了默认箭头样式。
解决方案
前面使用的 .opacity 实际上会触发视图动画/状态变动。
因此应该使用 .zIndex控制显示顺序,而非if判断。
ZStack {
Image(nsImage: item.image)
.resizable()
.scaledToFill()
.frame(width: 35, height: 35)
Group {
Color.gray.opacity(0.3)
Image(systemName:"plus.magnifyingglass")
.foregroundColor(.white)
.allowsHitTesting(false)
}
.frame(width: 35, height: 35)
.zIndex(hoveringIndex == index ? 1 : -1)
}
.onHover { isHovering in
if isHovering {
print("进入悬浮状态 ")
NSCursor.pointingHand.set()
} else {
print("退出悬浮状态")
NSCursor.arrow.set()
}
}
因为 .zIndex 是纯视觉层叠属性,不会触发视图重建。
总结
onHover无法修改鼠标样式的主要问题原因在于,当鼠标悬浮时,会显示overlay的视图,因为overlay的视图显示/隐藏是通过 if 判断或者 .opacity 透明度实现的。
这两个实现方法都会导致视图重建,当视图重建时,鼠标的状态也就会刷新,因此就导致鼠标样式无法被修改。
可以通过 zIndex 层叠属性实现视图的显示/隐藏。
注意,不要尝试使用hidden,否则会导致卡死的情况,该BUG请见《SwiftUI hidden导致编译器卡死问题》。
另外,对于复杂的多层级嵌套,推荐三元运算符替代if语句判断:
// 三元运算符写法,推荐
.onHover { isHovering in
isHovering ? NSCursor.pointingHand.set() : NSCursor.arrow.set()
}
// if语句写法
.onHover { isHovering in
if isHovering {
NSCursor.pointingHand.set()
} else {
NSCursor.arrow.set()
}
}
相关文章
1、SwiftUI修改鼠标光标/指针样式:https://fangjunyu.com/2025/07/09/swiftui%e4%bf%ae%e6%94%b9%e9%bc%a0%e6%a0%87%e5%85%89%e6%a0%87-%e6%8c%87%e9%92%88%e6%a0%b7%e5%bc%8f/
2、SwiftUI鼠标悬停onHover:https://fangjunyu.com/2025/07/09/swiftui%e9%bc%a0%e6%a0%87%e6%82%ac%e5%81%9conhover/
3、SwiftUI hidden导致编译器卡死问题:https://fangjunyu.com/2025/07/09/swiftui-hidden%e5%af%bc%e8%87%b4%e7%bc%96%e8%af%91%e5%99%a8%e5%8d%a1%e6%ad%bb%e9%97%ae%e9%a2%98/