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