Expandable Custom Segmented Picker in SwiftUI

I’m trying to create an expandable segmented picker in SwiftUI, I’ve done this so far :

struct CustomSegmentedPicker: View {
    
    @Binding var preselectedIndex: Int
    
    @State var isExpanded = false
    
    var options: [String]
    let color = Color.orange

    var body: some View {
        HStack {
            ScrollView(.horizontal) {
                HStack(spacing: 4) {
                    ForEach(options.indices, id:\.self) { index in
                        let isSelected = preselectedIndex == index
                        ZStack {
                            Rectangle()
                                .fill(isSelected ? color : .white)
                                .cornerRadius(30)
                                .padding(5)
                                .onTapGesture {
                                    preselectedIndex = index
                                    withAnimation(.easeInOut(duration: 0.5)) {
                                        isExpanded.toggle()
                                    }
                                }
                        }
                        .shadow(color: Color(UIColor.lightGray), radius: 2)
                        .overlay(
                            Text(options[index])
                                .fontWeight(isSelected ? .bold : .regular)
                                .foregroundColor(isSelected ? .white : .black)
                        )
                        .frame(width: 80)
                    }
                }
            }
            .transition(.move(edge: .trailing))
            .frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
            .background(Color(UIColor.cyan))
            .cornerRadius(30)
            .clipped()
            Spacer()
        }
    }
}

Which gives this result :

GIF showing the result of the expandable picker

Now, when it contracts, how can I keep showing the item selected and hide the others ? (for the moment, the item on the left is always shown when not expanded)

>Solution :

Nice job. You can add an .offset() to the contents of the ScollView, which shifts it left depending on the selection:

enter image description here

        HStack {
            ScrollView(.horizontal) {
                HStack(spacing: 4) {
                    ForEach(options.indices, id:\.self) { index in
                        let isSelected = preselectedIndex == index
                        ZStack {
                            Rectangle()
                                .fill(isSelected ? color : .white)
                                .cornerRadius(30)
                                .padding(5)
                                .onTapGesture {
                                    preselectedIndex = index
                                    withAnimation(.easeInOut(duration: 0.5)) {
                                        isExpanded.toggle()
                                    }
                                }
                        }
                        .shadow(color: Color(UIColor.lightGray), radius: 2)
                        .overlay(
                            Text(options[index])
                                .fontWeight(isSelected ? .bold : .regular)
                                .foregroundColor(isSelected ? .white : .black)
                        )
                        .frame(width: 80)
                    }
                }
                .offset(x: isExpanded ? CGFloat(-84 * preselectedIndex) : 0) // <<< here
            }
            .transition(.move(edge: .trailing))
            .frame(width: isExpanded ? 80 : CGFloat(options.count) * 80 + 10, height: 50)
            .background(Color(UIColor.cyan))
            .cornerRadius(30)
            .clipped()
            Spacer()
        }

Leave a Reply