How an enum with associated values can conform to the Identifiable protocol

How an enum with associated values can conform to the Identifiable protocol

Here I'll show you how you can easily make an enum conform to the Identifiable protocol and how you can use this in your SwiftUI views.

In my app No Meat Today, I show the Paywall when the user tries to change some settings limited to premium users (or Naomists, as I like to call them).

You can see it below when one tries to change their target meatless days:

  • a banner is shown to be a little bit less "in your face."
  • if the user taps "What?" I show the paywall
  • I scroll to the corresponding premium feature (Adjust your goals) in the carousel.

The setup

SwiftUI

This settings view was displaying 3 sheets using .sheet(isPresented:) and 3 booleans:

  • a picker view when you tap "What is meat?" to give your definition of meat (whether it's just red meat, or if you include fish, poultry, dairy products, or eggs)
  • a slider view when you're a premium user so that you can actually change your target
  • the paywall, using the current context (user wants to change the number of meatless meals/week) to highlight the proper feature

I ended up with a weird problem where the paywall context would be nil because of the way @State and Views work.

Enum with associated values

So instead of my 3 @State booleans, I opted for a single enum, and I used the sheet presenter that uses an item.

@State private var activeSheet: DietSettingsView.ActiveSheet?

โ€ฆ

var body: some View {
    List {
    }
    .sheet(item: $activeSheet) { item in
        switch item {
        case .meatSettings:
            MeatSettingsView()
        case .mealsNumberPicker:
            MealsNumberPicker()
        case .paywall(let featureContext):
            PaywallView(featureContext, source: .settingsDiet)
        }
    }
}

And the ActiveSheet enum now looks like this, thanks to this StackOverflow answer by Rob Napier.

 enum ActiveSheet: Hashable, Identifiable {
	 var id: Self { self }
	 case meatSettings, mealsNumberPicker, paywall(PremiumFeature?)
 }

As you can see, it's an enum with associated values when it comes to the paywall case. The PremiumFeature itself is an enum and can be nil.

This enum is Identifiable because sheet ย requires the item to be identifiable, and it's hashable because that's the easiest way to get an id when the enum has associated values.

Let's dig into that.

Protocol

The Identifiable Protocol

When your enum is not using associated values, you can use each item's hash value, like this:

enum ActiveSheet: Identifiable {
    var id: Int { hashValue }
    case meatSettings, mealsNumberPicker
}

This is because

When you define an enumeration without associated values, it gains Hashable conformance automatically

But if you try to do the same with associated values, you will get an error: "cannot find 'hashValue' in scope."

enum ActiveSheet: Identifiable {
    var id: Int { hashValue }
    case meatSettings, mealsNumberPicker, paywall(PremiumFeature?)
}

The Hashable Protocol

Fortunately, this is pretty easy to fix, and for that, you have 2 options. My PremiumFeatureis an enum, but it would work the same if it were a String or any of the other types in the standard library conforming to Hashable.

Identify each case by its hashValue

PremiumFeature is an enum without associated values, and because of what we've seen above, it gains hashable conformance automatically.

For this reason, Swift knows how to generate a hash(into:) method automatically to conform to the Hashable protocol.

enum ActiveSheet: Hashable, Identifiable {
    var id: Int { hashValue }
    case meatSettings, mealsNumberPicker, paywall(PremiumFeature?)
}

Identify each case by itself

The Identifiable protocol expects a stable ID, the type of which has to be Hashable.

This is where the magic happens โœจ.

  • We declared our enum with associated values as Hashable
  • We didn't have to do anything because Swift was able to generate the proper method on its own
  • Since that enum is Hashable, it can be its own Identifier!

So we can now rewrite the id like this:

enum ActiveSheet: Hashable, Identifiable {
    var id: Self { self }
    case meatSettings, mealsNumberPicker, paywall(PremiumFeature?)
}

Ain't that elegant?

Wrapping up

With this post, we've seen 2 things:

  • that you can use a single @State and sheet to handle all your sheets. While having multiple sheets is supported, I find that it's a cleaner way to deal with them.
  • that an enum with associated values can easily conform to the Identifiable protocol by using the Hashable protocol, provided your associated values are Hashable themselves

Before you leave ๐Ÿ˜‡

Consider doing one or more of these:

  • Follow me on Twitter @sowenjub or maybe even subscribe to this blog's newsletter (sporadic)
  • Download No Meat Today, a companion app for people who want to eat less meat, whatever you put behind "less" and "meat" (and ping me if you want to help translate it)