Skip to main content

Command Palette

Search for a command to run...

SwiftUI应用CoreData小纸条(六)

从json中import数据并使用searchable来完成搜索功能

Published
2 min read
SwiftUI应用CoreData小纸条(六)

SwiftUI应用CoreData小纸条(五)之后,停了很久,主要的原因还是发现了如果使用了SwiftPM后SwiftUI的Preview就会挂掉,昨天沉下心来找到了这个问题的原因,并使用一个自定义的modul来修复SwiftUI Preview挂掉的问题。所以接下来又会开始继续这个系列的内容。在学习英语小助手中已经有近千词汇了,日常中非常需要一个搜索功能来查询字典中的词汇。

CoreData数据结构

首先我们看看CoreData中的数据结构定义:

image.png

在这个示例中我们其实只需要word这个Entity。它有一个Attributes就是name,类型为String。我们会在这里存所有的单词。为了使用方便,我们为Word加入扩展:

import Foundation

public struct WordViewModel{
    public let name:String
}

public extension Word{
    var viewModel: WordViewModel {
        return WordViewModel(
            name: self.name ?? "Unknow",
            picture: self.picture
        )
    }
}

从json中import到CoreData中

首先我们需要写一个函数将json中的数据load到内存中来:

public func load<T: Decodable>(_ filename: String, bundel: Bundle = Bundle.main) -> T {
    let data: Data

    guard let file = bundel.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in \(bundel) bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

这是example.json文件的内容:

[
  {"name": "brackets"},
  {"name": "braces"},
  {"name": "square brackets"},
  {"name": "slash"},
  {"name": "single quotation mark"},
  {"name": "apostrophe"}
]

我们为load进来的数据准备一个struct:

import Foundation
struct JWord: Codable, Identifiable{
  var id=UUID()
  var name: String
  enum CodingKeys: String, CodingKey{
    case name
  }
}

改造一下Xcode自动生成的PersistenceController的preview

    public static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        if let words: [JWord] = load("example.json",bundel: .module){
            for word in words{
                let newWord = Word(context: viewContext)
                newWord.name = word.name
            }
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            print("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

这样就将example.json中的内容加载到内存中,并转存到CoreData中去了。

准备好显示数据的列表View

这是使用之前的通用FilteredList稍微改造下如下:

import CoreData
import SwiftUI

public struct FilteredList<T: NSManagedObject,Content: View>: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest var fetchRequest: FetchedResults<T>
    let content: (T) -> Content

    public var body: some View {
        List {
            ForEach(fetchRequest, id:\.self) { item in
                self.content(item)
            }
        }
    }

    public init(
        sortDescriptors: [NSSortDescriptor]=[],
        predicate: NSPredicate? = nil,
        @ViewBuilder content: @escaping (T) -> Content
    ){
        _fetchRequest = FetchRequest<T>(
            sortDescriptors: sortDescriptors,
            predicate: predicate,
            animation: .default)
        self.content = content
    }
}

这个View初始化时支持predicate来得到查询条件,支持sortDescriptors来得到排序方法。

创建搜索View

import SwiftUI

public struct DictonarySearchView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @State private var searchText = ""

    public init(){}
    public var body: some View {
        FilteredList(
            sortDescriptors: [
                NSSortDescriptor(
                    key: "name",
                    ascending: true,
                    selector: #selector(NSString.localizedStandardCompare(_:))
                )
            ],
            predicate: NSPredicate(format: "name LIKE[c] %@", "*\(searchText)*")
        ){ (item:Word) in
            let item = item.viewModel
            Text("\(item.name)")
        }
        .environment(\.managedObjectContext,viewContext)
        .navigationTitle("Words")
        .searchable(
            text: $searchText,
            placement: .navigationBarDrawer(displayMode: .always),
            prompt: "Look up for dictonary"
        )

    }
}

struct DictonarySearchView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            DictonarySearchView()
                .environment(\.managedObjectContext,PersistenceController.preview.container.viewContext)
        }
    }
}

这里有几点需要注意:

  • sortDescriptors: 使用name来升序,并使用NSString.localizedStandardCompare来排序
  • predicate: 使用like来查找,[c]忽略大小写

最后来看看效果:

screen.gif

More from this blog

Https 本地服务器小纸条

最近想要尝试一下Telegram mini app,在调试时需要使用https的服务。试来试去发现在Mac下完成一个简单的HTTPS服务器还是需要点奇奇怪怪的工具的。但是整体来讲非常简单。 准备证书 主要会使用mkcert来创建证书。首先安装mkcert。 brew install mkcert brew install nss # if use Firefox 将mkcert加入到本地root CA。 mkcert -install 生成证书 本地调试可以使用localhost或127.0...

Feb 21, 20241 min read
Https 本地服务器小纸条

macOS中使用Docker发布一个python项目的小纸条

最近写了一个Telegram Bot,它可以使用语音和文字与GPT进行交互,成为了我日常重度使用的工具。从练习英语的听说读,到日常的搜索使用上都让我有了不少收获。终于,日常跑在我笔记本上的日子就要过去了,我需要它能日常跑在我的服务器上,所以准备使用Docker整个image,使得我日常的更新和服务器的迁移更为简单些。所以写下这个小纸条,方便以后自己回来查看。 安装Docker 使用Homebrew安装简单方便: brew install --cask docker Homebrew会视你的机器...

Mar 10, 20233 min read
macOS中使用Docker发布一个python项目的小纸条

老房东的纸条箱

39 posts