Firstly, I want to say the value updates, as I print the value(s) in the console and sure, tapping each option prints as expected. However, for UI purposes, I have added a few visual components/styles to help with indicating the current selection.
My enum:
enum Gender : Int, CaseIterable {
case men = 0
case women = 1
private var cases: [String] {
["Men", "Women"]
}
func toString() -> String {
cases[self.rawValue]
}
}
This is the view that helps with the logic for displaying the data and indexing the data
struct GenderTabMenuIndicator: View {
var category: Gender
var body: some View {
HStack {
ForEach(0..<Gender.allCases.count) { cat in
GenderTabMenuIndicatorItem(category: Gender.allCases[cat], isActive: Gender.allCases[cat] == category)
}
}.frame(width: UIScreen.main.bounds.width * 0.75)
}
}
And this is simply the view. However, the isActive
does not seem to switch from the initial selection/value.
struct GenderTabMenuIndicatorItem: View {
@State var category: Gender
@State var isActive: Bool
var body: some View {
VStack(spacing: 0) {
Text(category.toString().uppercased())
.onTapGesture {
print("tapped")
print(category.toString())
}
.font(.system(size: 18, weight: isActive ? .bold : .light))
.frame(maxWidth: .infinity)
.layoutPriority(1)
if isActive {
Rectangle()
.frame(width: 50, height: 2, alignment: .center)
}
}.foregroundColor(Color(SYSTEM_FONT_COLOUR))
}
}
This is how I'm declaring/using all these components in my actual view:
@State private var selected_tab: Gender = .men
VStack {
GenderTabMenuIndicator(category: selected_tab)
}
I don't know if it's the ForEach loop, but that at the same time does print the corresponding case that's passed. I have used @State where I can to update the view, but to no luck.
Any help would be appreciated!
@State
is used for private changes, inside withing a view
to update changes back and forth from sub view you have to use @Binding
we can access/pass binding of @State
using $
, ex :- $yourStateVariable
// If you change the type of this `enum` to `String`, you can use
// `.rawValue.capitalized` instead of manually mapping all cases
// to create a `toString` method/computed property. But assuming
// you absolutely need to have `Int` as the `RawValue` type, you
// should instead utilize a switch statement because it gives you
// compile-time checking/safety if the order of these values ever
// changes or if new cases are ever added, as I have done here.
enum Gender : Int, CaseIterable {
case men = 0
case women = 1
func toString() -> String {
switch self {
case .men: "Men"
case .women: "Women"
}
}
}
struct ContentView: View {
@State private var selectedGender: Gender = .men
var body: some View {
VStack {
Text("Selected: \(selectedGender.toString())")
GenderTabMenuIndicator(
selectedGender: $selectedGender
)
}
}
}
// If you were to change the `Gender` `enum` `RawValue` type to `String`,
// you could then make this view type reusable by making it take a generic type
// and then it would work for any `enum` with a `String` as its `RawValue` type.
struct GenderTabMenuIndicator: View {
@Binding var selectedGender: Gender
var body: some View {
HStack {
ForEach(Gender.allCases) { gender in
GenderTabMenuIndicatorItem(
gender: gender,
selection: $selectedGender
)
}
} // NOTE: Apple advises not to use UIScreen for SwiftUI
.frame(width: UIScreen.main.bounds.width * 0.75)
}
}
// Same here:
// If you were to change the `Gender` `enum` `RawValue` type to `String`,
// you could then make this view type reusable by making it take a generic type
// and then it would work for any `enum` with a `String` as its `RawValue` type.
struct GenderTabMenuIndicatorItem: View {
var category: Gender
@Binding var selection: Gender
var isSelected: Bool { selection == gender }
var body: some View {
VStack(spacing: 0) {
Text(gender.toString().uppercased())
.onTapGesture {
selection = category
}
.font(.system(
size: 18,
weight: isSelected ? .bold : .light
))
.frame(maxWidth: .infinity)
.layoutPriority(1)
if isSelected {
Rectangle()
.frame(width: 50, height: 2, alignment: .center)
}
}
}
}
enum Gender: String, CaseIterable {
case man
case woman
}
enum MaritalStatus: String, CaseIterable {
case single
case married
case separated
case divorced
}
struct ContentView: View {
@State private var gender = Gender.man
@State private var maritalStatus = MaritalStatus.single
var body: some View {
VStack {
Text("Gender: \(gender.rawValue.capitalized)")
TabMenuIndicator(selectedItem: $gender)
Text("Marital Status: \(maritalStatus.rawValue.capitalized)")
TabMenuIndicator(selectedItem: $maritalStatus)
}
}
}
struct TabMenuIndicator<ItemType: RawRepresentable, CaseIterable, Equatable>: View {
@Binding var selectedItem: ItemType
var body: some View {
HStack {
ForEach(ItemType.allCases) { anItem in
TabMenuItem(
item: anItem,
selectedItem: $selectedItem
)
}
} // NOTE: Apple advises not to use UIScreen for SwiftUI
.frame(width: UIScreen.main.bounds.width * 0.75)
}
}
struct TabMenuItem<ItemType: RawRepresentable, CaseIterable, Equatable>: View {
var item: ItemType
@Binding var selectedItem: ItemType
var isSelected: Bool { selectedItem == item }
var body: some View {
VStack(spacing: 0) {
Text(item.rawValue.capitalized)
.onTapGesture {
selectedItem = item
}
.font(.system(
size: 18,
weight: isSelected ? .bold : .light
))
.frame(maxWidth: .infinity)
.layoutPriority(1)
if isSelected {
Rectangle()
.frame(width: 50, height: 2, alignment: .center)
}
}
}
}