当我在屏幕上看到第二个
BottomRoundedRectangle()
时,我试图隐藏LazyVStack {…}
,但它不起作用。
value
中的 .onPreferenceChange(OffsetKey.self)
也没有改变。
struct OffsetKey: PreferenceKey {
typealias Value = CGFloat
static let defaultValue: Value = .zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
struct OffsetKeyBackground: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { reader in
content
.preference(
key: OffsetKey.self,
value: -reader.frame(in: .named("secondLazyStack")).origin.y
)
}
}
}
struct ScrollQuestion: View {
@State
private var elementValue: CGFloat = .zero
var body: some View {
ScrollView(.vertical) {
// Dynamic long text there
LazyVStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.frame(height: 920) // dynamic text, 920 for example, may be bigger
}
// 2nd LazyVStack
LazyVStack {
RoundedRectangle(cornerRadius: 12)
.frame(height: 820)
}
.coordinateSpace(name: "secondLazyStack")
}
.safeAreaInset(edge: .bottom) {
// How to hide this correctly when we see second LazyVStack ?
BottomRoundedRectangle()
// .opacity(elementValue < 50 ? 1 : 0)
}
.modifier(OffsetKeyBackground())
.onPreferenceChange(OffsetKey.self) { value in
debugPrint(value)
elementValue = value
}
}
}
struct BottomRoundedRectangle: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 8)
.frame(height: 40)
.foregroundStyle(.indigo)
.padding(.horizontal)
Text("Visible only in first LazyVStack")
.foregroundStyle(.white)
}
}
}
#Preview {
ScrollQuestion()
}
我需要做什么才能正确隐藏
BottomRoundedRectangle()
?
您可以通过将第二个
LazyVStack
包裹在 GeometryReader
中来检测到这一点。
如果滚动视图的框架与
LazyVStack
的框架相交,则意味着 LazyVStack
的至少一部分位于屏幕上。
struct IsOnScreenKey: PreferenceKey {
static let defaultValue = false
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
struct DetectIsOnScreen: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { reader in
content
.preference(
key: IsOnScreenKey.self,
// bounds(of: .scrollView) gives us the scroll view's frame
// frame(in: .local) gives us the LazyVStack's frame
// both are in the local coordinate space
value: reader.bounds(of: .scrollView)?.intersects(reader.frame(in: .local)) ?? false
)
}
}
}
然后你可以这样做:
@State
private var isSecondVStackVisible = false
var body: some View {
ScrollView(.vertical) {
LazyVStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.3))
.frame(height: 920)
}
LazyVStack {
RoundedRectangle(cornerRadius: 12)
.modifier(DetectIsOnScreen())
.frame(height: 820)
.onPreferenceChange(IsOnScreenKey.self) { value in
isSecondVStackVisible = value
}
}
}
.safeAreaInset(edge: .bottom) {
BottomRoundedRectangle()
.opacity(isSecondVStackVisible ? 0 : 1)
}
}
请注意,在这种特殊情况下,也可以将首选项设置为
reader.bounds(of: .scrollView) != nil
这是因为
LazyVStack
很懒——如果它们不在屏幕上,它实际上就不存在。几何读取器无法将滚动视图的框架转换为不存在的坐标空间。