Coredata Fetchrequest Foreach loop with subsections of data and sort methods - core-data

I have a data model entity called Object laid out like so:
name: String
createdDate: Date()
updatedDate: Date()
My fetch request:
#FetchRequest(sortDescriptors: [SortDescriptor(\.updatedDate, order: .reverse)]) var objects: FetchedResults<Object>
I want to have my list of objects sorted with the most recently updated (keyed off updatedDate) at the top. I chose this sort method, because when combined with withAnimation I get a real nice animation when items lower in the list are updated and move to the top.
When the user creates a new object however, it goes to the bottom of the list because this new object doesn't have a value for updatedDate (intentional because my users tell the app when an object is updated). This behaviour makes sense but is undesired.
My current ObjectListView looks like this (simplified to focus on my question):
struct intervalsExist: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [SortDescriptor(\.updatedDate, order: .reverse)]) var objects: FetchedResults<Object>
var body: some View {
List {
ForEach(objects) { object in
NavigationLink {
ObjectDetailView(object: object)
} label: {
Text(object.name ?? "Unknown name")
}
.swipeActions(edge: .leading) {
withAnimation {
// Update `updatedDate` with current Date()
}
}
}
}
}
}
What I would like is for my list of objects to be presented as such:
---
Object with no updatedDate, most recent
Object with no updatedDate
Object with no updatedDate
Object with updatedDate, most recent
Object with updatedDate
Object with updatedDate
---
Where objects with updatedDate will reorder themselves to the the newest at the top when they are updated.
I tried using 2 sort methods in my fetch request, like so:
#FetchRequest(sortDescriptors: [
SortDescriptor(\.createdDate, order: .reverse),
SortDescriptor(\.updateDate, order: .reverse),
]) var objects: FetchedResults<Object>
While this gets me the ordering I am looking for above, items no longer re-order themselves within the list which is a key part of what I'm trying to build.

Related

SwiftUI reading a Core Data entity into a []

I have a Core Data entity with the attributes "latitude" and "longitude," and I want to read its data into a [] for MapKit's MapAnnotation. I understand how to use ForEach to iterate through an entity to create a View, as seen in the sample below, but I don't understand how I read data into a [].
ForEach(stores.reversed()) { store in
HStack {
Text("\(store.name ?? "")")
Spacer()
Text("\(store.latitude, specifier: "%.3f"),")
Text("\(store.longitude, specifier: "%.3f")")
}
}
"locations" is a variable containing coordinates for MapAnnotation.
"stores" is a variable containing the fetched entity data.
"Location" is an identifiable for the CLLocationCoordinate2D format.
Below is what I've attempted, but clearly it is wrong. How do I iterate through "stores" correctly?
#State var locations: [Location] = [
for store in stores {
Location(coordinate: .init(latitude: store.latitude, longitude: store.longitude))
}
]
A for loop inside of array brackets does nothing because it doesn’t return anything. I think what you're looking for is .map
#State var locations: [Location] = stores.map { store in
Location(
coordinate: .init(
latitude: store.latitude,
longitude: store.longitude
)
)
}

Core Data #FetchRequest SwiftUI

I am trying to use a fetch request in a SwiftUI Charts package/CocoaPod. My issue is that when I use a ForEach to iterate over the Attribute (ACFTScores.totalScore) it populates one value per chart... I know this is because the BarChart is in the body of the ForEach but I don't know how to grab the range of the fetch and make it work with the SwiftUI Charts.
struct DashboardACFT: View {
#FetchRequest(entity: ACFTScores.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ACFTScores.totalScore, ascending: true)]) var acftScores: FetchedResults<ACFTScores>
var body: some View {
VStack {
ForEach(acftScores, id: \.id) { score in
BarChartView(data: [Int(score.totalScore)], title: "Title", legend: "Legendary")
}
}
}
}
I haven't worked with charts but you need to map your fetch request into an array containing the values you want to use in your chart. Something like this
var body: some View {
VStack {
BarChartView(data: acftScores.map {$0.totalScore}, title: "Title", legend: "Legendary")
}
}
}
Since you for some reason seems to need to convert the property to Int using compactMap might be better.
acftScores.compactMap {Int($0.totalScore)}

ForEach Identifiable Struct Object Properties Into List Ordered By categoryName

