You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SwiftUI中如何实现鼠标滚轮无需Shift键直接水平滚动?

解决SwiftUI水平ScrollView无需Shift直接鼠标滚轮滚动的问题

嘿,我懂你的痛点——默认macOS上SwiftUI的水平ScrollView必须按住Shift才能用滚轮横向滚,确实有点麻烦。下面给你两种解决方案,分别适配不同的系统版本:

方法一:macOS 13+ 原生极简方案

从macOS Ventura(13.0)开始,苹果给NSScrollView加了个scrollWheelBehavior属性,直接设置就能让滚轮默认响应水平滚动。我们只需要在ScrollView出现时找到底层的NSScrollView实例,修改这个属性就行:

struct HorizontalScrollDemo: View {
    var body: some View {
        VStack {
            // 你的水平滚动区域
            ScrollView(.horizontal) {
                HStack(spacing: 20) {
                    // 这里替换成你的实际内容
                    ForEach(1...20, id: \.self) { index in
                        Text("卡片 \(index)")
                            .padding()
                            .background(Color.blue.opacity(0.1))
                            .cornerRadius(8)
                    }
                }
                .padding()
            }
            .onAppear {
                // 定位到当前窗口的NSScrollView
                if let scrollView = NSApp.windows.first?.contentView?.findFirstSubview(ofType: NSScrollView.self) {
                    scrollView.scrollWheelBehavior = .horizontal
                }
            }
        }
    }
}

// 给NSView加个扩展,方便递归查找子视图
extension NSView {
    func findFirstSubview<T: NSView>(ofType type: T.Type) -> T? {
        if let selfAsTarget = self as? T {
            return selfAsTarget
        }
        for subview in subviews {
            if let found = subview.findFirstSubview(ofType: type) {
                return found
            }
        }
        return nil
    }
}

这个方法的好处是几乎不用改原有布局,只是加了个onAppear的设置。如果你的视图结构比较复杂,可能需要调整查找子视图的逻辑,确保定位到正确的那个ScrollView。

方法二:兼容macOS 12及以下的自定义方案

如果需要支持旧版本系统,我们可以自己封装一个NSScrollView的SwiftUI包装器,拦截滚轮事件把垂直滚动转换成水平滚动:

// 自定义水平滚动View,支持直接用滚轮滚动
struct CustomHorizontalScrollView<Content: View>: NSViewRepresentable {
    private let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = NSScrollView()
        // 配置滚动属性
        scrollView.hasHorizontalScroller = true
        scrollView.hasVerticalScroller = false
        scrollView.autohidesScrollers = true
        scrollView.borderType = .noBorder
        
        // 把SwiftUI内容包装成NSView
        let hostingView = NSHostingView(rootView: content)
        hostingView.autoresizingMask = [.width, .height]
        scrollView.documentView = hostingView
        
        // 重写滚轮事件,把垂直偏移转成水平偏移
        scrollView.scrollWheel = { event in
            var modifiedEvent = event
            modifiedEvent.deltaX = event.deltaY // 把滚轮上下的偏移量给到水平方向
            modifiedEvent.deltaY = 0
            super.scrollWheel(modifiedEvent)
        }
        
        return scrollView
    }
    
    func updateNSView(_ nsView: NSScrollView, context: Context) {
        // 更新内容
        (nsView.documentView as? NSHostingView<Content>)?.rootView = content
    }
}

// 使用示例
struct ContentView: View {
    var body: some View {
        VStack {
            CustomHorizontalScrollView {
                HStack(spacing: 20) {
                    ForEach(1...20, id: \.self) { index in
                        Text("卡片 \(index)")
                            .padding()
                            .background(Color.blue.opacity(0.1))
                            .cornerRadius(8)
                    }
                }
                .padding()
            }
            .frame(height: 100) // 给滚动区域设置固定高度
        }
        .padding()
    }
}

这个自定义包装器完全接管了滚轮事件的处理,不管系统版本都能生效,缺点是需要替换原有ScrollView的写法,但适配性更强。

小提醒

  • 不管用哪种方法,都要确保你的HStack内容宽度超过滚动区域的宽度,不然不会触发滚动;
  • 方法一中的子视图查找逻辑如果在多ScrollView的场景下可能不准确,你可以结合ScrollViewReader或者给目标ScrollView加标签来精准定位。

内容的提问来源于stack exchange,提问作者user4150758

火山引擎 最新活动