CoreDataにカスタム(独自)クラスのデータを保存できたら嬉しいですよね!
SwiftUIでの記事が見つからなかったので書いてみます。
型をTransformableにしたり、NSCodingじゃなくてCodingを使うやり方もないかと試行していたのですが(その方が軽いと言っている方も)、とりあえず動いたので共有します。
#環境
Swift 5.5
Xcode 13.0
#1 CoreDataの使用
新規プロジェクト作成時にUse CoreDataにチェックを入れます。
Booking というエンティティを作り、以下のようなAttributeを設定しました。
この__detailList__に、カスタムクラスの配列 [Detail] を保存したいと思います。
TypeはBinary Dataにしてください。
#2 カスタムクラスの設定
Detailというカスタムのclassを作っていきます。
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
}
}
ここで注意ですが
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 :)で。
#3 データの保存
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)")
}
}
}
#4 データの読み出し
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