SwiftUI:可展开的标题图像

问题描述 投票:0回答:1

我在

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

有没有更简单的解决方案?或者我在这里遇到任何明显的问题?

swift list swiftui scrollview
1个回答
0
投票

如果我清楚了解您的要求:

  • 您想要一些滚动内容,例如,
    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)
        }
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.