SwiftUI基础篇CoreData

CoreData

概述

文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,CoreData部分调试,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接

1、如何在SwiftUI中使用CoreData

SwiftUI和CoreData作为Apple软件平台的其中两个重要组成部分,有很好的协同能力,SwiftUI有属性包装器、环境支持等等,这些都是为了确保能以最小的麻烦将Core Data集成到SwiftUI的App中。在SwiftUI之前,在架构角度来看,使用Core Data的方式如下:

  • Apple建议在AppDelegate的级别创建容器,然后根据需要return
  • 一部分人喜欢使用Manager Classes
  • 还有一部分人喜欢完全抽象Core Data,以方便完全转移到Realm或其他可选项

SwiftUI与Core Data的集成是不同的,在启动时,需要创建Core Data容器,然后将其托管对象注入到环境中,然后直接在那里执行Request。以下是四个特定的功能,可以帮你理解我的意思:

  1. NSManagedObject遵守ObservableObject协议,这代表可以将任何对象绑定到用户界面。
  2. 环境中有一个managedObjectContent的key,用于存储Core Data托管对象的上下文。
  3. 然后Xcode的模板将这个context注入到初始的内容视图中。
  4. 有一个@FetchRequest的属性包装器,它使用环境的托管对象上下文来获取request

因此,在应用启动时创建一个托管对象上下文,将其附加到视图的环境中,然后使用@FetchRequest加载数据供App使用。

2、在SwiftUI中如何配置Core Data

如果创建一个新项目,使用的是SwiftUI并需要使用Core Data,Xcode的配置分为四步:

  1. 创建一个空的项目模板,通常以你的项目命名(FFModifier.xcdatamodeld),和一个示例配置
  2. 添加一个Persistence.swift文件,将Core Data整齐的封装在这个地方。
  3. 使用managedObjectContext将上下文注入初始ContentView的environment中。
  4. 创建在ContentView中创建、读取和删除示例数据的示例代码。

这是提供了在SwiftUI中使用CoreData获取request的完整能力。

2.1、创建Main.xcdatamodeld

通过cmd+N来创建一个新文件,然后选择Data Model来创建一个Core Data模型。这个模型的名字很重要,因为要在代码中引用,我这里命名为"Main"。一旦创建了model,那么就可以在任何想要使用的位置创建实例。

2.2、创建ProgrammingLanguage的实例

打开xcdatamodeld文件并创建一个名为ProgrammingLanguage的实例,我创建了两个字符串属性:"name"和"creator"。你可以根据你的需求创建想要的任何属性了。

2.3、创建PersistenceController单例

需要一个地方来加载和管理Core Data配置,Apple的模版通过一个PersistenceController单例实现。主要做的工作是让Core Data启动并运行,同时也为SwiftUI提供与来Contenxts的能力。

Swift 复制代码
import CoreData

struct PersistenceController {
    //创建单例
    static let shared = PersistenceController()
    //Core Data
    let container: NSPersistentContainer
    //SwiftUI预览配置
    static var preview: PersistenceController = {
        let controller = PersistenceController(inMemory: true)
        
        //创建10个example
        for i in 0..<100 {
            let language = ProgrammingLanguage(context: controller.container.viewContext)
            language.name = "Example Language \(i)"
            language.creator = "A.Programmer \(i)"
        }
        
        return controller
    }()
    //加载Core Data初始化器,将使用内存存储设定为可选值
    init(inMemory: Bool = false) {
        
        //将Main更改为你自己的名
        container = NSPersistentContainer(name: "Main")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(filePath: "/dev/null")
        }
        container.loadPersistentStores { descroption, error in
            if let error = error {
                fatalError("BBLv Error: \(error.localizedDescription)")
            }
        }
    }
    //向PersistenceController类添加save方法,以便它检查context是否更改。
    func save() {
        let context = container.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print("save error: \(error.localizedDescription)")
            }
        }
    }
}

2.4、将Core Data容器的托管对象context注入到SwiftUI环境中

Swift 复制代码
let persistenceController = PersistenceController.shared

2.5、scenePhase监听App状态

