How to persist custom enum with SwiftData? – Swift

by
Ali Hasan
swift-concurrency swift-data swiftui-animation

Quick Fix: The issue has been fixed since iOS version 17 beta 7. Now the code below works correctly.

enum ItemType: Int, Codable { 
    case foo = 0 
    case bar = 1 
} 

@Model 
final public class Item { 

    var type: ItemType 

    init(type: ItemType) { 
        self.type = type 
    } 

} 

struct DetailView: View { 

    @Bindable var item: Item 

    var body: some View { 
        Picker("Type", selection: $item.type) { 
            Text("Foo").tag(ItemType.foo) 
            Text("Bar").tag(ItemType.bar) 
        } 
        .pickerStyle(.segmented) 
    } 

}

The Problem:

When using SwiftData to store a custom enum, changes made to the enum value in the UI are not persisted when the app is relaunched. How can this issue be resolved to ensure that the enum changes are persisted?

The Solutions:

Solution 1: Fixed in iOS 17 Beta 7

The issue with persisting custom enums using SwiftData has been resolved in iOS 17 beta 7 and above. The following updated code should now function correctly:

enum ItemType: Int, Codable {
    case foo = 0
    case bar = 1
}

@Model
final public class Item {
    var type: ItemType
    
    init(type: ItemType) {
        self.type = type
    }
}

struct DetailView: View {
    @Bindable var item: Item
    
    var body: some View {
        Picker("Type", selection: $item.type) {
            Text("Foo").tag(ItemType.foo)
            Text("Bar").tag(ItemType.bar)
        }
        .pickerStyle(.segmented)
    }
}

Solution 2: Use a Transient Property and a RawValue Attribute

To persist a custom enum with SwiftData, consider using a transient property and a raw value attribute. The transient property provides a getter and setter to convert the enum to and from its raw value. The raw value attribute stores the actual value in the database.

Here’s an example:

enum SomeType: Int, Codable {
    case foo = 0
    case bar = 1
}

@Model
final public class Item {
    
    @Transient var type: SomeType {
        get { SomeType(rawValue: _type)! }
        set { _type = newValue.rawValue }
    }
    
    @Attribute var _type: SomeType.RawValue
    
}

Solution 3: Nested enums

You can define your enum inside the model as a nested type. This will allow SwiftData to persist the enum’s value correctly.

@Model
final public class Item {

    @Column(unique: true)
    public var id: UUID // For simplicity

    @Column
    var type: ItemType

    enum ItemType: Int, Codable {
        case foo = 0
        case bar = 1
    }

    init(type: ItemType) {
        self.type = type
    }

}

Q&A

How to fix iOS version 16 SwiftUI – SwiftData enum issue?

The issue has been fixed in iOS 17 beta 7 or newer.

Is there a workaround for the iOS 16 SwiftUI – SwiftData enum issue?

You can create a Transient wrapper to get around the problem for now.

Is enum placed inside the class also having the same issue?

The issue exists even when the enum is placed inside the class.

Video Explanation:

The following video, titled "WWDC23 SwiftData: how to use enums in SwiftData models ...", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

WWDC introduced SwiftData, a new way of persisting data, basically CoreData on steriod and in SwiftUI's cloths, but this early beta, enum ...