I feel like I'm missing something obvious here, but I've been stuck on this for a couple of days and just can't seem to find the answer.
1.) I'm using a separate swift file with an Identifiable Struct that has an object with 2 of the Struct properties, name & categoryName. (side note, I'm using var instead of let in the object because the rows can't be rearranged with the .onMove modifier as a constant)
//STRUCT
struct Item: Identifiable {
var id = UUID()
var name: String
var categoryName: String
}
//OBJECT
var items : [Item] = [
//CLOTHING
Item(name: "Hats", categoryName: "Clothing"),
Item(name: "Shirts", categoryName: "Clothing"),
Item(name: "Pants", categoryName: "Clothing"),
//Electronics
Item(name: "Macbook", categoryName: "Electronics"),
Item(name: "Macbook Adapter", categoryName: "Electronics"),
Item(name: "iPhone", categoryName: "Electronics"),
]
2.) In a swiftui file I have this code to build the list, using a nested ForEach loop to pull the categoryName, add it to the Section header, then another to loop out the items.
//List code
NavigationView {
List {
ForEach(items) { currentItem in
Section(header: Text(currentItem.categoryName)){
ForEach(items) { currentItem in
NavigationLink(destination: ItemDetail(itemData: currentItem)){ ItemRow(item: currentItem)
}
}
Unfortunately what I get is a laughable result.
I get my categoryName in the section header and I get my items listed below it. Actually, I get ALL of the items listed below it, regardless of category. Then in a very confusing fashion the sections will print out exactly as many times as the rows in my object array.
In this instance I have 6, so I get 6 rows. Yet, of the 2 categoryName strings "Clothing" and "Electronics", they'll print out 3 times each.
It feels like there's a simple way to do "for each categoryName in items.categoryName add a title to the section and list the corresponding name" - but I'm not cracking this one.
Hoping someone here can point out what I'm doing wrong.
You have flat array and just iterate though it several times, so result is also flat multiplied several times.
Ok, for model you selected, ie items, the result you tried to accomplish can be reached with the following...
List {
ForEach(Array(Set(items.compactMap{ $0[keyPath: \.categoryName] })), id: \.self) { category in
Section(header: Text(category)) {
ForEach(items.filter { $0.categoryName == category }) { currentItem in
NavigationLink(destination: Text("ItemDetail(itemData: currentItem)")){ Text("\(currentItem.name)") }
}
}
}
}
.listStyle(GroupedListStyle())
However I would select different model for items, ie. dictionary as [categoryName: Item]
You can group your array by category:
let sections = Dictionary(grouping: items) { $0.categoryName }
Then you can use it like:
var keys: [String] = { sections.map {$0.key} }
var body: some View {
List {
ForEach(keys, id: \.self) { section in
Section(header: Text(section)){
ForEach(self.sections[section] ?? []) { currentItem in
Text(currentItem.name)
}
}
}
}
}
Note that I have simplified your code to run on my machine but changes are not effecting the answer

Angular add new doc in firestore without list

I am new in angular and firebase, I got a crud operation project which work like below
export class EmployeeService {
employeeList: AngularFireList<any>;
selectedEmployee: Employee = new Employee();
constructor(private firebase: AngularFireDatabase) { }
getData() {
this.employeeList = this.firebase.list('employees');
return this.employeeList;
}
insertEmployee(empoloyee: Employee) {
this.employeeList.push({
name: empoloyee.name,
position: empoloyee.position,
office: empoloyee.office,
salary: empoloyee.salary
});
}
updateEmployee(emp : Employee){
this.employeeList.update(emp.$key,{
name : emp.name,
position : emp.position,
office : emp.office,
salary : emp.salary
})
}
deleteEmployee(key : string){
this.employeeList.remove(key);
}
}
Here as per my understanding to add a new record it add items into list which has been fetched through getData.
Is there possible to add a new doc in collection without fetching list
Thanks
You are not fetching any data.
this.firebase.list('employees') is a list binding. Data is only fetched if you add valueChanges() and subscribe() to this observable created by valueChanges()

Mongoose update dependent field from another field's setter?

I have a scenario in node/express/mongoose where I have users who accumulate points, and when that point total crosses a certain threshold, they "level up" (think games with point-based levels).
I have created a custom setter on the points field that checks if the value has changed, and if so tries to update the level field. Levels are defined in another collection, but are saved as simple JSON objects when associated with user docs (hence the .lean() in the query). I did it this way vs a virtual field or population for efficiency.
Problem: this doesn't actually seem to update the user 'level' field when it should. What am I doing wrong?
// Define our user schema
var UserSchema = new mongoose.Schema({
...
points: {type: Number, default: 0, set: pointsChangeHandler},
level: {name: String, minPoints: Number, maxPoints: Number},
...
});
And the setter looks like so:
function goodPointsChangeHandler(newValue) {
var self = this;
if (newValue != self.goodPoints) {
console.log('Point change detected.');
// Find level that corresponds with new point total
Level.findOne({
'minPoints': {$lte : self.goodPoints},
'maxPoints': {$gt : self.goodPoints}}, '-_id').lean().exec(function(err, level) {
if (self.goodLevel == undefined || self.goodLevel.rank != level.rank) {
console.log('Level changed.');
self.goodLevel = level;
}
return newValue;
});
}
return newValue;
}
Based on #laggingreflex's comment, I tried modifying this within the scope of the model method (i.e. not in the Level.findOne() callback, and changes made that way were persisted without an explicit save() call.
Also, I had a pretty silly error where I was returning newValue from thefindOne` callback.. not sure what I was thinking there...
Long story short, and this may be obvious to node/express/mongoose experts, but you can modify fields other than the one whose setter method you're currently in, but the moment you find yourself in the callback of another async method, you'll have to do an explicit save() or your modifications to this will not be persisted.
So:
function myFieldSetterMethod(newValue) {
this.myField = "a";
this.myOtherField = "b";
return newValue;
// no save() necessary, this will update both fields
}
function myFieldSetterMethod(newValue) {
this.myField = "a";
SomeModel.findOne(..., function(err, doc) {
this.myOtherField = doc.somethingIWantFromThisDoc;
// now you'll need an explicit save
// this.save(...)
});
return newValue;
}

Resources