持续创作,加快生长!这是我参与「日新方案 10 月更文应战」的第2天,点击查看活动概况

coredata 是用于耐久化存储数据的,可以把它的作用简略理解为类似于前端浏览器的 localStorage。可是当你把 APP 删去的时分,APP 对应的 coredata 数据也会被删去。

本文旨在快速讲清 coredata 的开发运用。

通常有凭借 List 视图来解说 coredata 的运用,这是一种常用的方法。但除此之外,也应当有愈加通用的方法来运用 coredata,也便是下文将会解说的增修正查内容。

学会了以下的内容,即便脱离了 List ,你也能单独完结其中的某一项功用:

  • 怎么装置 coredata
  • 创立 coredata 实体和特点
  • coredata 怎么增加数据
  • coredata 怎么删去数据
  • coredata 怎么查询数据
  • coredata 怎么修正数据

话不多说,让咱们开端吧。

下文将会运用 Xcode 14、SwiftUI 开发。

装置 coredata

新建的项目装置 coredata

  1. 创立一个新项目

SwiftUI 开发之旅:CoreData 实操开发

  1. 挑选 ios app

SwiftUI 开发之旅:CoreData 实操开发

  1. 勾选 use Core Data

SwiftUI 开发之旅:CoreData 实操开发

翻开项目的 HelloCoreData.xcdatamodeld 文件,可以看到现已默许创立了一个名为 Item 的实体。

SwiftUI 开发之旅:CoreData 实操开发

到这儿,一个新项目装置 coredata 的部分就完结了。

现有项目装置 coredata

  1. 新建一个 Data Model 文件。

SwiftUI 开发之旅:CoreData 实操开发

  1. 文件名一般和项目名称相同。

SwiftUI 开发之旅:CoreData 实操开发

  1. 创立 Persistence.swift 文件

创立 Persistence,是为了让预览也能运用 coredata 数据;以下是一个官方模板,直接运用即可。

import CoreData
struct PersistenceController {
    static let shared = PersistenceController()
    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        // 给预览增加预设数据
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
        }
        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: "HelloCoreData")
        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)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

创立 coredata 实体和特点

在上文装置和装备好 coredata 后,接下来就可以创立实体和相应的特点了。

创立实体

在这儿咱们创立一个名为 User 的实体。

实体的名称一般选用首字符大写驼峰的命名方法。

SwiftUI 开发之旅:CoreData 实操开发

增加了 3 个特点。

SwiftUI 开发之旅:CoreData 实操开发

创立实体模型

创立完实体后,咱们还需求创立一个实体对应的模型,Xcode 也提供了主动生成实体模型的功用,但这儿咱们选用手动创立实体的方法。

  1. 首要设置 User 实体的 Class 特点。
SwiftUI 开发之旅:CoreData 实操开发
  1. 然后创立一个模型:Models/User.swift
import Foundation
import CoreData
final class User: NSManagedObject {
    @NSManaged var id: UUID
    // 用户名
    @NSManaged var name: String
    // 喜好
    @NSManaged var hobby: String
}

创立视图

接下来开端完结UI页面的编写。

创立一个名为 UserList.swift 文件。

咱们会从 ContentView.swift 运用 NavigationLink 导航到 UserList.swift 页面。

先给预览装备 .environment 修饰符,这样预览后续才干正确显现 coredata 数据。

再新增一个 @Environment 特点,下面的增修正查功用都会用到 viewContext

只需你在视图中操作 coredata,根本都需求设置 @Environment 特点。

import SwiftUI
struct UserList: View {
    @Environment(\.managedObjectContext) private var viewContext
    var body: some View {
        Text("Hello, World!")
    }
}
struct UserList_Previews: PreviewProvider {
    static var previews: some View {
        UserList()
            .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

到这儿,根本的准备作业和 UI 咱们都已完结,接下便是实际操作了。

此时预览还不能正常显现,下面咱们会修正这个问题。

coredata 查询数据

为了在 UserList 视图中显现用户列表数据,咱们需求运用 @FetchRequest 来获取数据:

import SwiftUI
struct UserList: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        entity: User.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \User.id, ascending: false)],
        animation: .default)
        // 这便是咱们获取到 coredata user 的数据
    private var userList: FetchedResults<User>
    var body: some View {
       Text("Hello, World!")
    }
}
struct UserList_Previews: PreviewProvider {
    static var previews: some View {
        UserList()
            .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

接着运用 ForEach 来显现数据:

struct UserList: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        entity: User.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \User.id, ascending: false)],
        animation: .default)
    private var userList: FetchedResults<User>
    var body: some View {
        VStack {
              if userList.isEmpty {
                Text("暂无用户数据")
            } else {
                ForEach(userList, id: \.self) { item in
                    HStack {
                        Text(item.name)
                        Text("喜好:\(item.hobby)")
                    }
                }
            }
        }.navigationTitle("用户列表")
    }
}

