SwiftUI and dynamic NSSortDescriptors in @FetchRequest

I have a list with items that contain a title and a date. User can set what to sort on (title or date).

However I can't figure out how to change the NSSortDescriptor dynamically.
Code Block
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)], animation: .default) private var items: FetchedResults<Test>
@State private var sortType: Int = 0
@State private var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
var body: some View {
Picker(selection: $sortType, label: Text("Sort")) {
Text("Title").tag(0)
Text("Date").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortType) { value in
sortType = value
if sortType == 0 {
sortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
} else {
sortDescriptor = NSSortDescriptor(keyPath: \Test.date, ascending: true)
}
}
List {
ForEach(items) { item in
let dateString = itemFormatter.string(from: item.date!)
HStack {
Text(item.title!)
Spacer()
Text(dateString)
}
}
}
.onAppear(perform: {
if items.isEmpty {
let newEntry1 = Test(context: self.viewContext)
newEntry1.title = "Apple"
newEntry1.date = Date(timeIntervalSince1970: 197200800)
let newEntry2 = Test(context: self.viewContext)
newEntry2.title = "Microsoft"
newEntry2.date = Date(timeIntervalSince1970: 168429600)
let newEntry3 = Test(context: self.viewContext)
newEntry3.title = "Google"
newEntry3.date = Date(timeIntervalSince1970: 904903200)
let newEntry4 = Test(context: self.viewContext)
newEntry4.title = "Amazon"
newEntry4.date = Date(timeIntervalSince1970: 773402400)
if self.viewContext.hasChanges {
try? self.viewContext.save()
}
}
})
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()


When I change

@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Test.title, ascending: true)], animation: .default) private var items: FetchedResults<Test>

to

@FetchRequest(sortDescriptors: [sortDescriptor], animation: .default) private var items: FetchedResults<Test>

the error "Cannot use instance member 'sortDescriptor' within property initializer; property initializers run before 'self' is available" appears.

I also tried to store the NSSortDescriptor in a UserDefault and create an init that creates it's own FetchRequest.. still no dynamic sorting...

Anyone a pointer where to look to solve this problem?


Whole project found here: https://github.com/l1ghthouse/FRDSD

Accepted Reply

Solved! Thanks to Asperi pointing to this QA: https://stackoverflow.com/a/59345830/12299030
Code Block
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@AppStorage("firstLaunch") var firstLaunch: Bool = true
@State var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
@State private var sortType: Int = 0
var body: some View {
Picker(selection: $sortType, label: Text("Sort")) {
Text("Title").tag(0)
Text("Date").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortType) { value in
sortType = value
if sortType == 0 {
sortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
} else {
sortDescriptor = NSSortDescriptor(keyPath: \Test.date, ascending: true)
}
}
ListView(sortDescripter: sortDescriptor)
.onAppear(perform: {
if firstLaunch == true {
let newEntry1 = Test(context: self.viewContext)
newEntry1.title = "Apple"
newEntry1.date = Date(timeIntervalSince1970: 197200800)
let newEntry2 = Test(context: self.viewContext)
newEntry2.title = "Microsoft"
newEntry2.date = Date(timeIntervalSince1970: 168429600)
let newEntry3 = Test(context: self.viewContext)
newEntry3.title = "Google"
newEntry3.date = Date(timeIntervalSince1970: 904903200)
let newEntry4 = Test(context: self.viewContext)
newEntry4.title = "Amazon"
newEntry4.date = Date(timeIntervalSince1970: 773402400)
if self.viewContext.hasChanges {
try? self.viewContext.save()
}
firstLaunch = false
}
})
}
}
struct ListView: View {
@FetchRequest var items: FetchedResults<Test>
@Environment(\.managedObjectContext) var viewContext
init(sortDescripter: NSSortDescriptor) {
let request: NSFetchRequest<Test> = Test.fetchRequest()
request.sortDescriptors = [sortDescripter]
_items = FetchRequest<Test>(fetchRequest: request)
}
var body: some View {
List {
ForEach(items) { item in
let dateString = itemFormatter.string(from: item.date!)
HStack {
Text(item.title!)
Spacer()
Text(dateString)
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()

Add a Comment

Replies

Solved! Thanks to Asperi pointing to this QA: https://stackoverflow.com/a/59345830/12299030
Code Block
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@AppStorage("firstLaunch") var firstLaunch: Bool = true
@State var sortDescriptor: NSSortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
@State private var sortType: Int = 0
var body: some View {
Picker(selection: $sortType, label: Text("Sort")) {
Text("Title").tag(0)
Text("Date").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.onChange(of: sortType) { value in
sortType = value
if sortType == 0 {
sortDescriptor = NSSortDescriptor(keyPath: \Test.title, ascending: true)
} else {
sortDescriptor = NSSortDescriptor(keyPath: \Test.date, ascending: true)
}
}
ListView(sortDescripter: sortDescriptor)
.onAppear(perform: {
if firstLaunch == true {
let newEntry1 = Test(context: self.viewContext)
newEntry1.title = "Apple"
newEntry1.date = Date(timeIntervalSince1970: 197200800)
let newEntry2 = Test(context: self.viewContext)
newEntry2.title = "Microsoft"
newEntry2.date = Date(timeIntervalSince1970: 168429600)
let newEntry3 = Test(context: self.viewContext)
newEntry3.title = "Google"
newEntry3.date = Date(timeIntervalSince1970: 904903200)
let newEntry4 = Test(context: self.viewContext)
newEntry4.title = "Amazon"
newEntry4.date = Date(timeIntervalSince1970: 773402400)
if self.viewContext.hasChanges {
try? self.viewContext.save()
}
firstLaunch = false
}
})
}
}
struct ListView: View {
@FetchRequest var items: FetchedResults<Test>
@Environment(\.managedObjectContext) var viewContext
init(sortDescripter: NSSortDescriptor) {
let request: NSFetchRequest<Test> = Test.fetchRequest()
request.sortDescriptors = [sortDescripter]
_items = FetchRequest<Test>(fetchRequest: request)
}
var body: some View {
List {
ForEach(items) { item in
let dateString = itemFormatter.string(from: item.date!)
HStack {
Text(item.title!)
Spacer()
Text(dateString)
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter
}()

Add a Comment