xcode - SwiftUI:如何在水平滚动视图中监听鼠标滚轮滚动事件
问题描述
我正在开发一个 macOS 软件,我发现在 swiftui 中,scrollview 设置为.horizontal
,所以当鼠标滚轮滚动时列表不会滚动,但它会在.vertical
模式下滚动。
但我真的需要这个功能。
所以我尝试了一个选项:实现一个可以处理鼠标滚轮滚动事件NSViewRepresentable
的 NSView 覆盖方法。scrollWheel
然后发布通知。
struct MouseWheelScrollEventView : NSViewRepresentable {
class MouseView : NSView {
override var acceptsFirstResponder: Bool {
true
}
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
}
override func scrollWheel(with event: NSEvent) {
NotificationCenter.default.post(name: Notification.Name("mouseevent"), object: event)
}
}
func makeNSView(context: Context) -> some NSView {
let view = MouseView()
DispatchQueue.main.async {
view.window?.makeFirstResponder(view)
}
return view
}
func updateNSView(_ nsView: NSViewType, context: Context) {
print("update")
}
}
然后在 SwiftUI 中,代码如下:
@available(OSX 11.0, *)
struct ContentView: View {
let mouseWheelScrollEventPublisher = NotificationCenter.default.publisher(for: Notification.Name(rawValue: "mouseevent"))
@State var deltaY = 0.0
@State var currentIndex = 1
var body: some View{
ZStack{
ScrollView(.horizontal,showsIndicators:false) {
ScrollViewReader { value in
LazyHStack(alignment: .top) {
ForEach(1...100, id: \.self) { index in
if(self.currentIndex == index) {
Text("\(String(index))")
.frame(width: 80, height: 80)
.background(Color.yellow)
.onTapGesture(perform: {
print(index)
})
}else {
Text("\(String(index))")
.frame(width: 80, height: 80)
.background(Color.blue)
.onTapGesture(perform: {
print(index)
})
}
}
}
.onReceive(mouseWheelScrollEventPublisher) { output in
let event = output.object as! NSEvent
let deltaY = event.deltaY
if(deltaY < 0 && self.currentIndex < 100) {
self.currentIndex += 1
value.scrollTo(currentIndex)
}else if(deltaY > 0 && self.currentIndex > 1) {
self.currentIndex -= 1
value.scrollTo(currentIndex)
}
}
}
}
.frame(width: 600)
MouseWheelScrollEventView()
}
}
}
现在滚动事件可以在.horizontal
scrollView中处理,但是有一个新问题:scrollViewItem不能处理点击事件,因为MouseWheelScrollEventView
处理的是鼠标点击事件。但我希望 scrollViewItem 也可以处理鼠标单击事件。
如何处理鼠标滚轮滚动事件和单击事件?
ps:我知道使用appkit可以解决问题,但是有没有办法尝试使用swiftui实现呢?
解决方案
在以下情况下,您可以简单地收听应用程序currentEvent
并跳过窥探 NSViewRep:
- 您已经为视图设置了一个悬停/焦点布尔值
- 不需要 NSTrackingArea 来限制响应(例如,用户希望始终与之交互的瞬态弹出窗口中只有一个 ScrollView)
下面是我的一个 macOS SwiftUI 应用程序中的瞬态弹出窗口示例。水平 ScrollView 呈现选项,可以通过缓慢或快速垂直滚动来“滚动选择”。您可以插入 ScrollViewProxy 以获得相同的想法。
在测试实现中,使用 .onReceive() {} 会重新计算视图,并且单个鼠标滚动会重复触发。将订阅存储在 @State 变量中可以让它按需要工作。
//horizontal scroll view
.onAppear { trackScrollWheel() }
func trackScrollWheel() {
NSApp.publisher(for: \.currentEvent)
.filter { event in event?.type == .scrollWheel }
.throttle(for: .milliseconds(200),
scheduler: DispatchQueue.main,
latest: true)
.sink { [weak vm] event in
vm?.goBackOrForwardBy(delta: Int(event?.deltaY ?? 0))
}
.store(in: &subs)
}
@State var subs = Set<AnyCancellable>() // Cancel onDisappear
推荐阅读
- javascript - 使用电子伪造打包应用程序时出现问题“无法解析'./→'”
- reactjs - redux 工具包中的变异状态
- xml - 如何在 Powershell 中显示整个 XML 树?
- asp.net-core - 将外部样式表页面分配给 .core Web 应用程序中的剃须刀页面
- laravel - Laravel snappy 返回 Failed to load about:blank, with network status code 301 and http status code 0 - 协议“about”未知错误
- java - 这是解决这个“oneFiveSeven”递归问题的正确方法吗?(不希望有效率)
- c# - 如何在 ASP.NET Core 中注册此依赖项?
- c++ - 具有相同复制值的类型转换问题到新数组类型
- elixir - 使用数据加载器批量加载苦艾酒中的字段
- javascript - 如何检查是否没有选定的值然后选择第一项?