ScrollView + NavigationView 动画故障 SwiftUI

问题描述 投票:0回答:5

我有一个简单的观点:

var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    ForEach(0..<2) { _ in
                        CardVew(for: cardData)
                    }
                }
            }
            .navigationBarTitle("Testing", displayMode: .automatic)
        }
    }

但是你可以用任何东西替换 CardView - 故障仍然存在。 Glitch video

有办法解决吗?

Xcode 12.0.1、Swift 5

swift animation swiftui scrollview
5个回答
32
投票

将顶部填充设置为 1 会破坏至少 2 个主要问题:

  1. 滚动视图不会在 NavigationView 和 TabView 下扩展 - 这使其失去了在栏下滚动的内容的美丽模糊效果。
  2. 在滚动视图上设置背景将导致大标题导航视图停止折叠。

当我不得不更改我正在开发的应用程序的所有屏幕上的背景颜色时,我遇到了这些问题。 所以我做了更多的挖掘和实验,并设法找到了一个非常好的解决问题的方法。

这是原始解决方案:

我们将 ScrollView 包装到 2 个几何读取器中。

最上面的一个是尊重安全区域 - 我们需要这个来阅读安全区域插图 第二个是全屏显示。

我们将滚动视图放入第二个几何读取器中 - 使其大小为全屏。

然后我们使用 VStack 添加内容,通过应用安全区域填充。

最后 - 我们有滚动视图,不会闪烁并接受背景,而不会破坏导航栏的大标题。

struct ContentView: View {
    var body: some View {
        NavigationView {
            GeometryReader { geometryWithSafeArea in
                GeometryReader { geometry in
                    ScrollView {
                        VStack {

                            Color.red.frame(width: 100, height: 100, alignment: .center)

                            ForEach(0..<5) { i in

                                Text("\(i)")
                                    .frame(maxWidth: .infinity)
                                    .background(Color.green)

                                Spacer()
                            }

                            Color.red.frame(width: 100, height: 100, alignment: .center)
                        }
                        .padding(.top, geometryWithSafeArea.safeAreaInsets.top)
                        .padding(.bottom, geometryWithSafeArea.safeAreaInsets.bottom)
                        .padding(.leading, geometryWithSafeArea.safeAreaInsets.leading)
                        .padding(.trailing, geometryWithSafeArea.safeAreaInsets.trailing)
                    }
                    .background(Color.yellow)
                }
                .edgesIgnoringSafeArea(.all)
            }
            .navigationBarTitle(Text("Example"))
        }
    }
}

优雅的解决方案

既然解决方案现在已经很清楚了 - 让我们创建一个优雅的解决方案,只需替换填充修复即可重用并应用于任何现有的 ScrollView。

我们创建 ScrollView 的扩展来声明

fixFlickering
函数。

逻辑基本上是我们将接收器包装到几何读取器中,并将其内容包装到带有安全区域填充的 VStack 中 - 就是这样。

使用ScrollView,因为编译器错误地推断嵌套滚动视图的内容应该与接收者相同。显式声明 AnyView 将使其接受包装的内容。

有 2 个重载:

  • 第一个不接受任何参数,您可以在任何现有的滚动视图上调用它,例如。您可以将
    .padding(.top, 1)
    替换为
    .fixFlickering()
    - 就是这样。
  • 第二个接受配置器闭包,用于让您有机会设置嵌套滚动视图。这是必需的,因为我们不使用接收器而只是包装它,但我们创建了一个新的 ScrollView 实例并仅使用接收器的配置和内容。在此闭包中,您可以按照您想要的任何方式修改提供的 ScrollView,例如。设置背景颜色。
extension ScrollView {
    
    public func fixFlickering() -> some View {
        
        return self.fixFlickering { (scrollView) in
            
            return scrollView
        }
    }
    
    public func fixFlickering<T: View>(@ViewBuilder configurator: @escaping (ScrollView<AnyView>) -> T) -> some View {
        
        GeometryReader { geometryWithSafeArea in
            GeometryReader { geometry in
                configurator(
                ScrollView<AnyView>(self.axes, showsIndicators: self.showsIndicators) {
                    AnyView(
                    VStack {
                        self.content
                    }
                    .padding(.top, geometryWithSafeArea.safeAreaInsets.top)
                    .padding(.bottom, geometryWithSafeArea.safeAreaInsets.bottom)
                    .padding(.leading, geometryWithSafeArea.safeAreaInsets.leading)
                    .padding(.trailing, geometryWithSafeArea.safeAreaInsets.trailing)
                    )
                }
                )
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
}

示例1

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    Color.red.frame(width: 100, height: 100, alignment: .center)

                    ForEach(0..<5) { i in

                        Text("\(i)")
                            .frame(maxWidth: .infinity)
                            .background(Color.green)
                        
                        Spacer()
                    }

                    Color.red.frame(width: 100, height: 100, alignment: .center)
                }
            }
            .fixFlickering { scrollView in
                
                scrollView
                    .background(Color.yellow)
            }
            .navigationBarTitle(Text("Example"))
        }
    }
}

示例2

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    Color.red.frame(width: 100, height: 100, alignment: .center)

                    ForEach(0..<5) { i in

                        Text("\(i)")
                            .frame(maxWidth: .infinity)
                            .background(Color.green)
                        
                        Spacer()
                    }

                    Color.red.frame(width: 100, height: 100, alignment: .center)
                }
            }
            .fixFlickering()
            .navigationBarTitle(Text("Example"))
        }
    }
}

18
投票

这是一个解决方法。将

.padding(.top, 1)
添加到
ScrollView

struct ContentView: View {
            
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    ForEach(0..<2) { _ in
                        Color.blue.frame(width: 350, height: 200)
                    }
                }
            }
            .padding(.top, 1)
            .navigationBarTitle("Testing", displayMode: .automatic)
        }
            
    }
}

6
投票

我简化了@KoCMoHaBTa 的答案。这是:

extension ScrollView {
    private typealias PaddedContent = ModifiedContent<Content, _PaddingLayout>
    
    func fixFlickering() -> some View {
        GeometryReader { geo in
            ScrollView<PaddedContent>(axes, showsIndicators: showsIndicators) {
                content.padding(geo.safeAreaInsets) as! PaddedContent
            }
            .edgesIgnoringSafeArea(.all)
        }
    }
}

像这样使用:

struct ContentView: View {
    var body: some View {
        NavigationView {
            ScrollView {
                /* ... */
            }
            .fixFlickering()
        }
    }
}

3
投票

在面临同样的问题时,我做了一些调查。 该故障似乎来自滚动视图弹跳和滚动上下文减速速度的组合。

目前我已经通过将减速率设置为快来设法使故障消失。它似乎让 swiftui 更好地计算布局,同时保持弹跳动画处于活动状态。

我的解决方法很简单,只需在视图的初始化中设置以下内容即可。缺点是这会影响滚动减速的速度。

 init() {
    UIScrollView.appearance().decelerationRate = .fast
}

一个可能的改进是计算所显示内容的大小,然后根据需要动态切换减速率。


0
投票

这是对我有用的解决方案。

ScrollView {
    LazyVStack {
        Rectangle().foregroundColor(.clear).frame(height: 1.0)
        //Your Content
    }
}

在实际设备 iPhone 6S iOS 15.7 上测试。

iPhone 15 Pro iOS 17.0 模拟器

iPad iOS 17.0 模拟器

当行高可变且不固定时,问题是由我引起的。

© www.soinside.com 2019 - 2024. All rights reserved.