条件回顾

在上几个章节中,咱们完成了Linkworld基本功用的搭建,也进一步了解了SwiftUI这一声明式语法的编程方式的魅力之处。

在本章中,咱们继续学习本地化存储相关办法,那么让咱们开端吧。

外链跳转:翻开运用外浏览器

在之前的章节中,咱们学习过运用WebKit在运用中翻开Web网页的办法,这里再补充一个知识点— —怎么引发体系浏览器并翻开网站。

在SwiftUI中引发外链的办法是运用Link办法,和NavigationLink方式相似,NavigationLink导航跳转是在运用内跳转页面,而Link则是翻开iOS本地浏览器并拜访指定网站。

来到HomePageView页面,咱们给创立一个新的按钮,如下代码所示:

// 翻开浏览器按钮
func openWebBtn() -> some View {
    Image(systemName: "network")
        .font(.system(size: 17))
        .foregroundColor(.blue)
}

然后咱们将按钮加到顶部导航菜单中,如下代码所示:

.navigationBarItems(leading: backBtn(),trailing: openWebBtn())

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

接下来咱们来完成跳转办法,在openWebBtn中运用Link办法,如下代码所示:

Link(destination: URL(string: "https://"+indexURL)!){
    Image(systemName: "network")
    	.font(.system(size: 17))
    	.foregroundColor(.blue)
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

因为需求运用到体系浏览器做合作,因而需求“运行”模拟器设备上测试效果。如下图所示:

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

FileManager本地化存储

接下来咱们来学习本地化存储,将恳求回来的JSON文件数据和本地创立的数据缓存起来,鄙人一次翻开时还可以操作上一次的数据。

咱们来到ViewModel视图模型中,键入下面的代码:

// 获取设备上的文档目录途径
func documentsDirectory() -> URL {
    FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
}

FileManager是本地文件存储管理器,用于充任文件存储的中心桥梁。上述代码中咱们创立了一个办法documentsDirectory答应开发者在可存取空间userDomainMask中运用沙盒documentDirectory,并回来一个URL途径。如此,文件存储器FileManager在整个运用中都可以被运用。

然后咱们经过FileManager本地文件管理器拜访放置数据的文件夹,咱们可以放在plist文件中,如下代码所示:

// 获取plist数据文件的途径
func dataFilePath() -> URL {
    documentsDirectory().appendingPathComponent("Linkworld.plist")
}

上述代码中,咱们操作的便是运用FileManager本地文件管理器取得Linkworld.plist文件的途径,便于咱们操作plist文件。

写入本地数据

确认文件夹后,咱们创立一个办法将数据写入到本次存储中,如下代码所示:

//写入本地数据
func saveItems() {
    let encoder = PropertyListEncoder()
    do {
        let data = try encoder.encode(models)
        try data.write(to: dataFilePath(), options: Data.WritingOptions.atomic)
    } catch {
        print("错误信息: (error.localizedDescription)")
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,咱们运用编码器PropertyListEncoder将目标实例与XML数据格式之间进行互相转化,作用是使得原始数据可以在体系中进行传输,传输的数据经过dataFilePath办法进行写入存储中,咱们将整个写入存储的操作创立一个办法saveItems。

咱们什么时分会运用到将数据写入存储呢?是的,在每次数据发生变化时。因而咱们可以在ViewModel视图模型创立的办法中调用saveItems办法,如下图所示:

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

写入本次存储保存后,咱们假如运用到网络恳求的办法时,还需求将网络恳求回来的数据也写入到本次存储中,以及在页面加载时读取本次存储的内容。

加载本地数据

所以咱们还需求创立一个读取本地存储数据的办法,将上一次存起来的数据鄙人一次翻开时加载出来,如下代码所示:

// 加载本地数据
func loadItems() {
    let path = dataFilePath()
    // 假如没有数据则越过
    if let data = try? Data(contentsOf: path) {
        let decoder = PropertyListDecoder()
        do {
            models = try decoder.decode([Model].self, from: data)
        } catch {
            print("错误提示: (error.localizedDescription)")
        }
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,咱们创立了一个读取本地数据的办法loadItems,在loadItems办法中,咱们首要判别数据途径是否存在,假如存在则执行运用编码器PropertyListEncoder传输数据,将符合Model数据模型的数据加载到models数据集中,假如失利则输出打印错误信息。

初始化本地数据

完成后,咱们需求在运用初始化时,读取本次数据的办法,如下代码所示:

init() {
    loadItems()
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

完成后,咱们来到ContentView视图,创立一条数据并改写模拟器预览,不管咱们离开此页面仍是推出Xcode,数据都会被保存在本地中,鄙人一次翻开时就会看得到上一次创立的数据。

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

CoreData数据耐久化结构

接下来咱们再学习一种本地数据耐久化的办法,也是目前运用最多的数据耐久化办法,即运用CoreData数据耐久化结构。

首要咱们要创立一个Data Model数据模型文件放置在Model文件夹中,命名为CoreData,如下图所示:

创立数据模型

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

然后选中CoreData数据模型,点击下面工具栏的Add Entity创立一个实体,命名为Model。而且在Model实体中界说好项目需求的特点,如下图所示:

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

因为Model实体咱们从头界说了,那么要确保Module模块要挑选CurrrentProductModule当时产品的模型,Codegen代码基因要挑选Manual/None,否则咱们在项目中引证模型的时分可能会找不到咱们界说的Model实体,如下图所示:

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

创立耐久存储区文件

模型预备完成后,下一步咱们需求创立一个耐久存储区的文件,用于保存数据到Model中,咱们在Model文件夹中创立一个新的Swift文件,命名为Persistence,并键入下面的代码:

import CoreData
struct Persistence {
    // 一个单例供咱们的整个运用程序运用
    static let shared = Persistence()
    // 存储中心数据
    let container: NSPersistentContainer
    // 用于加载 Core Data 的初始化程序,可以挑选运用内存中的存储区。
    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "CoreData")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores { description, error in
            if let error = error {
                fatalError("Error: (error.localizedDescription)")
            }
        }
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,咱们首要引证了CoreData数据耐久化结构,然后创立了一个结构体PersistenceController并界说一个常量shared用于初始化。假如是项目创立之初勾选了运用CoreData,则体系会默认创立需求的文件,上述的内容就当作固定的模版运用吧。

拜访数据库容器

然后声明一个新的变量container指向NSPersistentContainer数据库容器,声明容器后再给容器进行初始化操作。首要检查内存中是否存在数据库CoreData,假如存在则在需求时进行加载,假如加载失利则输出错误信息。

接下来咱们需求在项目中拜访数据库容器,翻开LinkworldApp文件,创立新的变量persistenceController赋值PersistenceController.shared,并运用环境修正器将数据库数据传递给子视图,如下代码所示:

import SwiftUI
import CoreData
@main
struct LinkworldApp: App {
    let persistenceController = Persistence.shared
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(.managedObjectContext, persistenceController.container.viewContext)
        }
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

项目特点匹配

紧接着,咱们还要改造下Model数据模型中的文件,使其声明的特点和数据库中的特点一一匹配,如下代码所示:

import CoreData
import Foundation
import SwiftUI
public class Model: NSManagedObject,Identifiable {
    @NSManaged public var id: UUID
    @NSManaged public var platformIcon: String
    @NSManaged public var title: String
    @NSManaged public var platformName: String
    @NSManaged public var indexURL: String
    enum CodingKeys: String, CodingKey {
        case platformIcon, title, platformName, indexURL
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,因为CoreData模型类继承自NSManagedObject协议,每个特点都运用@NSManaged进行声明。

引证数据库

界说好数据模型后,咱们来到ContentView视图,咱们运用@FetchRequest特点包装器从数据库加载数据,而且注释本来的@StateObject声明的viewModel,如下代码所示:

@FetchRequest(entity: Model.entity(),
              sortDescriptors: [NSSortDescriptor(keyPath: \Model.title, ascending: false)])
var models: FetchedResults<Model>

紧接着咱们替换掉本来viewModel形式视图数据遍历的List列表,如下图所示:

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

这时可能呈现很多报错,这是因为咱们替换了本来的viewModel模型视图中的models数据集,因而在很多运用models数据集进行数据传输的当地都会找不到目标而报错。

这不要紧,咱们一点点修复它。

修正NewView新建视图

咱们先来到NewView新建身份卡视图,咱们需求声明一个环境变量来管理目标上下文,而且还需求注释本来的viewModel视图模型,如下代码所示:

@Environment(.managedObjectContext) var context

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

增加身份卡的办法咱们也需求从头设计,而且注释之前的代码,如下代码所示:

// 赋值
let newItem = Model(context: context)
newItem.id = UUID()
newItem.platformIcon = platformIcon
newItem.title = title
newItem.platformName = platformName
newItem.indexURL = indexURL
// 保存
do {
    try context.save()
} catch {
    print(error)
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,咱们声明晰一个常量newItem来取得Model数据库实体的数据类型,然后给实体的参数赋值,最终调用save办法保存数据。

修正EditView修正视图

NewView新建身份卡视图基本完成了,咱们再来到EditView修正视图,同理,咱们声明一个环境变量来管理目标上下文,如下代码所示:

@Environment(.managedObjectContext) var context
@FetchRequest(entity: Model.entity(),
                  sortDescriptors: [NSSortDescriptor(keyPath: \Model.id, ascending: false)])
var models: FetchedResults<Model>

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

关于修正保存操作,咱们也需求注释原有的代码,增加新的修正更新办法,并修正EditView_Previews绑定联系,如下代码所示:

if let editItem = models.first(where: { $0.id == model.id }) {
    editItem.platformIcon = model.platformIcon
    editItem.title = model.title
    editItem.platformName = model.platformName
    editItem.indexURL = model.indexURL
    do {
        try context.save()
        self.presentationMode.wrappedValue.dismiss()
    } catch {
        let nsError = error as NSError
        fatalError("Unresolved error (nsError), (nsError.userInfo)")
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,因为咱们需求修正更新数据,因而首要要取得当时修正数据的ID,咱们经过判别当时ID与model(传输过来)中的数据做匹配,匹配成功后咱们进行从头赋值,最终仍旧调用save办法保存数据。

完成NewView新建页面和EditView修正页面后,咱们回到ContentView视图,首要是CardView视图,咱们注释原有的viewModel视图模型相关内容,而且声明一个全局变量用于关联数据,如下代码所示:

@Environment(.managedObjectContext) var context
var model: Model

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

然后修正翻开修正界面视图的绑定联系,而且先删去原先删去身份卡的办法,如下代码所示:

// 翻开修正弹窗
.sheet(isPresented: self.$showEditView, onDismiss: { self.showEditView = false }) {
    EditView(model: model)
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

完成后,咱们再来到ContentView的body视图,修正NewView绑定联系和CardView绑定联系,如下代码所示:

// 卡片视图
                            CardView(platformIcon: item.platformIcon, title: item.title, platformName: item.platformName, indexURL: item.indexURL,model: item)

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码做的事情比较绕,简略解释就是咱们在NewView视图和NewView视图从头声明晰相关特点或许变量,咱们就需求在运用到这些视图或许跳转到这些视图的当地做数据的绑定,便于数据在页面之间传递。

模拟器效果预览

完成之后,咱们发现操作了下模拟器,增加身份卡后仍是没有数据,这是因为Contentview_preview结构体中注入保管目标上下文,咱们给Contentview_preview结构体注入数据,咱们先创立示例数据,如下代码所示:

// SwiftUI预览的测试装备
static var preview: Persistence = {
    let controller = Persistence(inMemory: true)
    // 示例数据
    let newItem = Model(context: controller.container.viewContext)
    newItem.id = UUID()
    newItem.platformIcon = "icon_juejin"
    newItem.title = "签约作者"
    newItem.platformName = "技术社区"
    newItem.indexURL = "/user/3897092103223517"
    return controller
}()

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

然后加数据集加到Contentview_preview结构体中,如下代码所示:

ContentView().environment(.managedObjectContext, PersistenceController.preview.container.viewContext)

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

修正删去办法

最终咱们再回到CardView视图,修正下删去的办法,仍旧需求先引进数据库,如下代码所示:

@FetchRequest(entity: Model.entity(),
                  sortDescriptors: [NSSortDescriptor(keyPath: \Model.id, ascending: false)])
var models: FetchedResults<Model>

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

然后在调用删去办法的当地键入下面的代码,如下代码所示:

if let deleteItem = models.first(where: { $0.id == model.id }) {
        context.delete(deleteItem)
    do {
        try context.save()
    } catch {
        let nsError = error as NSError
        fatalError("Unresolved error (nsError), (nsError.userInfo)")
    }
}

实战教程元宇宙来了,准备好你的电子名片了吗?(八)

上述代码中,咱们和在EditView修正页面做的事情一样,经过判别当时操作的ID是models数据集中的那一项,然后调用delete办法删去数据,最终调用save办法保存当时操作。

项目小结

本章介绍了两种数据耐久化的办法,笔者比较引荐第二种运用CoreData结构进行数据耐久化的办法,因为后期可以和iCloud进行通讯完成云端存储的功用,后边的章节会找机会讲讲这个。

以及在本章中咱们将一些新增、修正、删去的办法都放在了视图中,没有好好利用MVVM结构形式,也是为了让我们先了解CoreData结构的运用。咱们也可以在后边自己想想怎么将一些办法抽离出来,搭建ViewModel视图模型部分,这就当作作业吧~

版权声明

本文为稀土技术社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!