SwiftUI - 如何在ScrollView中更改标题的大小

3
我想创建一个自定义的标题视图,它位于vstack内,在向上滚动时减小该自定义标题的高度。我遵循了这篇博客:https://www.bigmountainstudio.com/community/public/posts/13099-swiftui-geometryreader-sticky-header-when-scrolling-part-5 我与博客的不同之处在于将Image("Utah")替换为customView。
ScrollView {
        ZStack(alignment: .top) {
            // Bottom Layer
            VStack(spacing: 20) {
                ListContentView()
            }
            .padding(.horizontal, 20)
            .padding(.top, 128)

            // Top Layer (Header)
            GeometryReader { gr in
                VStack(alignment: .leading, spacing: 0) {
                    Spacer().frame(height: 32)
                    navigationHeader
                        .frame(height:
                                self.calculateHeight(minHeight: 44,
                                                     maxHeight: 128,
                                                     yOffset: gr.frame(in: .global).minY)
                        )
                        // sticky it to the top
                        .offset(y: gr.frame(in: .global).origin.y < 0 // Is it going up?
                                ? abs(gr.frame(in: .global).origin.y) // Push it down!
                                : -gr.frame(in: .global).origin.y) // Push it up!

                    Spacer()
                }
                .border(.red)
            }
        }
    }
    .navigationBarHidden(true)
    .background(.black)
}

func calculateHeight(minHeight: CGFloat, maxHeight: CGFloat, yOffset: CGFloat) -> CGFloat {
    // If scrolling up, yOffset will be a negative number
    if maxHeight + yOffset < minHeight {
        // SCROLLING UP
        // Never go smaller than our minimum height
        return minHeight
    }

    // SCROLLING DOWN
    return maxHeight
}

private var navigationHeader: some View {
    Group {
        Image("Back")
        Text("Title")
            .h2()
        Spacer.withHeight(Spacing.C4)
        Text("some subtitle text some subtitle text some subtitle text some subtitle text some subtitle text")
            .subheader()
            .fixedSize(horizontal: false, vertical: true)
    }
}

问题在于设置scrollView内部的VStack框架。如果我注释掉.frame修饰符,VStack将使用scrollView的整个空间,并尝试将其全部放置在顶部:

enter image description here

如果我尝试使用框架来设置滚动计算的偏移量,自定义标题视图将尝试使用所有空间,并忽略框架,如下所示:

enter image description here

有什么办法可以实现这里展示的行为吗?

enter image description here

抱歉内容很长,非常感谢您提供任何想法或其他方法来实现此滚动动画。我还尝试设置 .navigationBar() 但这只允许文本。也尝试了 .navigationBarItems(leading: customView) 但仅限于屏幕顶部的一半。
1个回答

3
这里提供一种方法。为了显示/隐藏描述,我将导航栏标题 navigationHeader 更改为函数,传入当前标题高度来决定是否显示。橙色背景仅用于测试目的。

enter image description here

struct ContentView: View {
    var body: some View {
        ScrollView {
            ZStack(alignment: .top) {
                // Bottom Layer
                VStack(spacing: 20) {
                    // dummy list
                    ForEach(0..<30) { item in
                        Text("List Item \(item)")
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(.gray)
                            .cornerRadius(10)
                    }
                }
                .padding(.horizontal, 20)
                .padding(.top, 128)
                
                // Top Layer (Header)
                GeometryReader { gr in
                    VStack {
                        // save height as it is used at two places
                        let h = self.calculateHeight(minHeight: 32,
                                                     maxHeight: 80,
                                                     yOffset: gr.frame(in: .global).minY)
                        Color.clear.frame(height: 32)
                        navigationHeader(showDescr: h > 50)
                            .padding().foregroundColor(.white)
                            .background(.orange) // for demo purpose only
                        
                            .frame(height: h)
                        // sticky it to the top
                            .offset(y: gr.frame(in: .global).origin.y < 0 // Is it going up?
                                    ? abs(gr.frame(in: .global).origin.y) // Push it down!
                                    : -gr.frame(in: .global).origin.y) // Push it up!
                        
                        Spacer()
                        
                    }
                    //.border(.red)
                }
            }
        }
        .navigationBarHidden(true)
        .background(.black)
    }
    
    func calculateHeight(minHeight: CGFloat, maxHeight: CGFloat, yOffset: CGFloat) -> CGFloat {
        // If scrolling up, yOffset will be a negative number
        if maxHeight + yOffset < minHeight {
            // SCROLLING UP
            // Never go smaller than our minimum height
            return minHeight
        }
        // SCROLLING DOWN
        return maxHeight + yOffset
    }
    
    
    private func navigationHeader(showDescr: Bool) -> some View {
        Group {
            if showDescr { // show/hide description
                VStack(alignment: .leading) { // Vstack instead of Group
                    Image(systemName: "arrow.left")
                    Text("Title")
                        .font(.largeTitle)
                    Text("some subtitle text some subtitle text some subtitle text some subtitle text some subtitle text")
                        .font(.subheadline)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .frame(maxHeight: .infinity)
            } else {
                HStack {
                    Image(systemName: "arrow.left")
                    Spacer()
                    Text("Title")
                        .font(.headline)
                    Spacer()
                }
            }
        }
    }
}

经过一些测试,最终发现了问题所在:您在navigationHeader中添加了一个VStack(alignment: .leading)。这解决了我问题的第二张图片。此外,您还添加了框架的更改,非常感谢。我将尝试在导航栏视图之间添加过渡动画,但您已经救了我的命。我没有想到的是,当向下滚动时,我看不到橙色框架向下增长。此外,在向上滚动一点(在切换到内联导航栏视图之前)时,似乎粘性不起作用。 - lopes710
是的,我在代码中注释了VStack,但忘记写在答案里了 :) 我尝试了一次快速的动画/过渡测试,但没有快速地使其工作,所以我跳过了。祝你好运,也许你解决它的方式很有趣。 - ChrisR
对于“粘性”问题...它在我的设备上(Xcode 14b,iOS 16)可以工作,我的发布的代码应该可以复现这个问题...?我已经更改了最小和最大高度,也许是这个原因? - ChrisR
1
不好意思,我复制的时候出错了...头部视图中缺少.frame(maxHeight: .infinity),我会在答案中进行更正。 - ChrisR
不用担心,你已经帮了很多忙。关于粘性效果,我正在使用Xcode 13.4.1和iOS 15。从你发布的gif中可以看出它稍微上移了一点,然后切换到了新的框架。我将从这里开始工作,如果成功的话就会发布。 - lopes710
1
是的,那个 .frame(maxHeight: .infinity) 可以解决向下扩展的问题。而且我已经修复了粘性问题。calculateHeight 中的 return maxHeight + yOffset 应该替换为 just return maxHeight。我将在问题中纠正代码。 - lopes710

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接