ScrollView + NavigationView 动画故障 SwiftUI [英] ScrollView + NavigationView animation glitch SwiftUI

查看:33
本文介绍了ScrollView + NavigationView 动画故障 SwiftUI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个简单的看法:

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

但是您可以用任何东西替换 CardView - 故障仍然存在.故障视频

But you can replace the CardView with anything - the glitch presists. Glitch video

有办法解决吗?

Xcode 12.0.1,Swift 5

Xcode 12.0.1, Swift 5

推荐答案

将 top padding 设置为 1 会破坏至少 2 个主要的事情:

Setting the top padding to 1 is breaking at least 2 major things:

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

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

I've encountered these issues when i had to change the background color on all screens of the app i was working on. So i did a little bit more digging and experimenting and managed to figure out a pretty nice solution to the problem.

我们将 ScrollView 包装成 2 个几何读取器.

We wrap the ScrollView into 2 geometry readers.

最上面的是安全区 - 我们需要这个来读取安全区插图第二个是全屏.

The top one is respecting the safe area - we need this one in order to read the safe area insets The second is going full screen.

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

We put the scroll view into the second geometry reader - making it size to full screen.

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

Then we add the content using VStack, by applying safe area paddings.

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

At the end - we have scroll view that does not flicker and accepts background without breaking the large title of the navigation bar.

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.

The elegant solution

Since the solution is clear now - lets create an elegant solution that can be reused and applied to any existing ScrollView by just replacing the padding fix.

我们创建了一个 ScrollView 的扩展,它声明了 fixFlickering 函数.

We create an extension of ScrollView that declares the fixFlickering function.

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

The logic is basically we wrap the receiver into the geometry readers and wrap its content into the VStack with the safe area paddings - that's it.

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

The ScrollView is used, because the compiler incorrectly infers the Content of the nested scroll view as should being the same as the receiver. Declaring AnyView explicitly will make it accept the wrapped content.

有 2 个重载:

  • 第一个不接受任何参数,您可以在任何现有的滚动视图上调用它,例如.你可以用 .fixFlickering() 替换 .padding(.top, 1) - 就是这样.
  • 第二个接受配置器闭包,用于让您有机会设置嵌套滚动视图.这是必需的,因为我们不使用接收器,只是将它包装起来,但是我们创建了一个 ScrollView 的新实例,并且只使用接收器的配置和内容.在这个闭包中,您可以以任何您喜欢的方式修改提供的 ScrollView,例如.设置背景颜色.
  • the first one does not accept any arguments and you can just call it on any of your existing scroll views, eg. you can replace the .padding(.top, 1) with .fixFlickering() - thats it.
  • the second one accept a configurator closure, which is used to give you the chance to setup the nested scroll view. Thats needed because we don't use the receiver and just wrap it, but we create a new instance of ScrollView and use only the receiver's configuration and content. In this closure you can modify the provided ScrollView in any way you would like, eg. setting a background color.
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"))
        }
    }
}

这篇关于ScrollView + NavigationView 动画故障 SwiftUI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