SwiftUI应用CoreData小纸条(六)

SwiftUI应用CoreData小纸条(六)

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

·

2 min read

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

Did you find this article valuable?

Support 老房东 by becoming a sponsor. Any amount is appreciated!