但现在预览还无法正常显现,咱们需求在 Persistence.swift 文件中给预览增加一些数据用于显现:

  for index in 0..<10 {
      let newItem = Item(context: viewContext)
      newItem.timestamp = Date()
      // 增加的预览数据
      let userItem = User(context: viewContext)
      userItem.id = UUID()
      userItem.name = "用户 \(index)"
      userItem.hobby = "篮球"
  }

SwiftUI 开发之旅:CoreData 实操开发

查询数据部分就完结了,是不是很简略。

不管你在哪一个视图中运用了 coredata 数据,要想让该视图正常预览,都需求在 Persistence.swift 增加相应的预览数据。当然,就算不增加预览数据,也不影响模拟器启动。

coredata 新增数据

在新增数据前,咱们先简略增加一些UI控件。

下面的代码中将会省掉预览的 UserList_Previews 代码。

自定义导航栏,增加一个返回按钮和新增按钮:

import SwiftUI
struct UserList: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        entity: User.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \User.id, ascending: false)],
        animation: .default)
    private var userList: FetchedResults<User>
    @State private var showAdd = false
    var body: some View {
        VStack {
            if userList.isEmpty {
                Text("暂无用户数据")
            } else {
                ForEach(userList, id: \.self) { item in
                    HStack {
                        Text(item.name)
                        Text("喜好:\(item.hobby)")
                    }
                }
            }
        }
        .padding()
        .navigationTitle("用户列表")
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: Button(action : {
        }){
            //按钮及其款式
            Image(systemName: "chevron.left")
        }, trailing:  Button(action : {
            self.showAdd = true
        }){
            Image(systemName: "plus")
        })
    }
}

SwiftUI 开发之旅:CoreData 实操开发

创立表单和数据字段:

import SwiftUI
struct UserList: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        entity: User.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \User.id, ascending: false)],
        animation: .default)
    private var userList: FetchedResults<User>
    @State private var showAdd = false
    // 表单绑定数据
    @State private var name: String = ""
    @State private var hobby: String = ""
    func addUser() {
    }
    var body: some View {
        VStack {
            if showAdd {
                VStack {
                    TextField("用户名", text: $name)
                        .padding()
                        .border(Color.gray)
                    TextField("喜好", text: $hobby)
                        .padding()
                        .border(Color.gray)
                    Button(action: {
                        addUser()
                    }, label: {
                        Text("保存")
                            .padding(.horizontal, 2)
                            .padding(.vertical, 15)
                            .frame(maxWidth: .infinity)
                            .background(Color.blue)
                            .foregroundColor(.white).cornerRadius(24)
                    })
                }.padding()
            }
            if userList.isEmpty {
                Text("暂无用户数据")
            } else {
                ForEach(userList, id: \.self) { item in
                    HStack {
                        Text(item.name)
                        Text("喜好:\(item.hobby)")
                    }
                }
            }
        }
        .padding()
        .navigationTitle("用户列表")
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading: Button(action : {
        }){
            //按钮及其款式
            Image(systemName: "chevron.left")
        }, trailing:  Button(action : {
            self.showAdd = true
        }){
            Image(systemName: "plus")
        })
    }
}

SwiftUI 开发之旅:CoreData 实操开发

UI准备作业完结,现在新增数据时,咱们需求通过 viewContext 获取到实体,然后给实体的特点赋值。

    func addUser() {
        withAnimation {
            if !name.isEmpty && !hobby.isEmpty {
                let newItem = User(context: viewContext)
                newItem.id = UUID()
                newItem.name = name
                newItem.hobby = hobby
                // ...
            }
        }
    }

最终,保存上下文。

调用 save() 保存数据很重要,如果不保存,即便数据新增成功,数据是没有真实存储到内存中的。

    func addUser() {
        withAnimation {
            if !name.isEmpty && !hobby.isEmpty {
                let newItem = User(context: viewContext)
                newItem.id = UUID()
                newItem.name = name
                newItem.hobby = hobby
                do {
                    try viewContext.save()
                    showAdd = false
                } catch {
                    let nsError = error as NSError
                     // fatalError() 使应用程序生成崩溃日志并停止。 尽管此功用在开发过程中可能很有用,不该在出产应用程序中运用此功用。
                    fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                }
            }
        }
    }

SwiftUI 开发之旅:CoreData 实操开发
SwiftUI 开发之旅:CoreData 实操开发

