'Filtering data into a list with sections

I have data sorted by priority. I'm trying to display that data in a list as:

priority 1
   item with that priority
   item with that priority 
priority 2
   item with that priority
   item with that priority

So I thought using a list with sections to display the priority, and then list under each item that has that priority.

struct PriorityView: View {
    
    @EnvironmentObject private var taskdata: DataModel
    @State var byPriotity: Int
    
    var body: some View {
        List {
            VStack(alignment: .leading, spacing: 12) {
                // priority 3 is display all items in the list
                if byPriotity == 3 {
                    ForEach(taskdata.filteredTasks(byPriority: byPriotity), id: \.id) { task in
                        Section(header: Text(getTitle(byPriority: task.priority)).font(.headline)) {
                            Text(task.title)
                        }
                    }
                }
            }
        }
    }
}

This just creates a list with a section that displays the priority, and then the title of the item. Which is not what I wanted to output. I started reading about section and some articles (this, this, and this) provide some good advise, but I'm stuck trying to figure out how to iterate and find all items with a specific priority, create a header, and then list all those item's titles. Maybe I should add a second forEach filtering all of the items with that particular priority? Or save the initial filtering in a variable and then filter that? I don't know.

I tried with

            ForEach(taskdata.filteredTasks(byPriority: 3)) { task in
                Section(header: Text(getTitle(byPriority: task.priority)).font(.headline)) {
                    ForEach(task.priority.filter {$0.contains(byPriotity) || byPriotity.isEmpty}) {
                        Text(task.title)
                    }
                }
            }

But I can't filter them like that. Int doesn't have filter

Then I tried

ForEach(task.priority.isEqual(to: byPriotity))

And again, issues with an int not having isEqual. And I'm going in circles here...

Here's the code I'm trying:

struct Task: Codable, Hashable, Identifiable {
    var id = UUID()
    var title: String
    var priority = 2
}

extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Element].self, from: data)
        else {
            return nil
        }
        self = result
    }
    
    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self), // comment all this out
              let result = String(data: data, encoding: .utf8)
        else {
            return "[]"
        }
        return result
    }
}

final class DataModel: ObservableObject {
    @AppStorage("tasksp") public var tasks: [Task] = []
    
    init() {
        self.tasks = self.tasks.sorted(by: {
            $0.priority < $1.priority
        })
    }
    
    func sortList() {
        self.tasks = self.tasks.sorted(by: {
            $0.priority < $1.priority
        })
    }
    
    func filteredTasks(byPriority priority: Int) -> [Task] {
        tasks.filter { priority == 3 || $0.priority == priority }
    }
    
}

struct PriorityView: View {
    
    @EnvironmentObject private var taskdata: DataModel
    @State var byPriotity: Int
    
    var body: some View {
        List {
            VStack(alignment: .leading, spacing: 12) {
                // priority 3 is display all items in the list
                // I'm going to add another view so the user can 
                // can select what to display eventually
                if byPriotity == 3 {
                    ForEach(taskdata.filteredTasks(byPriority: byPriotity), id: \.id) { task in
                        Section(header: Text(getTitle(byPriority: task.priority)).font(.headline)) {
                            Text(task.title)
                        }
                    }
                }
            }
        }
    }

    func getTitle(byPriority:Int) -> String {
        switch byPriority {
        case 0: return "Important"
        case 1: return "Tomorrow"
        default: return "Someday"
        }
    }
}

EDIT:

Trying with the code that vadian suggested. I really don't know what I'm doing, but tried, and this is the result:

enter image description here

Changed the struct name to:

struct SectionOrdered : Identifiable {
    var id: String { priority }
    
    let priority: String
    let tasks: [Task]
}

The code I tried is:

List {
    VStack(alignment: .leading, spacing: 12) {
        if byPriority == 3 {
            ForEach(taskdata.sections) { task in
                Section(header: task.priority) {
                    Text(task.title)
                }
            }
        }
    }
}

vadian suggested to filter all in the class, but I have no clue how to use that. I tried, but I'm making a worse mess here.

I wonder if this is possible at all.

enter image description here



Solution 1:[1]

To group your data into sections first create a struct Section

struct Section : Identifiable {
    var id : String { priority }
    
    let priority : String
    let tasks : [Task]
}

And in the view model publish the sections and group them in for example in the init method.

final class DataModel: ObservableObject {
    @AppStorage("tasksp") public var tasks: [Task] = []
    
    @Published var sections = [Section]()
    
    init() {
        let grouped = Dictionary(grouping: tasks, by: \.priority)
        let sortedKeys = grouped.keys.sorted()
        self.sections = sortedKeys.map{Section(priority: getTitle(byPriority: $0), tasks: grouped[$0]!)}
    }

    func getTitle(byPriority: Int) -> String {
        switch byPriority {
            case 0: return "Important"
            case 1: return "Tomorrow"
            default: return "Someday"
        }
    }
....

The benefit is to keep all unnecessary data processing out of the view.

In the view show the data

ForEach(taskdata.sections) { section in
    Section {
        ForEach(section.tasks) { task in
            Text(task.title)
        }
    } header: {
       Text(section.priority)
          .font(.headline)
    }
}

If you want to group filtered data into sections first filter it then group it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1