Ios macOS上的SwiftUI嵌套滚动视图问题
(如果有苹果的人看到这一点-我提交了反馈:FB8910881) 我正在尝试构建一个SwiftUI应用程序,其布局类似于Apple音乐应用程序-艺术家页面,或者类似于Apple()的SwiftUI教程。在应用程序中,您可以垂直滚动列表项,水平滚动所有行 在我的应用程序中,我在Ios macOS上的SwiftUI嵌套滚动视图问题,ios,swift,macos,swiftui,Ios,Swift,Macos,Swiftui,(如果有苹果的人看到这一点-我提交了反馈:FB8910881) 我正在尝试构建一个SwiftUI应用程序,其布局类似于Apple音乐应用程序-艺术家页面,或者类似于Apple()的SwiftUI教程。在应用程序中,您可以垂直滚动列表项,水平滚动所有行 在我的应用程序中,我在滚动视图中有一个行列表(垂直)。行本身垂直滚动,并包含一个ScrollView和一个LazyHGrid。它适用于iOS,但不适用于macOS 在iOS上,我可以在列表中垂直滚动,在行项目中水平滚动——在模拟器中,单击并拖动鼠
滚动视图中有一个行列表(垂直)。行本身垂直滚动,并包含一个ScrollView
和一个LazyHGrid
。它适用于iOS,但不适用于macOS
在iOS上,我可以在列表中垂直滚动,在行项目中水平滚动——在模拟器中,单击并拖动鼠标在应用程序中滚动——效果非常好
在macOS上,如果我没有超过一行,我只能垂直滚动。。。我通过神奇鼠标上/下/左/右滚动。行的滚动视图似乎捕捉到了垂直滚动。。。在Apple音乐应用程序中,如果您浏览艺术家播放列表行,您可以上下左右滚动
Xcode 12.2和Xcode 12.3测试版,macOS BigSur 11.0.1
这是我的密码:
import SwiftUI
struct ScrollViewProblem: View {
var body: some View {
ScrollView {
HStack {
Text("Scrolling Problem")
.font(.largeTitle)
Spacer()
}.padding()
ScrollRow()
ScrollRow()
ScrollRow()
ScrollRow()
}
}
}
struct ScrollRow: View {
let row = [
GridItem(.flexible(minimum: 200))
]
var body: some View {
VStack {
HStack {
Text("Row")
.font(.largeTitle)
Spacer()
}
ScrollView(.horizontal) {
LazyHGrid(rows: row, spacing: 20) {
ForEach(1..<7) { index in
Rectangle()
.fill(Color.blue)
.frame(width: 200, height: 200)
}
}
}
}
.padding()
}
}
struct ScrollViewProblem_Previews: PreviewProvider {
static var previews: some View {
ScrollViewProblem()
}
}
导入快捷界面
结构ScrollViewProblem:视图{
var body:一些观点{
滚动视图{
HStack{
文本(“滚动问题”)
.font(.largeTitle)
垫片()
}.padding()
ScrollRow()
ScrollRow()
ScrollRow()
ScrollRow()
}
}
}
结构滚动行:视图{
让行=[
网格项(.flexible(最小值:200))
]
var body:一些观点{
VStack{
HStack{
文本(“行”)
.font(.largeTitle)
垫片()
}
滚动视图(.horizontal){
LazyHGrid(行:行,间距:20){
ForEach(1..我已经找到了解决这个问题的方法,尽管我希望苹果公司能很快解决这个问题
据我所知,根本问题是鼠标滚轮滚动事件没有从内部水平滚动视图转发到外部垂直滚动视图
令人沮丧的是,有一种方法正好适用于该场景:指定滚动事件是否应转发到响应程序链上。它被称为wantsForwardedScrollEvents
,并且有文档记录。尽管文档说它是用于“弹性滚动”,显示了它在将事件向上传递到所有滚动事件的响应程序链时是如何同样有用
因此,我们需要的是:
制作一个NSView
,覆盖垂直滚动轴的WantForwardedScrollEvents
并返回true
,以便垂直滚动事件从内部水平滚动传递到外部垂直滚动
将此NSView
插入水平滚动和垂直滚动之间的SwiftUI视图层次结构
第二点有点棘手,但谢天谢地,真正令人惊奇的SwiftUI实验室博客做到了。说真的,我无法计算在使用SwiftUI时,这个博客多少次拯救了我的理智
因此,最终代码可以如下所示:
// we need this workaround only for macOS
#if os(macOS)
// this is the NSView that implements proper `wantsForwardedScrollEvents` method
final class VerticalScrollingFixHostingView<Content>: NSHostingView<Content> where Content: View {
override func wantsForwardedScrollEvents(for axis: NSEvent.GestureAxis) -> Bool {
return axis == .vertical
}
}
// this is the SwiftUI wrapper for our NSView
struct VerticalScrollingFixViewRepresentable<Content>: NSViewRepresentable where Content: View {
let content: Content
func makeNSView(context: Context) -> NSHostingView<Content> {
return VerticalScrollingFixHostingView<Content>(rootView: content)
}
func updateNSView(_ nsView: NSHostingView<Content>, context: Context) {}
}
// this is the SwiftUI wrapper that makes it easy to insert the view
// into the existing SwiftUI view builders structure
struct VerticalScrollingFixWrapper<Content>: View where Content : View {
let content: () -> Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content
}
var body: some View {
VerticalScrollingFixViewRepresentable(content: self.content())
}
}
#endif
已在macOS 11.0.1和Xcode 12.2上验证
[根据@MarshallLino16的评论更新]
将NSView
添加到视图层次结构后,您可能会遇到一个SwiftUI问题。内部视图停止响应外部视图状态的更改
使用此处示例代码中的术语:如果在ScrollViewProblem
中有一个@State
变量被传递到ScrollRow
,则ScrollRow
并不总是对该@State变量更改重新计算
这一问题在报告中提到
不幸的是,我还没有找到一个直接的解决方法来使@State
传播再次工作,所以目前我知道(并一直在使用我自己)的最佳工作解决方案是不将@State
变量从滚动视图问题
传递到滚动行
这意味着无论您需要传递给ScrollRow
,都必须将其封装在外部对象中(视图模型、视图存储,无论您的体系结构使用什么)。然后您可以将此对象传递到ScrollRow
,并让ScrollRow
保留它,并将其用作@State
或@StateObject
变量。Hi@Luffy-谢谢您的检查。您使用的是哪个版本的Xcode/macOS?我更新了我的描述:Xcode 12.2和Xcode 12.3 beta,macOS BigSur 11.0.1 and在我的描述中加入gif…仍然存在这个问题…我也遇到了同样的问题,代码非常相似。在12.2上编译,在11.0.1上运行。你可以用XCode 12.2在macOS 11.0.1上确认这个错误。使用SwiftUI应用程序作为生命周期。有同样的问题,macOS 11.0.1,XCode 12.2。这是一个惊人的答案,可以确认它对我有效太好了。非常感谢您花时间来分享它。唯一的问题是,它会导致一些大小调整回归,使行在滚动视图中居中。我通过将视图修改器向上移动到ZStack
上一级来解决这一问题,以避免混淆滚动视图。是的,引入额外的包装器可能会导致以前的SwiftUI结构表现不同。毕竟,它是一个多视图,因此如果定义了某些特定的大小,则不再为该视图定义。太好了,您已经设法根据您的需要对其进行了调整。哇,非常感谢您的详细回答!!这很有魅力!!不幸的是,包装的scrollview对视图没有反应模型改变了。你经历过吗?感谢anwser!我实际上找到了一个解决方法(不理想),每次需要更新可表示视图时,我都会设置内容
import SwiftUI
// just a helper to make using nicer by keeping #if os(macOS) in one place
extension View {
@ViewBuilder func workaroundForVerticalScrollingBugInMacOS() -> some View {
#if os(macOS)
VerticalScrollingFixWrapper { self }
#else
self
#endif
}
}
struct ScrollViewProblem: View {
var body: some View {
ScrollView {
HStack {
Text("Scrolling Problem")
.font(.largeTitle)
Spacer()
}.padding()
ScrollRow().workaroundForVerticalScrollingBugInMacOS()
ScrollRow().workaroundForVerticalScrollingBugInMacOS()
ScrollRow().workaroundForVerticalScrollingBugInMacOS()
ScrollRow().workaroundForVerticalScrollingBugInMacOS()
}
}
}