我在
Scrollview
里面有一个 NavigationStack
,并且想要在上面放置一个粘性图像。向下滚动时,图像应该展开,它也应该渗入导航栏。
我已经让它与
GeometryReader
一起工作,但它似乎导致了许多重新渲染,而且很明显它运行得不太好。
struct TestView: View {
let coordinateSpaceName = "navstack"
@State var size: CGRect = .zero
var body: some View {
NavigationStack {
ScrollView {
Text("y: \(size.minY), height: \(size.height)")
.frame(height: 200)
.frame(maxWidth: .infinity)
.background(
Image(systemName: "star")
.resizable()
.scaledToFit()
.opacity(0.2)
.frame(height: max(0, size.minY + size.height))
// ^ set background size to parent height + distance to top
.offset(x: 0, y: -(size.minY / 2))
// ^ move up
)
.overlay( // read own size and distance to top
GeometryReader { proxy in
let offset = proxy.frame(in: .named(coordinateSpaceName)).minY
Color.clear
.preference(key: StickyHeaderPreferenceKey.self, value: CGRect(
x: 0,
y: offset,
width: 0,
height: proxy.size.height
))
})
.onPreferenceChange(StickyHeaderPreferenceKey.self) { value in
size = value // save new values
}
Color.gray.frame(height: 1000)
.opacity(0.3)
}
.navigationTitle("Navigation Title")
.navigationBarTitleDisplayMode(.inline)
}
.coordinateSpace(name: coordinateSpaceName)
}
}
struct StickyHeaderPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = CGRect()
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
它看起来/应该看起来像这样(除了“向上滑动时变小”部分):
我注意到
.background
中第一个项目的 NavigationStack
正如我想要的那样位于导航栏下方,但我无法让它与 ScrollView
或 List
一起使用:
NavigationStack {
VStack (spacing: 0) {
Text("The background of this should bleed into the navigation bar, while being scrollable")
.frame(maxWidth: .infinity)
.frame(height: 200)
.background(
Image(systemName: "star.fill")
.resizable()
.scaledToFill()
.opacity(0.2)
)
ScrollView {
Color.gray.frame(height: 1000)
}
}
.navigationTitle("Navigation Title")
.navigationBarTitleDisplayMode(.inline)
}
有没有更简单的解决方案?或者我在这里遇到任何明显的问题?
如果我清楚了解您的要求:
VStack
内有一个ScrollView
。VStack
中第一个项目的背景应延伸到屏幕顶部(即进入安全区域)。ScrollView
被拉下时,背景应扩展至充满整个高度。有没有更简单的解决方案?
我还会在后台使用
GeometryReader
来测量第一个项目的位置,但可以在没有 PreferenceKey
且不命名坐标空间的情况下完成。
要获得粘性效果,可以将 y 偏移应用于背景。
因此,其工作原理如上面几点所述:
struct NewTestView: View {
@State var size: CGRect = .zero
private var star: some View {
Image(systemName: "star")
.resizable()
.scaledToFit()
.opacity(0.2)
.frame(height: max(1, size.maxY), alignment: .top)
.frame(maxWidth: .infinity)
.offset(y: -size.minY)
}
private var positionDetector: some View {
GeometryReader { proxy in
let frame = proxy.frame(in: .global)
Color.clear
.onChange(of: frame, initial: true) { oldVal, newVal in
size = newVal
}
}
}
var body: some View {
NavigationStack {
ScrollView {
VStack {
Text("The background of this should bleed into the navigation bar, while being scrollable")
.frame(maxWidth: .infinity)
.frame(height: 200)
.background { positionDetector }
.background(alignment: .top) { star }
.background(
Color.yellow
.padding(.top, -500) // fill top area when pulled down
)
Text("y: \(size.minY), height: \(size.height)")
.frame(height: 200)
.frame(maxWidth: .infinity)
Color.gray.frame(height: 1000)
.opacity(0.3)
}
}
.navigationTitle("Navigation Title")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
}
}
}