Core Data数据库在SwiftUI中使用起来不难,不过很多文章写的都是Swift中的使用方法,很少有纯SwiftUI的Core Data数据库的使用方法。我就想着自己来整理一下,写出来,如果有错误,欢迎指正~

开始前的准备

如果是新建项目的话,创建项目的时候选择上“Use Core Data”选中“Use Core Data”
然后点击Next进入项目。把ContentView.swift文件中苹果预设的代码删光,改成以下代码:

struct ContentView: View {
    //我们可以要求它提供当前托管对象内容,并将其分配给一个属性供我们使用
    @Environment(\.managedObjectContext) private var viewContext

    var body: some View {
        VStack {
            Button("Add") {
            //这里我们等会添加按钮的功能
            }
            List {
            //显示Core Data数据库内容的列表
            }
        }
    }
}

然后进入Persistence.swift文件,将第16~19行的以下代码删除掉,并且空出来位置:

for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
}

最后进入项目名称.xcdatamodeld文件,选中“ENTITIES”下预先给定的“Item”,按Delete键删除。删除预先给定的Item

如果是在已有项目中添加Core Data,按住CMD+N键,新建一个项目名称.xcdatamodeld文件,如下:
在这里插入图片描述

然后再新建一个名为Persistence.swift的swift文件,如下:在这里插入图片描述
然后将以下代码覆盖进去(需要注意的是,要将container = NSPersistentContainer(name: "Test")里的Test改成你的项目名称):

import CoreData

struct PersistenceController {
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        //留出来供后面使用的区域
        
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "Test")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.

                /*
                Typical reasons for an error here include:
                * The parent directory does not exist, cannot be created, or disallows writing.
                * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                * The device is out of space.
                * The store could not be migrated to the current model version.
                Check the error message to determine what the actual problem was.
                */
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}

然后在需要调用数据库的View的struct中写入以下代码:

//我们可以要求它提供当前托管对象内容,并将其分配给一个属性供我们使用
@Environment(\.managedObjectContext) private var viewContext

不过为了方便介绍,假设需要调用数据的View的代码如下:

struct ContentView: View {
    //我们可以要求它提供当前托管对象内容,并将其分配给一个属性供我们使用
    @Environment(\.managedObjectContext) private var viewContext

    var body: some View {
        VStack {
            Button("Add") {
            //这里我们等会添加按钮的功能
            }
            List {
            //显示Core Data数据库内容的列表
            }
        }
    }
}

这样准备工作就做好了。

添加Entity

我们选中项目名称.xcdatamodeld文件,然后点击下面的“Add Entity”,来添加一个Entity,如下:在这里插入图片描述

接下来我们双击新建的“”Entity来修改名称,这里修改成“Person”,并且点击“Attributes”下面的加号,添加两个attribute。双击第一个,将其命名为“id”,“Type”一栏选择“UUID”;双击第二个,将其命名为“name”,“Type”一栏选择“String”,如下:
在这里插入图片描述

添加初始数据

在Core Data数据库中,有一个很奇怪的特性——如果数据库是空的,就会报错,哪怕第一个动作是添加数据。所以一开始就要预先放入一个或者多个值(这个值只会出现在preview中,运行在虚拟机或者真正的机器上的时候则不会显示)。这时候我们就需要选中Persistence.swift文件,到我们之前预留出来的地方,输入我们预先填充的数据,例如:

//留出来供后面使用的区域
let newPerson = Person(context: viewContext)
newPerson.name = ""

这时候会报错,如果Entity的名称没拼写错误,就不用管。可能是缓存bug,不影响使用。

获取数据库中的数据

我们在View的结构体中输入:

@FetchRequest(entity: Person.entity(), sortDescriptors: [])
var persons: FetchedResults<Person>

这样我们就算激活了数据库,可以获取/删除数据了。

我们先来定义一个数组,来向数据库中输入数据,假设为:

let nameArray: [String] = ["张三","李四","王五","赵六"]

我们将代码修改一下,方便我们观察数据库的写入,修改为如下:

struct ContentView: View {
    //我们可以要求它提供当前托管对象内容,并将其分配给一个属性供我们使用
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(entity: Person.entity(), sortDescriptors: [])
    var persons: FetchedResults<Person>

    var body: some View {
        VStack {
            Button("添加") {
                
                //随机获取姓名
                let chosenName = nameArray.randomElement()!

                //给数据库写入数据
                let person = Person(context: self.viewContext)
                person.id = UUID()
                person.name = "\(chosenName)"
                        
                //保存当前数据
                try? self.viewContext.save()
            }
            List {
                //将数据库中的name数据依次列出
                ForEach(persons, id: \.id) { person in
                    Text(person.name!)
                }
            }
        }
    }
}

这时候效果如下:
在这里插入图片描述
点击一下添加,效果如下:
在这里插入图片描述
这时候报错的话就不用在意,因为是不影响运行的bug。如果实在很在意的话,关闭项目重新打开一下就好了
开发中,就只需要将随机获取数组中数据的代码改成获取用户输入即可。

按特定顺序获取数据

这时候我们多点几下可以发现他是按照输入顺序来添加到List中的,也就是说是按照添加顺序来输出的。如果我们这时候想按照名字排序输出,当然我门可以在Foreach部分设定一个过滤器,不过有更简单的办法,如下:

@FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Person.name, ascending: true)],
        animation: .default)
var persons: FetchedResults<Person>

sortDescriptors属性,这是个数组,之前我们设置为空。现在我们将其内容设定成NSSortDescriptor(keyPath: \Person.name, ascending: true),这表示按照Person的name属性进行排序,也就是按照名称排序。

这时候我们点击“添加”按钮就能发现,如果随机的姓名是一样的,新添加的就会排在同一个名字的上面。

删除数据

我们在Foreach下添加.onDelete,苹果官方的代码如下:

private func deleteItems(offsets: IndexSet) {
    withAnimation {
        offsets.map { persons[$0] }.forEach(viewContext.delete)

        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
}

然后我们修改一下view body部分,这时候当前界面的代码如下:

let nameArray: [String] = ["张三","李四","王五","赵六"]

struct ContentView: View {
    //我们可以要求它提供当前托管对象内容,并将其分配给一个属性供我们使用
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Person.name, ascending: true)],
        animation: .default)
    var persons: FetchedResults<Person>

    var body: some View {
        VStack {
            Button("添加") {
                //随机获取姓名
                let chosenName = nameArray.randomElement()!

                //给数据库写入数据
                let person = Person(context: self.viewContext)
                person.id = UUID()
                person.name = "\(chosenName)"
                        
                //保存当前数据
                try? self.viewContext.save()
            }
            List {
                //将数据库中的name数据依次列出
                ForEach(persons, id: \.id) { person in
                    Text(person.name!)
                }
                .onDelete(perform: deleteItems )
            }
        }
    }
    
    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { persons[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

然后我们运行preview,点击“添加”,右划新添加的内容,效果如下:
在这里插入图片描述
这时候我们就可以删除数据库内容了。

以上就是Core Data在纯SwiftUI生命周期中的使用方法,不复杂,就是为了详细写了比较多,希望能帮的有需要的人。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