Building a custom picker
segmented style
segmented style
Rotterdam, 19 juni 2024
The segmented picker style does not work well with both an Image and Text. Let’s create our own.
We start of by creating a hardcoded version of it:
struct HardCodedPicker: View {
@State var selection = "car"
@Namespace private var animation
var body: some View {
HStack(spacing: 0) {
ForEach(["car", "bicycle", "airplane"], id: \.self) { item in
ZStack {
Group { /// in order to use `.animation(_,value:)`, it needs to be added to a element who's existence is not dependent on the same property as `value`.
if item == selection {
RoundedRectangle(cornerRadius: 5)
.fill(.white)
.shadow(color: Color(white: 0.5, opacity: 0.4), radius: 3, x: 0, y: 0)
.padding(2)
}
}
.animation(.spring().speed(1.5), value: selection)
.matchedGeometryEffect(id: "id", in: animation) /// to animate the rectangle
VStack {
Image(systemName: item)
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(width: 20, height: 20)
Text(item.capitalized)
.font(.system(size: 14))
.lineLimit(2)
}
.padding(8)
.onTapGesture {
selection = item
}
}
.frame(maxWidth: .infinity)
.onTapGesture {
selection = item
}
}
}
.background(Color.init(white: 0.9))
.cornerRadius(8)
.frame(maxHeight: 80)
}
}
#Preview {
HardCodedPicker()
.padding()
}

You can then make it generic like this:
struct TextAndIconPicker<Data>: View where Data: Hashable {
public let sources: [Data]
@Binding public var selection: Data
let imageForSource: (Data) -> Image
let titleForSource: (Data) -> String
@Namespace private var animation
public init(
sources: [Data],
imageForSource: @escaping (Data) -> Image,
titleForSource: @escaping (Data) -> String,
selection: Binding<Data>
) {
self.sources = sources
self.imageForSource = imageForSource
self.titleForSource = titleForSource
_selection = selection
}
var body: some View {
HStack(spacing: 0) {
ForEach(sources, id: \.self) { item in
ZStack {
Group {
if item == selection {
RoundedRectangle(cornerRadius: 5)
.fill(.white)
.shadow(color: Color(white: 0.5, opacity: 0.4), radius: 3, x: 0, y: 0)
.padding(2)
}
}
.animation(.spring().speed(1.5), value: selection)
.matchedGeometryEffect(id: "id", in: animation)
VStack {
imageForSource(item)
.resizable()
.aspectRatio(1, contentMode: .fit)
.frame(width: 20, height: 20)
.padding(.top, 4)
.foregroundColor(item == selection ? .black : .gray)
Text(titleForSource(item))
.font(.system(size: 13, weight: item == selection ? .semibold : .light))
.lineLimit(2)
}
.padding(8)
}
.frame(maxWidth: .infinity)
.onTapGesture {
selection = item
}
}
}
.background(Color.init(white: 0.9))
.cornerRadius(8)
.frame(maxHeight: 80)
}
}
enum TransportationType: String, CaseIterable, CustomStringConvertible {
var description: String { self.rawValue }
case car, bicycle, airplane
}
fileprivate struct TextAndIconPicker_Preview: View {
@State private var defaultTransportationType: TransportationType = .car
var body: some View {
TextAndIconPicker(
sources: TransportationType.allCases,
imageForSource: { Image(systemName: $0.description) },
titleForSource: { $0.description.capitalized },
selection: $defaultTransportationType)
}
}
#Preview {
TextAndIconPicker_Preview()
}
That’s it!
IT gerelateerd onderzoek & advies
iPhone apps
iPad apps
App developer iOS
Heeft u vragen? Neem contact met me op!
Heeft u vragen? U kunt gebruik maken van het contactformulier.
Adres:
Korfoepad 79
3059XD Rotterdam
Tel. +316 19 532 770
KvK: 63601400


