Search code examples
swiftswiftdata

How to allow "dynamic" property type on SwiftData model based on value given to it's another property


I am working on SwiftData model that a field for details. This field can be of various types based on what type model was created with. Right now I have defined possible types and structs for these, but am unable to figure out how to add such "dynamic" type to details field, in a way that conforms to SwiftData restrictions as well. Below is simplified implementation (areas marked with ???? are ones I am not sure about):

import SwiftUI
import SwiftData

// Types
enum DetailsType: Codable {
  case Time
  case Distance
}

struct TimeDetails: Codable {
  let startTime: Date
  let endTime: Date
}

struct DistanceDetails: Codable {
  let distance: Double
}

// Model
@Model class MyModel {
  var type: DetailsType
  var details: ????
  
  init(type: DetailsType, details: ????) {
    self.type = type
    self.details = details
  }
}

Solution

  • You can't use generics with SwiftData models (or inheritance) so I see two approaches

    First one is to add all enum values as properties and make them optional and use the type property to determine what properties to access.

    @Model class MyModel {
        var type: DetailsType
        var distance: DistanceDetails?
        var time: TimeDetails?
    
        private init(type: DetailsType, distance: DistanceDetails? = nil, time: TimeDetails? = nil) {
            self.type = type
            self.distance = distance
            self.time = time
        }
    
        convenience init(distance: DistanceDetails) {
            self.init(type: .Distance, distance: distance)
        }
    
        convenience init(time: TimeDetails) {
            self.init(type: .Time, time: time)
        }
    }
    

    The second one, that I has only tested briefly but that seems to work fine with SwiftData, is to use an enum with associated values.

    enum DetailsType: Codable {
      case Time(TimeDetails)
      case Distance(DistanceDetails)
    }
    

    This would clearly simplify the model so to me it looks like the preferred solution if an associated enum is acceptable and SwiftData doesn't throw any surprises on us :)

    @Model class MyModelAssociated {
        var type: DetailsType
    
        init(type: DetailsType) {
            self.type = type
        }
    }