SwiftUI reading a Core Data entity into a [] - core-data

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
)
)
}

Related

Coredata Fetchrequest Foreach loop with subsections of data and sort methods

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.

Node - Knex to return array of objects as a result of joined tables

I am using knexjs and node and running postgres db with this tables: menu and menuItem having one-to-many relationship. I found a solution here knex: what is the appropriate way to create an array from results? however this returns an array of strings. What i need is to return an array of objects and an empty array if null that looks exactly as the example below:
[
{
id: 123,
name: 'Sunday Menu',
items: []
},
{
id: 456,
name: 'Monday Menu',
items: [
{
id: 987,
name: 'Fried Chicken',
pcs: 69
},
{
id: 876,
name: 'Egg Soup',
pcs: 50
},
]
}
]
My menu and menuItem table schema looks similar to this:
menu_table: {
id,
name,
timestamps
}
menuItem_table: {
id,
menu_id,
name,
pcs,
timestamps
}
Currently, my code is like this:
knex('menu').leftJoin('menuitem', 'menu.id', 'menuitem.menu_id')
.select(['menu.id as menuID', knex.raw('ARRAY_AGG(menuitem.name) as items')])
.groupBy('menu.id')
And here's the result:
[
{
"menuID": "20091fff-ca8b-42d6-9a57-9f6e1922d0fa",
"items": [
null
]
},
{
"menuID": "2ddad4fa-7293-46c5-878f-cb2881be3107",
"items": [
"Fried Chicken",
"Egg Soup",
"Vegetable Dish"
]
}
]
UPDATE: I found out how to do it using raw query how ever i can't translate it using knex. Here's my code:
SELECT menu.*, COALESCE(menuitem.items, '[]') AS items FROM menu LEFT JOIN LATERAL (
SELECT json_agg(menuitem.*) AS items FROM menuitem WHERE menu.id = menuitem.menu_id
) menuitem ON true
I finally found a solution to my question. Since I was able to get my desired result thru raw query, i just translate it to knex. My final code is this:
const coalesce = knex.raw(`coalesce(menuitem.items, '[]') as items`)
const sub = knex('menuitem').select(knex.raw('json_agg(menuitem.*) as items')).whereRaw('menu.id = menuitem.menu_id')
return knex('menu').select(['menu.*', coalesce]).joinRaw('left join lateral ? menuitem on true', sub)
I'm gonna go with this code in the mean time until someone would give me the most accurate answer.
This work, im add attachments, ty DevWannabe :
const results = await knex.column({
'id': 'str.id',
'projectName': 'str.project_name',
})
.from('venturedoor.startups as str')
.leftJoin('venturedoor.attachments as att', 'str.id', 'att.startup_id')
// join array attachments
.select(['str.id', knex.raw('ARRAY_AGG(att.*) as attachments')])
.groupBy('str.id')
knex.raw('coalesce(usr.userName,usr.email ,seat.username) as username')
We can use like that measn. If userName is empty so will call email if email also then will call name from seats.

knex js query many to many

i'm having trouble with node & knex.js
I'm trying to build a mini blog, with posts & adding functionality to add multiple tags to post
I have a POST model with following properties:
id SERIAL PRIMARY KEY NOT NULL,
name TEXT,
Second I have Tags model that is used for storing tags:
id SERIAL PRIMARY KEY NOT NULL,
name TEXT
And I have many to many table: Post Tags that references post & tags:
id SERIAL PRIMARY KEY NOT NULL,
post_id INTEGER NOT NULL REFERENCES posts ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags ON DELETE CASCADE
I have managed to insert tags, and create post with tags,
But when I want to fetch Post data with Tags attached to that post I'm having a trouble
Here is a problem:
const data = await knex.select('posts.name as postName', 'tags.name as tagName'
.from('posts')
.leftJoin('post_tags', 'posts.id', 'post_tags.post_id')
.leftJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
Following query returns this result:
[
{
postName: 'Post 1',
tagName: 'Youtube',
},
{
postName: 'Post 1',
tagName: 'Funny',
}
]
But I want the result to be formated & returned like this:
{
postName: 'Post 1',
tagName: ['Youtube', 'Funny'],
}
Is that even possible with query or do I have to manually format data ?
One way of doing this is to use some kind of aggregate function. If you're using PostgreSQL:
const data = await knex.select('posts.name as postName', knex.raw('ARRAY_AGG (tags.name) tags'))
.from('posts')
.innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
.innerJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
.groupBy("postName")
.orderBy("postName")
.first();
->
{ postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }
For MySQL:
const data = await knex.select('posts.name as postName', knex.raw('GROUP_CONCAT (tags.name) as tags'))
.from('posts')
.innerJoin('post_tags', 'posts.id', 'post_tags.post_id')
.innerJoin('tags', 'tags.id', 'post_tags.tag_id')
.where('posts.id', id)
.groupBy("postName")
.orderBy("postName")
.first()
.then(res => Object.assign(res, { tags: res.tags.split(',')}))
There are no arrays in MySQL, and GROUP_CONCAT will just concat all tags into a string, so we need to split them manually.
->
RowDataPacket { postName: 'post1', tags: [ 'tag1', 'tag2', 'tag3' ] }
The result is correct as that is how SQL works - it returns rows of data. SQL has no concept of returning anything other than a table (think CSV data or Excel spreadsheet).
There are some interesting things you can do with SQL that can convert the tags to strings that you concatenate together but that is not really what you want. Either way you will need to add a post-processing step.
With your current query you can simply do something like this:
function formatter (result) {
let set = {};
result.forEach(row => {
if (set[row.postName] === undefined) {
set[row.postName] = row;
set[row.postName].tagName = [set[row.postName].tagName];
}
else {
set[row.postName].tagName.push(row.tagName);
}
});
return Object.values(set);
}
// ...
query.then(formatter);
This shouldn't be slow as you're only looping through the results once.

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

Mixed schemas in a single subdocument array

If I have a parent schema like:
{
doc_name:String,
doc_collection:[?????]
}
and subdoc children:
child1 =
{
child_type: String,
property1: String,
property2: Number
}
child2 =
{
child_type: string,
different_property1: Number,
much_different_property2: String
}
can parentschema.doc_collection hold subdocuments of both child1 and child2 schemas?
Or do I have to do:
{
doc_name:String,
doc_collection:
{
child1:[child1],
child2:[child2]
}
}
I would normally create a subdocument schema that can encompass properties of ALL the types of objects I'm tring to stick in the array, but these are just too different. From the controller perspective, the child schemas are all of type doc_collection.
if my memories are good, mongoose doesn't handle complex type array (in term of content validation)
that means, if your model is like :
{
doc_collection : []
}
{
doc_collection : [child1]
}
is the same.
and the worst is that if your model is
{
doc_collection : [child1]
}
you would be able to add anythind in doc_collection array
myModel.doc_collection.push(anyChild2);
take a look to the array chapter : http://mongoosejs.com/docs/schematypes.html

Resources