如果鼠标移动太快,SwiftUIonHover不会注册鼠标离开元素
我在 SwiftUI 中制作了一些自定义滑块视图,它们根据悬停状态改变外观,但是如果鼠标移出太快(这实际上是移动光标的非常合理的速度),它会保持悬停状态,直到您重新-悬停并缓慢地重新离开组件。
有解决方案吗?悬停代码非常标准:
struct RulerSlider: View {
@State var hovering = false
var body: some View {
GeometryReader { geometry in
ZStack {
// Ruler lines
if hovering {
Ruler()
}
}
.onHover { hover in
withAnimation(.easeOut(duration: 0.1)) {
self.hovering = hover
}
}
}
}
}
这是问题的样子:
重现bug的示例代码:https :
//gist.github.com/rdev/ea0c53448e12835b29faa11fec8e0388
回答
我今天解决了这个问题,在一个空的 NSView 上有一个跟踪区域。这是在半复杂且快速刷新的网格视图中进行测试的,该视图以前具有与您想象的相同的行为。大约 75 个视图将此修饰符应用于此要点中的GIF 捕获,大多数视图彼此之间的边界为零。
呼叫站点的糖
import SwiftUI
extension View {
func whenHovered(_ mouseIsInside: @escaping (Bool) -> Void) -> some View {
modifier(MouseInsideModifier(mouseIsInside))
}
}
可表示为空跟踪视图
struct MouseInsideModifier: ViewModifier {
let mouseIsInside: (Bool) -> Void
init(_ mouseIsInside: @escaping (Bool) -> Void) {
self.mouseIsInside = mouseIsInside
}
func body(content: Content) -> some View {
content.background(
GeometryReader { proxy in
Representable(mouseIsInside: mouseIsInside,
frame: proxy.frame(in: .global))
}
)
}
private struct Representable: NSViewRepresentable {
let mouseIsInside: (Bool) -> Void
let frame: NSRect
func makeCoordinator() -> Coordinator {
let coordinator = Coordinator()
coordinator.mouseIsInside = mouseIsInside
return coordinator
}
class Coordinator: NSResponder {
var mouseIsInside: ((Bool) -> Void)?
override func mouseEntered(with event: NSEvent) {
mouseIsInside?(true)
}
override func mouseExited(with event: NSEvent) {
mouseIsInside?(false)
}
}
func makeNSView(context: Context) -> NSView {
let view = NSView(frame: frame)
let options: NSTrackingArea.Options = [
.mouseEnteredAndExited,
.inVisibleRect,
.activeInKeyWindow
]
let trackingArea = NSTrackingArea(rect: frame,
options: options,
owner: context.coordinator,
userInfo: nil)
view.addTrackingArea(trackingArea)
return view
}
func updateNSView(_ nsView: NSView, context: Context) {}
static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) {
nsView.trackingAreas.forEach { nsView.removeTrackingArea($0) }
}
}
}
- Great! Works perfectly