LoginSignup
5
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-29

経緯

Swift Playgroundsでメモアプリを作る際、SQLiteRealmなどを使おうとしたのですが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
            }
        }
    }
}

完成サンプル

5
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3