将顶部填充设置为1会破坏至少2个重要的事情:
- 滚动视图不会延伸到NavigationView和TabView下方,这使得它失去了在栏下滚动的内容的美丽模糊效果。
- 在滚动视图上设置背景将导致大标题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被使用,因为编译器错误地推断嵌套滚动视图的内容应该与接收器相同。显式声明AnyView将使其接受包装的内容。
有两个重载:
- 第一个不接受任何参数,您只需在任何现有滚动视图上调用它即可,例如,您可以用
.fixFlickering()
替换.padding(.top, 1)
- 就是这样。
- 第二个接受一个配置器闭包,用于让您有机会设置嵌套滚动视图。这是必需的,因为我们不使用接收器,只是将其包装起来,但是我们创建了一个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"))
}
}
}
scrollIndicatorInsets
!不幸的是,我想知道你是否需要 UIKit 才能真正做到这一点。 - Coder-256pinnedView
一起使用。 - Jacek GrygielAnyView
的原因,我不会实现它。 - Martin