最后一步是可选的,但建议使用,来确保数据完整,针对的场景是当App状态切换为后台时,应该调用save()方法,以便Core Data来保存你的更改,在SwiftUI中,通过添加一个属性到App来监听场景。

Swift 复制代码
@Environment(\.scenePhase) var scenePhase

2.6、结合起来的代码

这段代码写在FFModifierApp中,APP的main入口,由于做了很多其他的例子也用到了这个入口,目前这里代码比较多,这是我整理的只有关于CoreData部分的代码。

Swift 复制代码
@main
struct FFModifierApp: App {
    //将Core Data容器的托管对象context注入到SwiftUI环境中,这里使用perview使用的是假数据,
    //如果真实情况下,使用单例进行初始化(PersistenceController.shared)
    let persistenceController = PersistenceController.preview
    //最后一步是可选的,但建议使用,来确保数据完整,针对的场景是当App状态切换为后台时,
    //应该调用save()方法,以便Core Data来保存你的更改,在SwiftUI中,通过添加一个属性到App来监听场景。
    @Environment(\.scenePhase) var scenePhase

    var body: some Scene {
        WindowGroup {
            FFCoreDataFetchRequest()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
        }
        .onChange(of: scenePhase) {
            persistenceController.save()
        }
    }
}

在上面的PersistenceController.swift类中我选择将数据保存在内存中了,所以该代码的内存存储部分很重要,因为Core Data配置为将信息保存到内存而不是磁盘是,这代表着Core Data中的数据被定义为临时数据,在程序关闭时会被清理。

3、如何从SwiftUI视图访问核心数据管理对象Context

核心代码为@Environment(\.managedObjectContext) var managedObjectContext,在每一个想要使用CoreData的上下文内容上都要使用这个环境变量。下面的例子仅代表着FFCoreDataManagged想要使用数据。但是啥也没干。

Swift 复制代码
struct FFCoreDataManagged: View {
    //将context作为环境变量传递给ContentView(我这里是FFCoreDataManagged),
    //在此视图中添加一个@Environment属性来读取托管对象上下文。
    @Environment(\.managedObjectContext) var managedObjectContext
    var body: some View {
        Text("Hello, World!")
    }
}

4、如何使用@FetchRequest创建一个核心数据获取请求

FetchRequest可以加载符合指定的条件的Core Data结果,并且SwiftUI可以将这些结果直接绑定到用户界面。创建fetchRequest需要两条信息:

  • 要查询的实体
  • 以及确定返回结果顺序的排序描述符。
Swift 复制代码
struct FFCoreDataFetchRequest: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext
    //  SortDescriptors参数是一个数组,所以可以提供尽可能多的排序选项
    @FetchRequest(sortDescriptors: []) var languages: FetchedResults<ProgrammingLanguage>
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

调试结果

看似简单的代码就成功获得了数据,其实是上面许多步骤后的结果

5、如何使用predicate过滤CoreData的fetchRequests

CoreData的fetchRequest中可以使用Predicates,就像在UIKit中,所有这些都是通向你的@FetchRequest属性包装器提供一个predicate来实现的。

Swift 复制代码
struct FFCoreDataFetchRequestsFilter: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    //创建predicate
    @FetchRequest(
        sortDescriptors: [],
        predicate: NSPredicate(format: "name == %@", "Python")
    ) var languages: FetchedResults<ProgrammingLanguage>
    //目前@FetchRequest使用的是标准的CoreData的Predicate,也可以创建复合Predicate
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

6、如何从SwiftUI视图添加CoreData对象

在SwiftUI中保存core data对象的工作方式与在SwiftUI之外的工作往事完全相同,访问托管对象的context,在该上下文中创建你的类型的实例,然后在准备好时保存context。

