如何防止我的 SwiftUI 视图在滚动时调整大小

问题描述:

我希望有人能指出我正确的方向 - 我一直在试验 SwiftUI,我创建了一个与 Twitter 个人资料 UI 有点相似的视图.

I’m hoping someone can point me in the right direction - I have been experimenting with SwiftUI, I’ve created a view somewhat similar to the Twitter Profile UI.

当标题折叠时,我似乎有一个奇怪的效果.

I seem to have a weird effect when the header collapses.

在滚动视图包含在标题下滚动之前,有一个最佳位置",其中标题折叠并且标签栏调整了少量大小.

There is a ‘sweet spot’ in which the header is collapsed and the tab bar resizes by a small amount before the scroll view contains to scroll under the header.

您可以在此视频中看到.

我根本不希望标签段调整大小,而是应该在标题折叠后保持原位,并且滚动视图应该在其下方*移动.

I don’t want the tab segment to resize at all, instead it should simply stay in place once the header has collapsed and the scroll view should move freely underneath it.

我确定我遗漏了一些明显的东西,但一些新鲜的眼睛可能正是我需要的帮助.

I’m sure I am missing something obvious, however some fresh eyes might be just the help I need.

对此的任何想法将不胜感激!

Any thoughts on this would be much appreciated!

import SwiftUI

struct ContentView: View {
    
    @State private var safeArea: EdgeInsets = EdgeInsets(.zero)
    @State private var offset: CGFloat = 0
    @State private var tabBarOffset: CGFloat = 0
    
    @State var currentTab = "Tab #1"
    @Namespace var animation
    
    var body: some View {
        GeometryReader { foo in
            ScrollView {
                VStack(spacing: 0) {
                    // Header
                    GeometryReader { proxy -> AnyView in
                        // Sticky Header
                        DispatchQueue.main.async {
                            offset = proxy.frame(in: .global).minY
                            safeArea = foo.safeAreaInsets
                        }
                        
                        return AnyView(makeHeader())
                    }
                    .frame(height: 180)
                    .zIndex(1)
                    
                    // Profile
                    VStack(spacing: 0) {
                        VStack(spacing: 0) {
                            HStack(spacing: 0) {
                                TabButton(title: "Tab #1", currentTab: $currentTab, animation: animation)
                                    .frame(maxWidth: .infinity)
                                TabButton(title: "Tab #2", currentTab: $currentTab, animation: animation)
                                    .frame(maxWidth: .infinity)
                                TabButton(title: "Tab #3", currentTab: $currentTab, animation: animation)
                                    .frame(maxWidth: .infinity)
                            }
                            Divider()
                        }
                        .padding(.top, 20)
                        .background(Color.white)
                        .offset(y: tabBarOffset < 90 ? -tabBarOffset + 90 : 0)
                        .overlay(
                            GeometryReader { proxy -> Color in
                                DispatchQueue.main.async {
                                    tabBarOffset = proxy.frame(in: .global).minY
                                }
                                return Color.clear
                            }
                            .frame(width: 0, height: 0),
                            alignment: .top
                        )
                        .zIndex(1)
                        VStack {
                            ForEach((0..<50)) {
                                Text("Row #\($0)")
                                Divider()
                            }
                        }
                        .padding(.top)
                        .zIndex(0)
                    }
                }
            }
            .ignoresSafeArea(.all, edges: .top)
        }
    }
    
    @ViewBuilder func makeHeader() -> some View {
        ZStack {
            // Cover
            Color.gray
                .frame(maxWidth: .infinity)
                .frame(height: offset > 0 ? 180 + offset : 180)
        }
        .clipped()
        // Stretchy Effect
        .frame(height: offset > 0 ? 180 + offset : nil)
        .offset(y: offset > 0 ? -offset : -offset < 80 ? 0 : -offset - 80)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct TabButton: View {
    var title: String
    @Binding var currentTab: String
    var animation: Namespace.ID
    
    var body: some View {
        Button(
            action: {
                withAnimation { currentTab = title }
            },
            label: {
                LazyVStack(spacing: 12) {
                    Text(title)
                        .fontWeight(.semibold)
                        .foregroundColor(currentTab == title ? .blue : .gray)
                        .padding(.horizontal)
                    
                    if currentTab == title {
                        Capsule()
                            .fill(Color.blue)
                            .frame(height: 1.2)
                            .matchedGeometryEffect(id: "TAB", in: animation)
                    } else {
                        Capsule()
                            .fill(Color.clear)
                            .frame(height: 1.2)
                    }
                    
                }
            }
        )
    }
}

这里,设置 90 而不是 80.因为您设置顶部标签栏视图 y 偏移 90,并且您的整个逻辑基于 90 值.

Here, Set 90 instead of 80. As your setting top tabbar view y offset 90 and your whole logic is based on 90 value.

@ViewBuilder func makeHeader() -> some View {
    ZStack {
        // Cover
        Color.red
            .frame(maxWidth: .infinity)
            .frame(height: offset > 0 ? 180 + offset : 180)
    }
    .clipped()
    // Stretchy Effect
    .frame(height: offset > 0 ? 180 + offset : nil)
    .offset(y: offset > 0 ? -offset : -offset < 90 ? 0 : -offset - 90) //<=== Here
}