'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:
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.
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 |