Swift 复制代码
struct FFCoreDataAddObjects: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(sortDescriptors: []) var languages: FetchedResults<ProgrammingLanguage>
    var body: some View {
        //创建实例
        Button("Insert Example Languate") {
            let language = ProgrammingLanguage(context: managedObjectContext)
            language.name = "Python"
            language.creator = "Guido van Rossum"
            PersistenceController.shared.save()
        }
        //在添加一组对象之后,程序会多一组对象。
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

调试结果

7、如何从SwiftUI视图中删除CoreData对象

在SwiftUI中删除CoreData对象与在UIKit中删除他们基本相同,尽管需要跳过一些特殊的步骤才能与SwiftUI视图集成。

Swift 复制代码
struct FFCoreDataDeleteObjects: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        sortDescriptors: []
    ) var languages: FetchedResults<ProgrammingLanguage>
    
    var body: some View {
        //无论在哪里显示数据,都向SwiftUI视图添加onDelete修饰符。
        List {
            ForEach(languages) { language in
                Text("Creator: \(language.creator ?? "Anonymous")")
            }
            .onDelete(perform: { indexSet in
                removelanguages(at: indexSet)
            })
        }
        .toolbar(content: {
            EditButton()
        })
    }
    
    func removelanguages(at offsets: IndexSet) {
        for index in offsets {
            let language = languages[index]
            managedObjectContext.delete(language)
        }
        PersistenceController.shared.save()
    }
}

调试结果

8、如何限制获取FetchRequest中的item数量

SwiftUI的@FetchRequest属性包装器非常适合对Object发出简单request,提供排序和过滤功能。但是,如果想调整返回item的数量,那么需要额外设置

Swift 复制代码
struct FFCoreDataLimit: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    //创建@FetchRequest
    @FetchRequest var languages: FetchedResults<ProgrammingLanguage>
    
    init() {
        let request: NSFetchRequest<ProgrammingLanguage> = ProgrammingLanguage.fetchRequest()
        request.sortDescriptors = [
            NSSortDescriptor(keyPath: \ProgrammingLanguage.name, ascending: true)
        ]
        request.fetchLimit = 10
        _languages = FetchRequest(fetchRequest: request)
    }
    
    var body: some View {
        List(languages) { language in
            Text(language.name ?? "UnKnown")
        }
    }
}

9、如何在SwiftUI中使用NSUserActivity

SwiftUI有一个专用的onContinueUserActivity()修饰符,可以捕获各种NSUserActivity类型,比如来自网络的点击、来自第三方或者Siri启动等等。在AppDelegate中使用application(_:continue:restorationHandler:)处理过这个问题,但SwiftUI方法更简单的处理这个问题。要实现这个需求,首先要创建一个函数并接受参数NSUserActivity,但是,不需要在App结构体中执行此操作。

Swift 复制代码
func handleWechatLogin(_ userActivity: NSUserActivity) {
    if let id = userActivity.userInfo?["targetContentIdentifier"] {
        print("Found identifier \(id)")
    }
}

使用方法

Swift 复制代码
var body: some Scene {
    WindowGroup {
        FFCoreDataDeleteObjects()
            .environment(\.managedObjectContext, persistenceController.container.viewContext)
            .onContinueUserActivity("targetContentIdentifier", perform: { userActivity in
                handleWechatLogin(userActivity)
            })
    }
    .onChange(of: scenePhase) {
        persistenceController.save()
    }
}
相关推荐
iOS阿玮3 天前
苹果 iOS 19 曝光,你的iPhone 还能再战一年?
app·apple
Lexiaoyao203 天前
SwiftUI 字体系统详解
swiftui·swift
光阴独白4 天前
Apple Login for JavaScript
前端·apple
1024小神4 天前
theos工具来编译xcode的swiftUI项目为ipa文件
macos·swiftui·xcode
iOS阿玮5 天前
都2025年,你竟然还敢买iOS的源码?
前端·app·apple
东坡肘子5 天前
MCP 崛起与苹果的 AI 框架设想 | 肘子的 Swift 周报 #077
人工智能·swiftui·swift
YungFan6 天前
SwiftUI-国际化
ios·swiftui·swift
1024小神10 天前
xcode开发swiftui项目的时候,怎么调试ui占位和ui大小
ui·ios·swiftui
东坡肘子12 天前
给毛孩子照相 | 肘子的 Swift 周报 #076
swiftui·swift·apple
iOS阿玮14 天前
Apple开发者已入驻微信公众号
前端·app·apple