Reputation: 11275
I've run into my first complex control in SwiftUI. It's a date frequency picker where the user can choose the day of the week for weekly recurring events, or the day of the month for monthly recurring events. I have @State variables to keep track of weekly vs monthly in my segment controller and also which day of the week or month the user has selected. When these values change I want to update my @Binding frequency. The code below is working, but feels dirty to me to update the frequency in the body call.
enum Frequency {
case weekly(on: Int)
case monthly(on: Int)
}
struct FrequencyView : View {
@Binding var frequency: Frequency
@State var segment: Int
@State var weekDay: Int
@State var monthDay: Int
var displayFrequency: Frequency {
if segment == 0 {
return .weekly(on: weekDay)
} else {
return .monthly(on: monthDay)
}
}
var body: some View {
frequency = displayFrequency
return NavigationView {
VStack {
Text(displayFrequency.long).fontWeight(.bold)
SegmentedControl(selection: $segment) {
Text("Weekly").tag(0)
Text("Monthly").tag(1)
}.padding()
if segment == 0 {
Text("Day of the week").foregroundColor(Acorns.stone)
ExpensesWeeklyPickerView(day: $weekDay).padding()
} else {
Text("Day of the month").foregroundColor(Acorns.stone)
ExpensesMonthlyPickerView(day: $monthDay).padding()
}
Spacer()
}.navigationBarTitle(Text("Frequency"))
}
}
}
Was curious if anyone has found a better way to do this?
Upvotes: 4
Views: 3494
Reputation: 385500
First, add accessors for weekDay
and monthDay
to Frequency
:
extension Frequency {
var weekDay: Int {
get {
if case .weekly(on: let day) = self { return day }
else { return 0 }
}
set { self = .weekly(on: newValue) }
}
var monthDay: Int {
get {
if case .monthly(on: let day) = self { return day }
else { return 0 }
}
set { self = .monthly(on: newValue) }
}
}
Now you can use these accessors to create bindings for your subviews:
if segment == 0 {
Text("Day of the week").foregroundColor(Acorns.stone)
ExpensesWeeklyPickerView(day: $frequency.weekDay).padding()
} else {
Text("Day of the month").foregroundColor(Acorns.stone)
ExpensesMonthlyPickerView(day: $frequency.monthDay).padding()
}
To change the frequency when a segment is clicked, you need to give the SegmentedControl
a binding that changes the frequency. I would introduce another (private) enum
to use for the segment:
fileprivate enum Segment: Int, Hashable {
case weekly
case monthly
}
Then add an extension to Frequency
to translate to/from Segment
:
extension Frequency {
fileprivate var segment: ContentView.Segment {
get {
switch self {
case .weekly(on: _): return .weekly
case .monthly(on: _): return .monthly
}
}
set {
switch newValue {
case .weekly: self = .weekly(on: 0)
case .monthly: self = .monthly(on: 0)
}
}
}
}
Finally, get rid of your segment
property and use frequency.segment
instead:
SegmentedControl(selection: $frequency.segment) {
Text("Weekly").tag(Segment.weekly)
Text("Monthly").tag(Segment.monthly)
}.padding()
if frequency.segment == .weekly {
...
Upvotes: 2