ForEach Identifiable Struct Object Properties Into List Ordered By categoryName - struct

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

Related

using for loop with push() to create arrays within a new array

I'm working on a project where I need to declare customsItem formatted in a particular way.
The format given is:
var customsItem = {
"description":"T-Shirt",
"quantity":20,
"net_weight":"1",
"mass_unit":"lb",
"value_amount":"200",
"value_currency":"USD",
"origin_country":"US",
};
In my project however, I have multiple descriptions, so I need to make customsItem an array containing both.
I have array itemInCart =
[
{
itemDescription: 't-shirt',
qty: 1,
pre_orderQty: 1,
price: 30,
weight: 8
},
{
itemDescription: 'pants',
qty: 0,
pre_orderQty: 1,
price: 40,
weight: 5
}
]
I need to get these items in the correct format and within an array called customsItem. I thought to do this using a for loop with push(). Currently, I'm not getting anything when I try to console.log(customsItem), so I'm not sure if this is the best way to achieve the results that I am trying to get. I would really appreciate any help or advice on how to correctly get the results that I need. Thank you!
const customsItem = [];
for (var item of itemInCart) {
const items = {
"description":item.itemDescription,
"quantity":item.qty + item.pre_orderQty,
"net_weight":item.weight,
"mass_unit":"oz",
"value_amount":item.price,
"value_currency":"USD",
"origin_country":"US",
}
customItem.push(
items
)
}
You are not pushing into the correct array:
const customsItem = [];
for (var item of itemInCart) {
const items = {
"description":item.itemDescription,
"quantity":item.qty + item.pre_orderQty,
"net_weight":item.weight,
"mass_unit":"oz",
"value_amount":item.price,
"value_currency":"USD",
"origin_country":"US",
}
customItem.push( <---- needs to be customsItem.push
items
)
}

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.

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

Filtering an object via a different object via Vue Computed

I am trying build a table that is filtered by checkboxes. I have 3 objects:
selected: What check boxes are currently selected
sites: What populates the checkboxes. Object Structure: sites: [{ siteid, name } ...]
items: all table items. Object Structure: items: [{siteid, gradeid, cpl} ..]
Pastebin: https://pastebin.com/J2kBr2Xy
CodePen: https://codepen.io/tomdickson/pen/OqXpay
Hope this provides enough information
Try this code. I think this will work.
computed: {
filteredPositions () {
return this.items.filter(item => this.selected.includes(item.siteid));
}
}
Fiddle - https://jsfiddle.net/8x3yer54/1/
Just replace computed with this and it works fine.
computed: {
filteredSite() {
if (!this.selected.length || this.selected.includes(true))
return this.items
return this.items.filter(item => this.selected.find((item2) => item2.site==item.site))
}
}

Get field value from Array based on another field data matching

Below is my sample JSON object. I want to get the 'title' value based
on the longitude value. I have multiple(20-30) longitudes to get the titles, so I don't want to loop through those many times:
{
items:[
{
longitude:-72.897668,
latitude:40.453576,
title:52 street
},
{
longitude:-71.897668,
latitude:41.453576,
title:5th Ave
}
]
}
Can anyone suggest me how to get value without using for-loops.
Did you try something linke array.filter()?
function filterByLongitude(element) {
var expected = -72.897668
return element.longitude === expected;
}
var items = [{
longitude:-72.897668,
latitude:40.453576,
title:'52 street'
}, {
longitude:-71.897668,
latitude:41.453576,
title: '5th Ave'
}];
var match = items.filter(filterByLongitude);
console.log(match[0].title); // 52 street

Resources