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 :
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)
2
Answers
Nice job. You can add an
.offset()
to the contents of theScollView
, which shifts it left depending on the selection:Here is another approach using
.matchedGeometryEffect
, which can handle different label widths without falling back toGeometryReader
.Based on expansionState it either draws only the selected item or all of them and
.matchedGeometryEffect
makes sure the animation goes smooth.