SwiftUI onHover无法修改鼠标样式的问题
SwiftUI onHover无法修改鼠标样式的问题

SwiftUI onHover无法修改鼠标样式的问题

问题描述

在使用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/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注