I would like to replicate this picker in swiftUI. In particular, I have a button on the bottom left of the screen and when I click it I would like to show different icons (similar to the image below, but vertically). As soon as I click on one of the choices the button should shrink back to the initial form (circle) with the chosen icon.
When closed:
When open:
I am new to this language and to app in general, I tried with a Pop Up menu, but it is not the desired result, for now I have an horizontal segmented Picker.
You can't do this with the built-in Picker
, because it doesn't offer a style like that and PickerStyle
doesn't let you create custom styles (as of the 2022 releases).
You can create your own implementation out of other SwiftUI views instead. Here's what my brief attempt looks like:
Here's the code:
enum SoundOption {
case none
case alertsOnly
case all
}
struct SoundOptionPicker: View {
@Binding var option: SoundOption
@State private var isExpanded = false
var body: some View {
HStack(spacing: 0) {
button(for: .none, label: "volume.slash")
.foregroundColor(.red)
button(for: .alertsOnly, label: "speaker.badge.exclamationmark")
.foregroundColor(.white)
button(for: .all, label: "volume.2")
.foregroundColor(.white)
}
.buttonStyle(.plain)
.background {
Capsule(style: .continuous).foregroundColor(.black)
}
}
@ViewBuilder
private func button(for option: SoundOption, label: String) -> some View {
Button {
withAnimation(.easeOut) {
if isExpanded {
self.option = option
isExpanded = false
} else {
isExpanded = true
}
}
} label: {
Image(systemName: label)
.fontWeight(.bold)
.padding(10)
}
.frame(width: shouldShow(option) ? buttonSize : 0, height: buttonSize)
.opacity(shouldShow(option) ? 1 : 0)
.clipped()
}
private var buttonSize: CGFloat { 44 }
private func shouldShow(_ option: SoundOption) -> Bool {
return isExpanded || option == self.option
}
}
struct ContentView: View {
@State var option = SoundOption.none
var body: some View {
ZStack {
Color(hue: 0.6, saturation: 1, brightness: 0.2)
SoundOptionPicker(option: $option)
.shadow(color: .gray, radius: 3)
.frame(width: 200, alignment: .trailing)
}
}
}