You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI List悬停水平滚动子视图时无法垂直滚动的问题求助

SwiftUI List嵌套水平滚动视图后垂直滚动失效的解决方法

问题场景与限制

在SwiftUI List中嵌套支持水平滚动的子视图时,出现以下异常:

  • 鼠标悬停在普通子视图上时,List可正常垂直滚动
  • 悬停在水平滚动视图上一段时间后,List无法接收垂直滚动事件,只能操作水平滚动

需满足的限制条件:

  • 替换为ScrollView+LazyVStack会导致严重性能问题,必须使用List
  • macOS 13+提供的.scrollDisabled(true)会同时禁用水平和垂直滚动,不符合需求(仅需保留水平滚动,垂直滚动事件交给List)

最小复现代码

import SwiftUI

struct ContentView: View {
    var body: some View {
        List {
            ForEach(0..<5) { index in
                Text("\(index) line")
            }
            ScrollView(.horizontal) {
                Text("hello, world! hello, world! hello, world! hello, world! hello, world!\n hello, world! hello, world! hello, world! \n hello, world! hello, world! hello, world! hello, world!")
            }.font(.largeTitle)
            ForEach(5..<10) { index in
                Text("\(index) line")
            }
        }
    }
}

#Preview {
    ContentView()
}

解决方案

方法一:封装UIKit的UIScrollView实现水平滚动

利用UIKit的UIScrollView的directionalLockEnabled属性,让垂直滚动事件透传给父级List。封装一个仅支持水平滚动的自定义视图:

import SwiftUI
import UIKit

struct HorizontalScrollView<Content: View>: UIViewRepresentable {
    let content: Content
    
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        // 启用方向锁定,仅允许水平滚动,垂直事件透传
        scrollView.isDirectionalLockEnabled = true
        scrollView.showsVerticalScrollIndicator = false
        scrollView.showsHorizontalScrollIndicator = true
        scrollView.alwaysBounceVertical = false
        scrollView.alwaysBounceHorizontal = true
        
        // 添加SwiftUI内容到UIScrollView
        let hostingView = UIHostingController(rootView: content).view!
        hostingView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(hostingView)
        
        NSLayoutConstraint.activate([
            hostingView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            hostingView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            hostingView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            hostingView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            hostingView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
        ])
        
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {
        // 更新内容时替换旧的hosting view
        let hostingView = UIHostingController(rootView: content).view!
        hostingView.translatesAutoresizingMaskIntoConstraints = false
        uiView.subviews.first?.removeFromSuperview()
        uiView.addSubview(hostingView)
        
        NSLayoutConstraint.activate([
            hostingView.topAnchor.constraint(equalTo: uiView.topAnchor),
            hostingView.bottomAnchor.constraint(equalTo: uiView.bottomAnchor),
            hostingView.leadingAnchor.constraint(equalTo: uiView.leadingAnchor),
            hostingView.trailingAnchor.constraint(equalTo: uiView.trailingAnchor),
            hostingView.heightAnchor.constraint(equalTo: uiView.heightAnchor)
        ])
    }
}

使用时替换原有的ScrollView(.horizontal):

HorizontalScrollView {
    Text("hello, world! hello, world! hello, world! hello, world! hello, world!\n hello, world! hello, world! hello, world! \n hello, world! hello, world! hello, world! hello, world!")
}.font(.largeTitle)

方法二:通过手势优先级强制垂直事件传递给List

给水平ScrollView添加高优先级的垂直拖动手势,拦截垂直滚动事件并让父级List处理:

ScrollView(.horizontal) {
    Text("hello, world! hello, world! hello, world! hello, world! hello, world!\n hello, world! hello, world! hello, world! \n hello, world! hello, world! hello, world! hello, world!")
}
.font(.largeTitle)
.highPriorityGesture(
    DragGesture(minimumDistance: 0)
        .onChanged { value in
            // 当垂直拖动距离大于水平时,抢占手势优先级,让List处理滚动
            if abs(value.translation.height) > abs(value.translation.width) {}
        }
)

方案说明

  • 方法一基于UIKit,兼容性更好,在macOS上的滚动行为更稳定,不会出现手势抢占问题
  • 方法二更轻量,但依赖SwiftUI的手势优先级机制,部分场景下可能需要调整手势的minimumDistance参数

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 00:55:11