Reputation: 2866
I am writing an app for iOS using SwiftUI and CoreData. I am trying to solve one problem for a few days now. How to make dynamic filters using dynamically changing predicate in SwiftUI based on user input?
I have followed this tutorial to learn about dynamic filters and CoreData: https://www.hackingwithswift.com/quick-start/ios-swiftui/dynamically-filtering-fetchrequest-with-swiftui
After few small changes I have the following code. ContentView.swift:
import SwiftUI
struct ContentView: View {
@Environment(\.managedObjectContext) var moc
@State var lastNameFilter = "A"
var body: some View {
VStack {
FilteredList(predicate: lastNameFilter)
Button("Add Examples") {
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"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
FilteredList.swift:
import CoreData
import SwiftUI
struct FilteredList: View {
var predicate:String
var fetchRequest: FetchRequest<Singer>
var singers: FetchedResults<Singer>{fetchRequest.wrappedValue}
var body: some View {
List(singers, id: \.self) { singer in
Text("\(singer.firstName ?? "Unknown") \(singer.lastName ?? "Unknown")")
}
}
init(predicate: String) {
self.predicate = predicate
self.fetchRequest = FetchRequest<Singer>(entity: Singer.entity(), sortDescriptors: [], predicate: NSPredicate(format: "lastName BEGINSWITH %@", predicate))
}
}
//struct FilteredList_Previews: PreviewProvider {
// static var previews: some View {
// }
//}
I also have 1 entity named Singer and this entity has 2 attributes: firstName and lastName, both of which are Strings. Above example seems to work fine in Simulator, but crashes the app when using Preview in Xcode.
I would appreciate any help, for example:
Upvotes: 2
Views: 2787
Reputation: 30746
Here is another way:
import CoreData
import SwiftUI
struct FilteredList: View {
@FetchRequest var singers: FetchedResults<Singer>
init(lastName: String) {
_singers = FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "lastName BEGINSWITH %@", lastName))
}
var body: some View {
List(singers) { singer in
Text("\(singer.firstName ?? "Unknown") \(singer.lastName ?? "Unknown")")
}
}
}
Be aware that every time the View is init and the FetchRequest is init the database is hit so you may need to lift the fetch into a superview.
Upvotes: 0
Reputation: 448
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let taylor = Singer(context: context)
taylor.firstName = "Taylor"
taylor.lastName = "Swift"
let ed = Singer(context: context)
ed.firstName = "Ed"
ed.lastName = "Sheeran"
let adele = Singer(context: context)
adele.firstName = "Adele"
adele.lastName = "Adkins"
return ContentView().environment(\.managedObjectContext, context)
}
struct FilteredList<T: NSManagedObject, Content: View>: View {
var fetchRequest: FetchRequest<T>
var items: FetchedResults<T> { fetchRequest.wrappedValue }
let content: (T) -> Content
var body: some View {
List(items, id: \.self) { item in
self.content(item)
}
}
init(predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor] = [], @ViewBuilder content: @escaping (T) -> Content) {
fetchRequest = FetchRequest<T>(entity: T.entity(), sortDescriptors: sortDescriptors, predicate: predicate)
self.content = content
}
}
Use this new FilteredList
type with @State private var predicate: NSPredicate?
instead of @State var lastNameFilter = "A"
. When new filtering is needed just set this private @State property to the new predicate and the list will be updated accordingly.
The concrete usage would be:
FilteredList(predicate: predicate) { (singer: Singer) in
Text("\(singer.firstName ?? "") \(singer.lastName ?? "")")
}
Preview of the FilteredList:
static var previews: some View {
FilteredList(predicate: nil) { (singer: Singer) in
Text("\(singer.firstName ?? "") \(singer.lastName ?? "")")
}.environment(\.managedObjectContext, (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext)
}
Upvotes: 3