ScrollView + NavigationView动画故障SwiftUI

时间:2020-10-09 12:49:41

标签: swift animation swiftui scrollview

我有一个简单的看法:

var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    ForEach(0..<2) { _ in
                        CardVew(title: "Работа над ошибками", description: "Принести до следующей субботы", name: "Методы и срдества программного обеспеченияМетоды и срдества программного обеспечения", task: "test", done: true)
                    }
                }
            }
            .navigationBarTitle("Testing", displayMode: .automatic)
        }
    }

但是您可以输入任何内容来代替CardView-故障仍然存在。 Glitch video

有办法解决吗?

Xcode 12.0.1

4 个答案:

答案 0 :(得分:7)

这是一种解决方法。将.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)
        }
            
    }
}

答案 1 :(得分:4)

我简化了 @KoCMoHaBTa's answer。这是:

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()
        }
    }
}

答案 2 :(得分:3)

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

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

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

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

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

答案 3 :(得分:1)

将顶部填充设置为1会破坏至少两个主要方面:

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

当我不得不在正在处理的应用程序的所有屏幕上更改背景颜色时,我遇到了这些问题。 因此,我做了一些进一步的挖掘和试验,并设法找到了解决该问题的不错的解决方案。

这是原始解决方案:

我们将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,是因为编译器错误地推断出嵌套滚动视图的Content,这与接收者应该相同。显式声明AnyView将使其接受包装的内容。

有2个重载:

  • 第一个不接受任何参数,您可以在任何现有的滚动视图中调用它,例如。您可以将.padding(.top, 1)替换为.fixFlickering()-就这样。

  • 第二个接受配置程序闭包,该闭包用于为您提供设置嵌套滚动视图的机会。那是必需的,因为我们不使用接收器而只是包装它,而是创建一个新的ScrollView实例,并且仅使用接收器的配置和内容。在此关闭中,您可以按任何方式修改提供的ScrollView,例如。设置背景颜色。

    扩展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"))
        }
    }
}