UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Grouped list, but the category is in the view model

Forums > SwiftUI

Sorry if I'm repeating a settled topic here, but I'm not sure the right way to phrase this question to get the desired answer.

I want to create a grouped list. Each group should be separated by var category = String.

Here's my data model:

struct MindfulModel: Codable, Identifiable {
    var id = UUID().uuidString
    var description: String
    var image: String
    var name: String
    var audio: String
    var category: String
    var attribution: String
    var link: String

Here's my list code:

ForEach(self.mindfulModel.mindfulList, id:\.category) { category in
                        VStack(alignment: .leading) {
                            Section(header: Text(category.category)) {
                                HStack {
                                    ForEach(self.mindfulModel.mindfulList) { item in
                                        NavigationLink {
                                            AudioView(item: item)
                                        } label: {
                                            MindfulTile(item: item)
                                        .scrollTransition { effect, phase in
                                                .scaleEffect(phase.isIdentity ? 1 : 0.8)
                                                .opacity(phase.isIdentity ? 1 : 0.8)
class MindfulnessViewModel: ObservableObject  {

    @Published var mindfulList = [MindfulModel]()

    init() {

    func meditate() {

        // get reference from firebase
        let mindfulDb = Firestore.firestore()

        // read the docs at a specific path
        mindfulDb.collection("mindfulness").getDocuments { snapshot, error in

            // check for errors
            if error == nil{
                // no errrors, yay!

                if let snapshot = snapshot {

                    // update the mindfulList
                    DispatchQueue.main.async {
                        // get all the documents in mindfulness
                        self.mindfulList = snapshot.documents.map { mindfulDoc in
                            // create a mindful for each document returned
                            return MindfulModel(id: mindfulDoc.documentID,
                                                description: mindfulDoc["description"] as? String ?? "",
                                                image: mindfulDoc["image"] as? String ?? "",
                                                name: mindfulDoc["name"] as? String ?? "",
                                                audio: mindfulDoc["audio"] as? String ?? "",
                                                category: mindfulDoc["category"] as? String ?? "",
                                                attribution: mindfulDoc["attribution"] as? String ?? "",
                                                link: mindfulDoc["link"] as? String ?? "",
                                                time: mindfulDoc["time"] as? String ?? ""
            else {
                // handle the error

extension MindfulModel {
    init(name: String, description: String, image: String ,audio: String, category: String, attribution: String, link: String) {
        self.name = name
        self.description = description
        self.image = image
        self.audio = audio
        self.category = category
        self.attribution = attribution
        self.link = link
        self.time = time

As you can see, I'm able to put the category at the top of the side-scrolling list... But SwiftUI makes a discrete list for each instance of "category"; I only want one category line for each category.

Please help!


Would be useful if you could share details on this object self.mindfulModel.mindfulList. Is it array? Provide more information about it.


Information added as requested.



there was a related discussion of this just last week, so see this thread. (it was in the HWS Plus forum, but as a trial HWS+ member, you might still be able to see it?)

hope that helps,



I suppose one of the ways to handle this is to prepare data for such view, something like this. As to my understanding with your current model you cannot handle sections properly.

struct ContentView: View {
    // We initialize our model
    @StateObject private var vm = ItemModel()

    var body: some View {
        List {
            // We create our section
            ForEach(vm.dictOfItems, id: \.key) { key, value in
                Section(header: Text(key)) {
                    // we create our items
                    ForEach(value) { item in

struct ModelItem: Identifiable  {
    var id = UUID().uuidString
    var name: String
    var category: String

    // This is just mock data
    static var mockData = [
        ModelItem(name: "One", category: "B"),
        ModelItem(name: "Two", category: "B"),
        ModelItem(name: "Three", category: "C"),
        ModelItem(name: "Four", category: "D"),
        ModelItem(name: "Five", category: "C"),
        ModelItem(name: "Six", category: "A"),
        ModelItem(name: "Seven", category: "A"),
        ModelItem(name: "Eight", category: "B"),
        ModelItem(name: "Nine", category: "D"),

class ItemModel: ObservableObject {
    @Published var arrayOfItems = [ModelItem]()

    // As this property is computed, as soon as @Published "publishes" change this property recalculated again
    var dictOfItems: [(key: String, value: [ModelItem])] {
        // as soon as you have data in arrayOfItems you convert to data which can be used in grouped list
        // Here we convert fetched array into dictionary of items sorted by category
        // in the format -- [(key: String, value: [ModelItemOne])] e.g [(key: "A", value: ModelItem(name: "Six", category: "A")]
        Dictionary(grouping: arrayOfItems, by: { $0.category }).sorted(by: { $0.key < $1.key })

    init() {

    // You fetch your data from the sever and assign to @Published var
    // We simulate here for data to arrive in 3 sec
    func fetchData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.arrayOfItems = ModelItem.mockData


Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.