Search code examples
swiftswiftuienumsobservableobject

Accessing an enum in ObservableObject


I'm trying to refactor some code I have and thought I'd be able to move some of the code from globally accessible enum into the ViewModel of the app.

However, when I try to access it from the View it doesn't seem to be accessible or even an option in the autocomplete.

I was wondering if there was a reason why this was the case and if there was something I was missing..

// view
struct MyView: View {
 @StateObject var vm = MyViewModel()
 var body: some View {
  ForEach(vm.MyEnum.allCases, id: \.self) { item in
   ...
  }
 }
}

// view model
final class MyViewModel: ObservableObject {
 enum MyEnum: String, CaseIterable {
  case a, b, c
 }
}

I seem to be able to access everything else in a View Model but when it comes to enums they are not. I tried reading about enums to see why, but most things I come across are mainly "how-to" but nothing deep diving why.


Solution

  • Your problem isn't really about enums, it's more about properties vs types. When you do this:

    final class MyViewModel: ObservableObject {
        enum MyEnum: String, CaseIterable {
            case a, b, c
        }
    }
    

    ... you're defining 2 types.

    1. MyViewModel, which is a class
    2. MyEnum, which is an enum

    It doesn't matter that your MyEnum is nested inside MyViewModel — it's still just a type, not a property.

    Properties are declared with let or var. For example:

    final class MyViewModel: ObservableObject {
        let myEnumArray = [MyEnum.a, MyEnum.b, MyEnum.c] /// create an instance property
    
        enum MyEnum: String, CaseIterable {
            case a, b, c
        }
    }
    
    struct MyView: View {
        @StateObject var vm = MyViewModel()
        var body: some View {
    
                    /// access the instance property here!
            ForEach(vm.myEnumArray, id: \.self) { item in
                /// ...
            }
        }
    }
    

    In the above code I've created a new property, myEnumArray, which you can access with vm.myEnumArray. This will work.

    Now what if you want to access MyEnum's allCases? Note that this property is static — the property belongs to the type itself and not an instance of the type. That's why it's not showing up in your autocomplete. Example:

    MyViewModel.MyEnum.allCases /// Works! The type is `MyViewModel.MyEnum` and I'm accessing the static `allCases` property.
    
    @StateObject var vm = MyViewModel()
    vm.MyEnum.allCases /// NO! the `MyEnum` property doesn't exist inside `vm`.
    

    So your code really has 2 problems.

    1. You think MyEnum is a property. No, it's a type!
    2. Because MyEnum is a type, you can't access it with vm.MyEnum (that's how you access an instance property). You need to do MyViewModel.MyEnum.

    Here's the fixed code:

    struct MyView: View {
        var body: some View {
            ForEach(MyViewModel.MyEnum.allCases, id: \.self) { item in
                /// ...
            }
        }
    }