Multiple sections from single data source SwiftUI

The specification’s are rather unclear. After reading everything more than once, I came to conclusion, that the desired outcome is to have a single source of truth, with different views for the same data.

We loop through categories, to create sections. Later on we loop through indices to actually display the content from Message struct.

import SwiftUI

struct MultipleSectionsView: View {
    @State private var messages = [
        Message(content: "Hello", category: .important, isSelected: false),
        Message(content: "World", category: .significant, isSelected: false),
        Message(content: "Hello", category: .important, isSelected: false),
        Message(content: "World", category: .significant, isSelected: false),
        Message(content: "Hello", category: .important, isSelected: false),
        Message(content: "World", category: .pointless, isSelected: false)
    ]

    var body: some View {
        Form {
            ForEach(Message.MessageCategory.allCases, id: \.self) { category in
                Section(category.rawValue.uppercased()) {
                    ForEach(messages.indices,  id:\.self) { index in
                        if messages[index].category == category {
                            HStack {
                                Image(
                                    systemName:
                                        messages[index].isSelected ? "checkmark.circle" : "circle"
                                )
                                
                                Text(messages[index].content)
                            }
                            .onTapGesture {
                                messages[index].toggleSelection()
                            }
                        }
                    }
                }
            }
        }
    }
}

struct Message: Identifiable {
    let id = UUID()
    let content: String
    let category: MessageCategory

    var isSelected: Bool

    mutating func toggleSelection() {
        isSelected = !isSelected
    }

    enum MessageCategory: String, CaseIterable {
        case pointless, important, significant
    }
}

In ForEach we need to use id. By using id we observe changes to the indices as well. Otherwise we could possibly get a crash by running out of bounds. You can test this out by simply adding messages = messages.dropLast() to the .onTapGesture closure and removing id:.self in the ForEach.

Not the most efficient solution, but I guess it does the job.

Homework: refactor code to a more efficient approach. At the moment we are looping through whole messages for each category. The solution should allow getting the data, without looping through messages for each category. Any other ideas for improvement?

Leave a Comment