【Swift】コードのみでCoreDataを実装

# 経緯
Swift Playgroundsでメモアプリを作る際、[SQLite](https://github.com/stephencelis/SQLite.swift)や[Realm](https://github.com/realm/realm-swift)などを使おうとしたのですがObjective-Cを含んでいる為、使用できませんでした。
という事でCoreDataを使おうと思います。
しかし、Swift PlaygroundsではCoreDataを設定する事ができない為、コードだけで実装します。

# 作る物
簡単なメモアプリを作ってみようと思います。

# CoreData設定
```MemoModel.swift
import CoreData
import SwiftUI

@objc(Memo)
class Memo: NSManagedObject {
    @NSManaged var index: Int
    @NSManaged var title: String
    @NSManaged var text: String
}

extension Memo: Identifiable {
    var id: Int {
        index
    }
}
```

```PersistenceController.swift
import CoreData
import SwiftUI

class PersistenceController {
    static let shared = PersistenceController()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        let memoEntity = NSEntityDescription()
        memoEntity.name = "Memo"
        memoEntity.managedObjectClassName = "Memo"

        let indexAttribute = NSAttributeDescription()
        indexAttribute.name = "index"
        indexAttribute.type = .integer64
        memoEntity.properties.append(indexAttribute)

        let titleAttribute = NSAttributeDescription()
        titleAttribute.name = "title"
        titleAttribute.type = .string
        memoEntity.properties.append(titleAttribute)

        let textAttribute = NSAttributeDescription()
        textAttribute.name = "text"
        textAttribute.type = .string
        memoEntity.properties.append(textAttribute)

        let model = NSManagedObjectModel()
        model.entities = [memoEntity]

        let container = NSPersistentContainer(name: "MemoModel", managedObjectModel: model)

        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }

        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("failed with: \(error.localizedDescription)")
            }
        }

        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy

        container.viewContext.automaticallyMergesChangesFromParent = true
        self.container = container
    }
}
```

```ViewModel.swift
import SwiftUI
import CoreData

class ViewModel: ObservableObject {
    @Published var index: Int = 0
    @Published var title: String = ""
    @Published var text: String = ""
    @Published var isNewMemo = false
    @Published var Item: Memo!

    
    //編集確定 or 新規作成
    func CreateMemo(context : NSManagedObjectContext) {
        if Item != nil {
            Item.index = UnixTime()
            Item.title = title
            Item.text = text
            try! context.save()

            Item = nil
            isNewMemo.toggle()
            index = UnixTime()
            title = ""
            text = ""
            return
        }
        let newMemo = Memo(context: context)
        newMemo.index = UnixTime()
        newMemo.title = title
        newMemo.text = text
        do {
            try context.save()
            isNewMemo.toggle()
            index = UnixTime()
            title = ""
            text = ""
        }
        catch {
            print(error.localizedDescription)
        }
    }

    //編集時
    func EditMemo(item: Memo) {
        Item = item
        index = item.index
        title = item.title
        text = item.text
        isNewMemo.toggle()
    }
    
    //IDの重複を避ける為,UnixTimeをIDに適用
    func UnixTime() -> Int {
        let dt = Date()
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "yyyy-MM-dd HH:mm:ss", options: 0, locale: Locale(identifier: "ja_JP"))
        let date: Date? = dateFormatter.date(from: dateFormatter.string(from: dt))
        let dateUnix: TimeInterval? = date?.timeIntervalSince1970

        return Int(dateUnix!)
    }
}
```

# Viewに紐付け
```MemoApp.swift
import SwiftUI

@main
struct MemoApp: App {
    let persistenceController = PersistenceController.shared
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}
```

# View
```ContentView.swift
import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = ViewModel()
    @Environment(\.managedObjectContext) private var context
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Memo.index, ascending: true)], animation: .spring()) private var results: FetchedResults<Memo>
    @State var TrashMemo: FetchedResults<Memo>.Element = FetchedResults<Memo>.Element()
    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            if results.isEmpty {
                VStack {
                    Spacer()
                    HStack {
                        Spacer()
                        Text("メモがありません。")
                            .fontWeight(.black)
                            .font(.system(size: 35))
                        Spacer()
                    }
                    Spacer()
                }
            } else {
                List(results) { memo in
                    Button(action: {
                        viewModel.EditMemo(item: memo)
                    }) {
                        HStack {
                            Text(memo.title)
                            Spacer()
                            Button(action: {
                                context.delete(memo)
                                try! context.save()
                            }) {
                                Image(systemName: "xmark")
                            }
                            .padding(.trailing, 10)
                            .buttonStyle(PlainButtonStyle())
                        }
                    }
                }
            }
            Button(action: {
                viewModel.Item = nil
                viewModel.title = ""
                viewModel.text = ""
                viewModel.isNewMemo.toggle()
            }) {
                ZStack {
                    Circle()
                        .frame(width: 60, height: 60)
                        .foregroundColor(.blue)
                        .padding()
                    Image(systemName: "plus")
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 40, height: 40)
                        .foregroundColor(.white)
                }
            }
        }
        .fullScreenCover(isPresented: $viewModel.isNewMemo) {
            MemoView(viewModel: viewModel)
        }
    }
}
```

```MemoView.swift
import SwiftUI

struct MemoView: View {
    @ObservedObject var viewModel : ViewModel
    @Environment(\.managedObjectContext) var context
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        GeometryReader { geo in
            VStack {
                HStack {
                    Button(action: {
                        self.presentationMode.wrappedValue.dismiss()
                    }) {
                        Text("Back")
                    }
                    Spacer()
                    Button(action: {
                        viewModel.CreateMemo(context: context)
                    }) {
                        Text("Save")
                            .foregroundColor(viewModel.title == "" ? Color.gray : .blue)
                    }
                    .disabled(viewModel.title == "" ? true : false)
                }
                .padding(.vertical)
                .padding(.horizontal)
                ZStack(alignment: .topLeading) {
                    if viewModel.title.isEmpty {
                        Text("Title")
                            .font(.system(size: 30, weight: .black, design: .default))
                            .foregroundColor(.gray)
                            .padding(.top, 2)
                            .padding(.leading, 3)
                    }
                    TextField("", text: $viewModel.title)
                        .font(.system(size: 30, weight: .black, design: .default))
                }
                .padding(.horizontal)

                ZStack(alignment: .topLeading) {
                    if viewModel.text.isEmpty {
                        Text("Text")
                            .font(.system(size: 15, weight: .bold, design: .default))
                            .foregroundColor(.gray)
                            .padding(.top, 7)
                            .padding(.leading, 5)
                    }
                    TextEditor(text: $viewModel.text)
                        .font(.system(size: 15, weight: .bold, design: .default))
                        .frame(height: geo.size.height-100, alignment: .leading)
                        .fixedSize(horizontal: false, vertical: true)
                }
                .padding(.top)
                .padding(.horizontal)
                Spacer()
            }
            .onAppear() {
                UITextView.appearance().backgroundColor = .clear
            }
            .onDisappear() {
                UITextView.appearance().backgroundColor = nil
            }
        }
    }
}
```

# 完成サンプル
https://github.com/SNQ-0/CoreData-Memo