Harry Blue
Harry Blue

Reputation: 4502

How can I prevent my SwiftUI view resizing on scroll

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.

You can see in this video.

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

Upvotes: 1

Views: 1168

Answers (1)

Raja Kishan
Raja Kishan

Reputation: 18904

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
}

Upvotes: 2

Related Questions