Home > front end >  How to animate a rectangle traveling between buttons headings?
How to animate a rectangle traveling between buttons headings?

Time:10-07

I have a group of buttons that behave like a segmented picker. As you tap a button, it updates an enum in state. I'd like to show an indicator on top that runs between buttons instead of show/hide.

This is what I have:

struct ContentView: View {
    enum PageItem: String, CaseIterable, Identifiable {
        case page1
        case page2

        var id: String { rawValue }

        var title: LocalizedStringKey {
            switch self {
            case .page1:
                return "Page 1"
            case .page2:
                return "Page 2"
            }
        }
    }

    @Namespace private var pagePickerAnimation
    @State private var selectedPage: PageItem = .page1

    var body: some View {
        HStack(spacing: 16) {
            ForEach(PageItem.allCases) { page in
                Button {
                    selectedPage = page
                } label: {
                    VStack {
                        if page == selectedPage {
                            Rectangle()
                                .fill(Color(.label))
                                .frame(maxWidth: .infinity)
                                .frame(height: 1)
                                .matchedGeometryEffect(id: "pageIndicator", in: pagePickerAnimation)
                        }
                        Text(page.title)
                            .padding(.vertical, 8)
                            .padding(.horizontal, 12)
                    }
                    .contentShape(Rectangle())
                }
            }
        }
        .padding()
    }
}

enter image description here

I thought the matchedGeometryEffect would help do this but I might be using it wrong or a better way exists. How can I achieve this where the black line on top of the button animates over one button and moves over the other?

CodePudding user response:

You missed the animation:

 struct ContentView: View {
    
    @Namespace private var pagePickerAnimation
    @State private var selectedPage: PageItem = .page1

    var body: some View {
        
        HStack(spacing: 16) {
            
            ForEach(PageItem.allCases) { page in
                
                Button { selectedPage = page } label: {
                    
                    VStack {
                        
                        if page == selectedPage {
                            
                            Rectangle()
                                .fill(Color(.label))
                                .frame(maxWidth: .infinity)
                                .frame(height: 1)
                                .matchedGeometryEffect(id: "pageIndicator", in: pagePickerAnimation)
                            
                        }
                        
                        
                        Text(page.title)
                            .padding(.vertical, 8)
                            .padding(.horizontal, 12)
                        
                        
                        
                    }
                    .contentShape(Rectangle())
                    
                }
            }
        }
        .padding()
        .animation(.default, value: selectedPage) // <<: here
    }
}



enum PageItem: String, CaseIterable, Identifiable {
    case page1
    case page2

    var id: String { rawValue }

    var title: LocalizedStringKey {
        switch self {
        case .page1:
            return "Page 1"
        case .page2:
            return "Page 2"
        }
    }
}
  • Related