Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Use matchedGeometryEffect to create 'slide' effect for border

I’m trying to create a custom ‘tab’ selection control with a horizontal row of options and the user can select one of N number of options. The ‘selected’ option will have a ‘border’ around it. Here’s a prototype I made:

@objc public enum ContactTabStyle: Int, CaseIterable {
    case one, two, three, four
    
    public var segmentTitle: String {
        switch self {
            case .one: return "Hello"
            case .two: return "World"
            case .three: return "Three"
            case .four: return "Four"
        }
    }
}

struct SwiftUIView: View {
    let segments: [ContactTabStyle] = [.one, .two, .three, .four]
    @State var selectedTab: ContactTabStyle = .one
    
    @Namespace var tabName
    
    var body: some View {
        HStack {
            ForEach(segments, id: \.self) { segment in
                Button {
                    selectedTab = segment
                } label: {
                    Text(segment.segmentTitle)
                        .padding(12.0)
                        .border(selectedTab == segment ? Color.blue : Color.clear, width: 3.0)
                        .cornerRadius(4.0)
                        .matchedGeometryEffect(id: segment.segmentTitle, in: tabName) // doesn't work
                }
            }
        }
    }
}

The view looks and works fine, but I can’t get the animation to ‘slide’ from one selection to another. It just does a normal SwiftUI fade-in-and-out. I believe I should use matchedGeometryEffect to get the sliding effect, but it doesn’t seem to be working. I’ve tried adding the matchedGeometryEffect to the label around the button as well, but it didn’t work either.

Here’s a preview of what it looks like:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

enter image description here

>Solution :

The Texts don’t need to match geometry – it’s the borders that need to match geometry.

If you use border to make a border, the border is not its own "view", so you can’t modify only the border with matchedGeometryEffect. One workaround is to add the border as the background of either the Text or the Button (these produce slightly different effects – see which you like better).

Button {
    selectedTab = segment
} label: {
    Text(segment.segmentTitle)
        .padding(12.0)
        .background {
            if selectedTab == segment {
                RoundedRectangle(cornerRadius: 4)
                    .stroke(lineWidth: 3)
                    // every border should have the same id!
                    .matchedGeometryEffect(id: "selection", in: tabName)
            }
        }
        
}

or

Button {
    selectedTab = segment
} label: {
    Text(segment.segmentTitle)
        .padding(12.0)
        
}
.background {
    if selectedTab == segment {
        RoundedRectangle(cornerRadius: 4)
            .stroke(Color.accentColor, style: .init(lineWidth: 3))
            .matchedGeometryEffect(id: "selection", in: tabName)
    }
}
.animation(.default, value: selectedTab)
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading