【SwiftUI】CoreDataでカスタムクラスのデータを保存する！

CoreDataにカスタム（独自）クラスのデータを保存できたら嬉しいですよね！
SwiftUIでの記事が見つからなかったので書いてみます。

型をTransformableにしたり、NSCodingじゃなくてCodingを使うやり方もないかと試行していたのですが（その方が軽いと言っている方も）、とりあえず動いたので共有します。

#環境

Swift 5.5
Xcode 13.0

#１　CoreDataの使用
新規プロジェクト作成時にUse CoreDataにチェックを入れます。

__Booking__ というエンティティを作り、以下のようなAttributeを設定しました。

![スクリーンショット 2022-01-20 12.26.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758221/991fa047-87d9-203f-652e-253691903884.png)

この__detailList__に、カスタムクラスの配列　[Detail] を保存したいと思います。
TypeはBinary Dataにしてください。

#２　カスタムクラスの設定
Detailというカスタムのclassを作っていきます。

```Swift
class Detail: NSObject, NSCoding{
      var ID    = UUID()
      var day: Int = 0
      var free: Bool = false
      
    init(day: Int?, free: Bool?) {
          self.day = day!
          self.free = free!
       }
    
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.day, forKey: "day")
        aCoder.encode(self.free, forKey: "free")
    }
    
    required init?(coder aDecoder: NSCoder) {
        self.day = aDecoder.decodeInteger(forKey: "day") as! Int
        self.free = aDecoder.decodeBool(forKey: "free") as! Bool
    }
}
```

ここで注意ですが

```Swift
        self.day = coder.decodeObject(forKey: "day") 
        self.free = coder.decodeObject(forKey: "free")
```
↑のように、decodeObjectにしているとエラーが出ます！こんなかんじ
> -[NSKeyedUnarchiver decodeObjectForKey:] : value for key ( day ) is not an object. This will become an error in the future.

型に合わせてdecodeInteger, decodeBool等を使用してください！
※文字列をデコードするときは、decodeObject（forKey :)で。

#３　データの保存

```Swift
struct AddingNewBookingView: View {
    @Environment(\.managedObjectContext) private var viewContext
    ...
    var body: some View {
    ...
    }
    func addBooking(){
        let bookings = myBooking //　View内で作成した配列([Bool])
        
        for i in 0..<bookings.count {
            let detail = Detail(day: i, free:  bookings[i])
            detailList.append(detail)
        }
        
        let newBooking = Booking(context: viewContext)
        
        do{
            // ここが大切！
            let detailListData: Data = try NSKeyedArchiver.archivedData(withRootObject: detailList, requiringSecureCoding: false) as Data
            
            newBooking.id = UUID()
            newBooking.name = name
            newBooking.studentNum = Int16(studentNum)!
            newBooking.detailList = detailListData

        } catch let error {
            print(error.localizedDescription)
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

```

#４　データの読み出し

```Swift
struct BookingEditView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @ObservedObject var booking: Booking //親ビューでフェッチしたものを受け取っています
    ...
    var body: some View {
       VStack{
         ...
       }.onAppear(perform: {
                if  let detailListData = booking.detailList {
                do {
                    name = booking.name!
                    studentNum = String(booking.studentNum)
                    let data: Data = detailListData as Data

                    // ここが大切！
                    detailList =  try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [Detail]
                } catch let error{
                    print(error.localizedDescription)
                }
            })
        }
    }
}
```



参考：
https://qiita.com/tky823/items/7f3899dc07191d4b8635
https://stackoverflow-com.translate.goog/questions/39784513/swift-nscoding-decodeobject-with-nil-all-the-time?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=sc