Table of contents
在SwiftUI应用CoreData小纸条(五)之后,停了很久,主要的原因还是发现了如果使用了SwiftPM后SwiftUI的Preview就会挂掉,昨天沉下心来找到了这个问题的原因,并使用一个自定义的modul来修复SwiftUI Preview挂掉的问题。所以接下来又会开始继续这个系列的内容。在学习英语小助手中已经有近千词汇了,日常中非常需要一个搜索功能来查询字典中的词汇。
CoreData数据结构
首先我们看看CoreData中的数据结构定义:
在这个示例中我们其实只需要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]忽略大小写
最后来看看效果: