自定义鼠标上下事件View Modifier导致SwiftUI视图层级无法更新
问题原因
问题根源在于你自定义的View Modifier如果是基于AppKit/UIKit底层视图实现鼠标事件监听,事件回调的闭包执行上下文没有和SwiftUI的视图更新机制绑定,导致状态变更无法触发视图body的重建。哪怕用@State或ObservableObject,只要回调不在SwiftUI的更新周期内,或者闭包捕获的是状态的旧副本,视图就不会更新。
解决方案
方案一:用SwiftUI原生Gesture实现(推荐)
直接利用SwiftUI的DragGesture(最小距离设为0)来监听鼠标按下和抬起,原生Gesture的回调会自动纳入SwiftUI的更新周期,状态变更能直接触发视图刷新:
struct MousePressModifier: ViewModifier { let onPress: () -> Void let onRelease: () -> Void func body(content: Content) -> some View { content .gesture( DragGesture(minimumDistance: 0) .onChanged { _ in onPress() } .onEnded { _ in onRelease() } ) } } // 使用示例 struct PlayerView: View { @State private var isPlaying = false var body: some View { Circle() .frame(width: 60, height: 60) .foregroundColor(.blue) .overlay { Image(systemName: isPlaying ? "pause.fill" : "play.fill") .foregroundColor(.white) } .modifier(MousePressModifier( onPress: { isPlaying.toggle() }, onRelease: { /* 处理抬起逻辑 */ } )) } }
方案二:修复底层视图实现的Modifier
如果必须用AppKit/UIKit底层视图实现,需要通过Coordinator持有状态绑定,并确保在主线程更新状态:
struct MouseEventModifier: ViewModifier { let onMouseDown: () -> Void let onMouseUp: () -> Void func body(content: Content) -> some View { content .background(MouseEventCaptureView(onDown: onMouseDown, onUp: onMouseUp)) } } fileprivate struct MouseEventCaptureView: NSViewRepresentable { let onDown: () -> Void let onUp: () -> Void func makeNSView(context: Context) -> NSView { let view = NSView() view.wantsLayer = true // 设置跟踪区域,监听鼠标按下/抬起 let trackingArea = NSTrackingArea( rect: .zero, options: [.activeAlways, .mouseDownAndUp, .inVisibleRect], owner: context.coordinator, userInfo: nil ) view.addTrackingArea(trackingArea) return view } func makeCoordinator() -> Coordinator { Coordinator(onDown: onDown, onUp: onUp) } func updateNSView(_ nsView: NSView, context: Context) {} class Coordinator: NSObject { private let onDown: () -> Void private let onUp: () -> Void init(onDown: @escaping () -> Void, onUp: @escaping () -> Void) { self.onDown = onDown self.onUp = onUp } override func mouseDown(with event: NSEvent) { // 确保在主线程执行状态更新 DispatchQueue.main.async { self.onDown() } } override func mouseUp(with event: NSEvent) { DispatchQueue.main.async { self.onUp() } } } } // 使用示例 struct PlayerView: View { @State private var isPlaying = false var body: some View { Circle() .frame(width: 60, height: 60) .foregroundColor(.blue) .overlay { Image(systemName: isPlaying ? "pause.fill" : "play.fill") .foregroundColor(.white) } .modifier(MouseEventModifier( onMouseDown: { isPlaying.toggle() }, onMouseUp: { /* 处理抬起逻辑 */ } )) } }
内容的提问来源于stack exchange,提问作者Marcel Tesch
相关产品推荐
相关产品推荐

