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
相关产品推荐
相关产品推荐