演示效果:

SwiftUI 开发之旅:CoreData 实操开发

到这儿,新增数据部分完结。

tips: 在模拟器输入中文:在设置-通用-语言与地区中增加简体中文的语言,在设置-通用-键盘增加简体中文输入法后,在模拟器中按 Command + K,调起软键盘,点击软键盘下面那个小地球仪,切换成中文输入,就能在模拟器中输入中文了。

coredata 删去数据

先来增加一个删去提示框,首要创立一个操控显现提示框的变量和用于存储被删去数据id的变量。

  @State private var isDelete = false
  @State private var deleteId: UUID = UUID()
  // ...

然后增加删去按钮和提示框,保存被删去数据的 id。

  if userList.isEmpty {
      Text("暂无用户数据")
  } else {
      ForEach(userList, id: \.self) { item in
          HStack {
              Text(item.name)
              Text("喜好:\(item.hobby)")
              Button(action: {
                  isDelete = true
                  // 点击删去时保存要删去的数据的 id
                  self.currentUserId = item.id
              }, label: {
                  Text("删去")
              })
              .alert("提示", isPresented: $isDelete) {
                  Button(role: .cancel) {
                      isDelete = false
                  } label: {
                      Text("撤销")
                  }
                  Button(role: .destructive) {
                  } label: {
                      Text("删去")
                  }
              } message: {
                  Text("确定删去吗?")
              }
          }
      }
  }

增加删去函数,该函数依据传递的 id 参数找到需求被删去的数据,然后传递给 viewContext.delete 函数,删去后保存即可。

  func deleteUser(id: UUID) {
      if let record = userList.first(where: { $0.id == id }) {
          withAnimation {
              viewContext.delete(record)
              do {
                  try viewContext.save()
                  isDelete = false
              } catch {
                  isDelete = false
                  let nsError = error as NSError
                  fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
              }
          }
      }
  }

调用 deleteUser 函数:

  Button(role: .destructive) {
      deleteUser(id: currentUserId)
  } label: {
      Text("删去")
  }

在这儿我现已事先新增了3条数据,来看看完结的效果:

SwiftUI 开发之旅:CoreData 实操开发

coredata 修正数据

修正时分会用到上一步创立的表单和数据。

再新增一个变量操控修正:

@State private var showUpdate = false

在文字旁边新增一个修正按钮,一起将当前要修正的数据对象的值赋值给表单绑定值,让表单显现对应数据。

Text(item.name)
Text("喜好:\(item.hobby)")
Button(action: {
    showUpdate = true
    // 赋值
    self.name = item.name
    self.hobby = item.hobby
    self.currentUserId = item.id
}, label: {
    Text("修正")
})

当 showUpdate 为 true 时显现表单,以及切换为调用 updateUser 函数:

if showAdd || showUpdate {
  VStack {
      TextField("用户名", text: $name)
          .padding()
          .border(Color.gray)
      TextField("喜好", text: $hobby)
          .padding()
          .border(Color.gray)
      Button(action: {
          if showAdd {
              addUser()
          } else if showUpdate {
              updateUser(id: currentUserId)
          }
      }, label: {
          Text("保存")
              .padding(.horizontal, 2)
              .padding(.vertical, 15)
              .frame(maxWidth: .infinity)
              .background(Color.blue)
              .foregroundColor(.white).cornerRadius(24)
      })
  }.padding()
}

编写 updateUser 函数:

func updateUser(id: UUID) {
    if let record = userList.first(where: { $0.id == id }) {
        withAnimation {
            record.name = self.name
            record.hobby = self.hobby
            do {
                try viewContext.save()
                showUpdate = false
            } catch {
                showUpdate = false
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

来看最终效果:

SwiftUI 开发之旅:CoreData 实操开发

在上面的几个操作中,省掉了一些数据清理作业,比方新增后的数据置空,表单的躲藏等。

总结

本文咱们完结了 coredata 的增修正查等根本操作,信任通过学习你现已根本把握了这些内容,coredata 还有许多运用方法,比方更细粒度的查询,分页等操作,有时机咱们再讲吧。

这是 SwiftUI 开发之旅专栏的文章,是 swiftui 开发学习的经验总结及实用技巧共享,欢迎关注该专栏,会坚持输出。一起欢迎关注我的个人大众号 @JSHub:提供最新的开发信息速报,优质的技术干货引荐。或是查看我的个人博客:Devcursor。

点赞:如果有收成和帮助,请点个赞支撑一下!

保藏:欢迎保藏文章,随时查看!

评论:欢迎评论交流学习,共同进步!