SwiftUI Core Data:动态过滤 @FetchRequest

\color{red}{\Large \mathbf{Hacking \quad with \quad iOS: SwiftUI \quad Edition}}

{\Large \mathbf{Core \ Data}}

Dynamically filtering @FetchRequest - 韦弦zhy

我被问到的有一个SwiftUI问题比其他任何问题都多:如何动态更改Core Data @FetchRequest以使用其他谓词或排序顺序?出现问题是因为获取请求被创建为一个属性,因此,如果您尝试使它们引用另一个属性,Swift将拒绝。

这里有一个简单的解决方案,通常回想起来很明显,因为它正是其他一切工作的原理:我们应该将想要的功能分解到一个单独的视图中,然后将值注入其中。

我想用一些真实的代码来演示这一点,因此,我整理了一个最简单的示例:它将三个歌手添加到Core Data,然后使用两个按钮显示姓氏以A或S结尾的歌手。

首先创建一个名为Singer的新Core Data实体,并为其指定两个字符串属性:“firstName”和“lastName”。使用数据模型检查器将其Codegen 更改为 Manual / None,然后转到 Editor 菜单,然后选择 Create NSManagedObject Subclass,这样我们就可以获取可以自定义的Singer类。

Xcode为我们生成文件后,打开 Singer+CoreDataProperties.swift 并添加以下两个属性,这些属性使该类更易于在SwiftUI中使用:

var wrappedFirstName: String {
    firstName ?? "Unknown"
}

var wrappedLastName: String {
    lastName ?? "Unknown"
}

好的,现在进入真正的工作。

第一步是设计一个可容纳我们信息的视图。就像我说的那样,这还将有两个按钮,使我们可以更改视图过滤的方式,并且还将有一个额外的按钮来插入一些测试数据,以便您查看其工作方式。

首先,向您的 ContentView 结构体添加两个属性,以便我们有一个可以将对象保存到的托管对象上下文,以及可以用作过滤器的某些状态:

@Environment(\.managedObjectContext) var moc
@State private var lastNameFilter = "A"

对于视图的主体,我们将使用带有三个按钮的VStack,并在注释中添加希望List显示匹配歌手的位置:

VStack {
    // 匹配的歌手列表

    Button("添加示例") {
        let taylor = Singer(context: self.moc)
        taylor.firstName = "Taylor"
        taylor.lastName = "Swift"

        let ed = Singer(context: self.moc)
        ed.firstName = "Ed"
        ed.lastName = "Sheeran"

        let adele = Singer(context: self.moc)
        adele.firstName = "Adele"
        adele.lastName = "Adkins"

        try? self.moc.save()
    }

    Button("Show A") {
        self.lastNameFilter = "A"
    }

    Button("Show S") {
        self.lastNameFilter = "S"
    }
}

到目前为止,非常容易。现在,对于有趣的部分:我们需要将// 匹配的歌手列表替换为真实的东西。这将不会使用@FetchRequest,因为我们希望能够在初始化程序中创建自定义提取请求,但是我们将使用的代码几乎相同。

创建一个名为“FilteredList”的新SwiftUI视图,并为其提供以下属性:

var fetchRequest: FetchRequest<Singer>

这将存储我们的提取请求,以便我们可以在body内循环它。但是,由于我们仍然不知道要搜索的内容,因此我们不在此处创建提取请求。相反,我们将创建一个自定义初始化程序,该初始化程序接受过滤器字符串,并使用该字符串来设置fetchRequest属性。

现在添加此初始化程序:

init(filter: String) {
    fetchRequest = FetchRequest<Singer>(
        entity: Singer.entity(),
        sortDescriptors: [],
        predicate: NSPredicate(format: "lastName BEGINSWITH %@", filter)
    )
}

这将使用当前的管理对象上下文运行获取请求。由于此视图将在ContentView中使用,因此我们甚至不需要将托管对象上下文注入到环境中——它将从ContentView继承上下文。

剩下的就是编写视图主体,这里唯一有趣的是,如果没有@FetchRequest,我们需要读取fetchRequestwrappedValue属性以提取数据。因此,给出以下内容:

var body: some View {
    List(fetchRequest.wrappedValue, id: \.self) { singer in
        Text("\(singer.wrappedFirstName) \(singer.wrappedLastName)")
    }
}

如果您不喜欢使用fetchRequest.wrappedValue,则可以创建一个简单的计算属性,如下所示:

var singers: FetchedResults<Singer> { fetchRequest.wrappedValue }

至于FilteredList的预览结构,可以安全地将其删除。

现在,视图已完成,我们可以返回ContentView并将注释替换为将过滤器传递到FilteredList的一些实际代码:

FilteredList(filter: lastNameFilter)

现在运行该程序进行尝试:首先点击“添加示例”按钮以创建三个歌手对象,然后点击“Show A”或“ Show S”在姓氏字母之间切换。您应该看到我们的列表根据不同的数据动态更新,具体取决于您按的是哪个按钮。

因此,这项工作需要一点新知识,但实际上并没有那么难——只要您像SwiftUI一样思考,解决方案就在那里。

译自 Dynamically filtering @FetchRequest with SwiftUI