iOS Development

ios – Peak resetting to 0 first when utilizing a .matchedGeometryEffect() transition in SwiftUI

Spread the love


I am struggling to know why .matchedGeometryEffect() is behaving this manner:

I am making an attempt to animate a tab’s peak to totally different heights, based mostly on its content material. To take action I am utilizing .matchedGeometryEffect() on the tab background as observe:

struct ContentView: View {

    // MARK: - Properties
    @State personal var selectedTab: Tab = .cost
    @Namespace var namespace

    // MARK: - Physique
    var physique: some View {
        ZStack(alignment: .backside) {
            // Background shade
            Shade.gray6
                .ignoresSafeArea()
            
            // Tab
            swap selectedTab {
            case .dashboard:
                DashboardTabView(namespace: namespace)
                
            case .transactions:
                TransactionsTabView(namespace: namespace)
                
            case .cost:
                PaymentTabView(namespace: namespace)
                
            case .companies:
                ServicesTabView(namespace: namespace)
                
            case .settings:
                SettingsTabView(namespace: namespace)
            }

        }
        .safeAreaInset(edge: .backside, spacing: 0) {
            // Tab bar
            TabBar(selectedTab: $selectedTab)
        }
    }
}

struct DashboardTabView: View {

    // MARK: - Properties
    let namespace: Namespace.ID
    
    // MARK: - Physique
    var physique: some View {
        TabViewContainer(namespace: namespace) {
            Textual content("Dashboard Tab")
                .foregroundStyle(.black)
        }
    }
}

struct PaymentTabView: View {

    // MARK: - Properties
    let namespace: Namespace.ID

    // MARK: - Physique
    var physique: some View {
        TabViewContainer(namespace: namespace) {
            HStack(spacing: 16) {
                CTAButton(title: "Obtain", icon: "arrow.down.sq.") { }
                    .matchedGeometryEffect(id: "one", in: namespace)
                CTAButton(title: "Pay", icon: "dollarsign.arrow.circlepath", shade: Shade(hex: "#F1D302")) { }
                    .matchedGeometryEffect(id: "two", in: namespace)
            }
            .body(peak: 52)
            .padding(.horizontal)
        }
    }
}

struct TabViewContainer<Content material: View>: View {

    // MARK: - Init
    init(
        cornerRadii: RectangleCornerRadii = .init(topLeading: 21, bottomLeading: 0, bottomTrailing: 0, topTrailing: 21),
        namespace: Namespace.ID,
        @ViewBuilder content material: @escaping () -> Content material
    ) {
        self.cornerRadii = cornerRadii
        self.namespace = namespace
        self.content material = content material
    }

    // MARK: - Properties
    let namespace: Namespace.ID
    @State personal var childHeight: CGFloat = 0
    @ViewBuilder var content material: () -> Content material
    personal let cornerRadii: RectangleCornerRadii

    // MARK: - Physique
    var physique: some View {
        ZStack(alignment: .backside) {
            // Background shade
            Shade.gray0
                .matchedGeometryEffect(id: "tab_background", in: namespace)
                .body(peak: childHeight)
                .clipShape(UnevenRoundedRectangle(cornerRadii: cornerRadii, model: .steady))
            
            // Content material
            content material()
                .padding(.vertical)
                .overlay {
                    GeometryReader { proxy in
                        Shade.clear
                            .activity(id: proxy.dimension.peak) {
                                childHeight = max(proxy.dimension.peak, 0)
                            }
                    }
                }
        }
    }
}

I can see the animation on the display nevertheless it seems like as a substitute of animating from the origin view to the vacation spot view straight, it reset the peak to 0 first, inflicting the animation to interrupt.

Would anyone perceive why it is behaving this manner by any likelihood? Am I lacking one thing within the implementation?

Here is what it seems like proper now. I slowed down the animation on Xcode simulator so you may see it extra clearly:

Hyperlink to Xcode simulator demo

I am making an attempt to show some tabs based mostly on the selectedTab worth within the ContentView. These tabs (DashboardTabView, PaymentTabView, and so forth) all leverage the identical TabViewContainer that comprises a Shade.gray0 view that makes use of .matchedGeometryEffect(id: "tab_background", in: namespace).

My expectation is that these tab will transition peak seamlessly from one tab to a different, and actually once I arduous code the values for the peak the animation is working propertly.

The issue arises once I use GeometryReader to retrieve the peak of the content material() youngster view. Then the animation breaks.

Leave a Reply

Your email address will not be published. Required fields are marked *