What would be a good way to partition instances by a particular matching property, such that each partition is a collection view section?

Say that in this example here, the struct

struct Reminder: Identifiable {
    var id: String = UUID().uuidString
    var title: String
    var dueDate: Date
    var notes: String? = nil
    var isComplete: Bool = false
    var city: String
}

is modified slightly to include a city string. In the collection view that displays the reminders, I'd like each section to be each unique city, so if two reminder cells have the same city string then they would be in the same section of the collection view.

The progress I've made to this end is sorting the reminders array so that reminders cells are grouped together by city

func updateSnapshot(reloading ids: [Reminder.ID] = []) {
    var snapshot = Snapshot()
    snapshot.appendSections([0])
    let reminders = reminders.sorted { $0.city }
    snapshot.appendItems(reminders.map { $0.id })
    if !ids.isEmpty {
        snapshot.reloadItems(ids)
    }
    dataSource.apply(snapshot)
}

Where I'm stuck is in coming up with a way to make the snapshot represent sections by unique cities, and not just one flat section of all reminders.

Replies

You should first create a dictionary, with city as key and [reminder] as data. That will be your data source.

var dictByCity = [String: [Reminder]]  ()
for reminder in reminders {
    if dictByCity[reminder.city] == nil {  // entry not created yet
        dictByCity[reminder.city] = [reminder]  // Create with a first element in array
    } else {
        dictByCity[reminder.city]!.append(reminder)  // key already created, append a new reminder
    }
}

Note that dictionary is not ordered, we shall have to order later. To prepare, let's build the array of sorted cities

let sortedCities: [String] = dictByCity.keys.sorted { $0.city }

You have 3 delegate func to define (adapt to snapshot if needed, but snapshot may not be necessary here):

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
        return dictByCity.keys.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // The row correspond to rank in sortedCities
        let remindersInSection = dictByCity[sortedCities[section]]  // This is an [Reminder]
        return remindersInSection.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let remindersInSection = dictByCity[sortedCities[indexPath. section]]  // This is an [Reminder]
        //  You populate the cell with the data from remindersInSection[indexPath.row]
       
    }

Please tell if anything unclear