I have two pickers, one is for priority and the other one is for the selection of a date. I want to change the background of both pickers based on the selection of the priority picker, as example if the priority is high the picker should be red, middle orange and so on... Currently I can only manually set the background color of the picker with an initializer. This is my code for the View:
struct AddWunschSheetView: View {
@State private var titelTextField: String = ""
@State private var kostenTextField: String = ""
@State private var sliderValue = 1.0
@State private var amount = 0.5
@State private var prioPicker = 1
@State private var selection = 1
@Environment(\.dismiss) var dismiss
init() {
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor.red
let attributes: [NSAttributedString.Key:Any] = [
.foregroundColor: UIColor.white
]
UISegmentedControl.appearance().setTitleTextAttributes(attributes, for: .selected)
}
var body: some View {
NavigationStack {
Form {
Section("Artikeldetails") {
TextField("", text: $titelTextField, prompt: Text("Titel des Artikels"))
}
Section("Priority") {
Picker("", selection: $prioPicker) {
Text("Dringend").tag(1)
Text("Hoch").tag(2)
Text("Mittel").tag(3)
Text("Niedrig").tag(4)
}
.pickerStyle(.segmented)
}
Picker("", selection: $selection) {
Text("Tag").tag(1)
Text("Woche").tag(2)
Text("Monat").tag(3)
}
.pickerStyle(.segmented)
if selection == 1 {
Slider(value: $sliderValue, in: 1...31, step: 1.0, minimumValueLabel: Text("1"), maximumValueLabel: Text("31"), label: {})
.padding(.horizontal)
} else if selection == 2 {
Slider(value: $sliderValue, in: 1...52, step: 1.0, minimumValueLabel: Text("1"), maximumValueLabel: Text("52"), label: {})
.padding(.horizontal)
} else if selection == 3 {
Slider(value: $sliderValue, in: 1...12, step: 1.0, minimumValueLabel: Text("1"), maximumValueLabel: Text("12"), label: {})
.padding(.horizontal)
}
HStack {
Spacer()
Text( String(format: "%.0f", sliderValue))
if selection == 1 {
Text("Tage bis zum Ereigniss")
} else if selection == 2 {
Text("Wochen bis zum Ereigniss")
} else if selection == 3 {
Text("Monate bis zum Ereigniss")
}
Spacer()
}
HStack {
VStack {
Text("Kosten")
.bold()
TextField("", text: $kostenTextField, prompt: Text("0€"))
.keyboardType(.numberPad)
.padding(.top)
}
Spacer()
if prioPicker == 1 {
Gauge(value: amount) {
Text("50%")
}
.gaugeStyle(.accessoryCircularCapacity)
.tint(.red)
} else if prioPicker == 2 {
Gauge(value: amount) {
Text("50%")
}
.gaugeStyle(.accessoryCircularCapacity)
.tint(.orange)
} else if prioPicker == 3 {
Gauge(value: amount) {
Text("50%")
}
.gaugeStyle(.accessoryCircularCapacity)
.tint(.blue)
} else if prioPicker == 4 {
Gauge(value: amount) {
Text("50%")
}
.gaugeStyle(.accessoryCircularCapacity)
.tint(.green)
}
}
.padding(.horizontal)
}
.navigationTitle("Neuen Artikel hinzufügen")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Save") {
dismiss()
}
}
}
}
}
}
The UI looks like this:
I have already tried to customize the init with if-statements, I've tried to customize the sections, pickers,... I am not even sure if this is possible.
It is difficult to add styling to a segmented Picker
, as you have discovered. However, what you could do is use the same technique as shown in the answer to Segmented picker in iOS - handle tap on already selected item (it was my answer):
.matchedGeometryEffect
.matchedGeometryEffect
.spring(duration: 0.28)
corresponds pretty closely.The .matchedGeometryEffect
requires a namespace:
@Namespace private var ns
Your previous init
with legacy UI calls is no longer needed
// init() {
// UISegmentedControl.appearance().selectedSegmentTintColor = UIColor.red
//
// let attributes: [NSAttributedString.Key:Any] = [
// .foregroundColor: UIColor.white
// ]
// UISegmentedControl.appearance().setTitleTextAttributes(attributes, for: .selected)
// }
I would suggest creating an enum for your priority levels, something like:
enum Priority: CaseIterable {
case dringend
case hoch
case mittel
case niedrig
var asString: String {
"\(self)".capitalized
}
var color: Color {
switch self {
case .dringend: .red
case .hoch: .orange
case .mittel: .blue
case .niedrig: .green
}
}
}
This enum should be used for the state variable that holds the priority selection:
@State private var prioPicker = Priority.dringend
The Picker
can then be assembled as follows:
Section("Priority") {
Picker("", selection: $prioPicker) {
ForEach(Priority.allCases, id: \.self) { priority in
Text(priority.asString)
}
}
.pickerStyle(.segmented)
.background {
// A row of placeholders
HStack(spacing: 0) {
ForEach(Priority.allCases, id: \.self) { priority in
Color.clear
.matchedGeometryEffect(id: priority, in: ns, isSource: true)
}
}
}
.overlay {
ZStack {
prioPicker.color
Text(prioPicker.asString)
.font(.footnote)
.fontWeight(.semibold)
.foregroundStyle(.white)
}
.matchedGeometryEffect(id: prioPicker, in: ns, isSource: false)
.clipShape(RoundedRectangle(cornerRadius: 7))
.animation(.spring(duration: 0.28), value: prioPicker)
}
}
The Gauge
can also be simplified:
Gauge(value: amount) {
Text("50%")
}
.gaugeStyle(.accessoryCircularCapacity)
.tint(prioPicker.color)
The same approach can also be used for the segmented picker used to select the period, but giving it the color from the selected priority:
enum Period: CaseIterable {
case tag
case woche
case monat
var asString: String {
"\(self)".capitalized
}
var maxSliderVal: Int {
switch self {
case .tag: 31
case .woche: 52
case .monat: 12
}
}
}
@State private var selection = Period.tag
Picker("", selection: $selection) {
ForEach(Period.allCases, id: \.self) { period in
Text(period.asString)
}
}
.pickerStyle(.segmented)
.background {
HStack(spacing: 0) {
ForEach(Period.allCases, id: \.self) { period in
Color.clear
.matchedGeometryEffect(id: period, in: ns, isSource: true)
}
}
}
.overlay {
ZStack {
prioPicker.color
Text(selection.asString)
.font(.footnote)
.fontWeight(.semibold)
.foregroundStyle(.white)
}
.matchedGeometryEffect(id: selection, in: ns, isSource: false)
.clipShape(RoundedRectangle(cornerRadius: 7))
.animation(.spring(duration: 0.28), value: selection)
}
Slider(
value: $sliderValue,
in: 1...Double(selection.maxSliderVal),
step: 1.0,
minimumValueLabel: Text("1"),
maximumValueLabel: Text("\(selection.maxSliderVal)"),
label: {}
)
.padding(.horizontal)
.onChange(of: selection.maxSliderVal) { oldVal, newVal in
sliderValue = min(sliderValue, Double(newVal))
}
HStack {
Spacer()
Text( String(format: "%.0f", sliderValue))
if selection == .tag {
Text("Tage bis zum Ereigniss")
} else if selection == .woche {
Text("Wochen bis zum Ereigniss")
} else if selection == .monat {
Text("Monate bis zum Ereigniss")
}
Spacer()
}
Here's how it all looks:
It is interesting to note, that when the RoundedRectangle
clip shape is applied to the ZStack
, the shape automatically adopts square corners for the inner edges. However, if you would prefer the shape to have four rounded corners in all positions, you just need to move the .clipShape
to the color instead.