TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

List Items Inside if Statements

Forums > SwiftUI

To summarise, my problem is this: I want to take an array of items and display them in a list, with the items being displayed in different sections of the list depending on certain properties of the items. I can achieve this by creating the sections one by one, but when I try to simplify the process with a ForEach it no longer works as expected. The sections work, but I can no longer add items to the list.

Here's a simplified example which illustrates the problem:

First, I set up a Vegetable struct and a Basket class:

struct Vegetable : Identifiable {
    let id = UUID()
    let name : String
    let freshness : Int
}

class Basket : ObservableObject {
    @Published var vegetables : [Vegetable]

    init() {
        // Some example vegetables
        let carrot = Vegetable(name: "Carrot", freshness: 1)
        let cucumber = Vegetable(name: "Cucumber", freshness: 0)
        let parsnip = Vegetable(name: "Parsnip", freshness: 1)
        vegetables = [carrot, cucumber, parsnip]
    }
}

Then for the main view:

struct TestView: View {

    @ObservedObject var basket = Basket()

    let sectionHeadings = ["FRESH", "NOT FRESH"]

    var body: some View {

        List {

            Button("Add a fresh potato") {
                self.basket.vegetables.append(Vegetable(name: "Potato", freshness: 1))
            }.foregroundColor(.blue)                        

            Section(header: Text(sectionHeadings[0])) {
                ForEach(self.basket.vegetables) { vegetable in
                    if vegetable.freshness == 0 {
                        Text(vegetable.name)
                    }
                }
            }

            Section(header: Text(sectionHeadings[1])) {
                ForEach(self.basket.vegetables) { vegetable in
                    if vegetable.freshness == 1 {
                        Text(vegetable.name)
                    }
                }
            }             
        }
    }
}

This works perfectly. I see a list with two sections ("Not fresh" and "Fresh"), and a new row containing "Potato" is added to the "Fresh" section every time I press the button. However, when I try to simplify the code using a ForEach as shown below, this no longer works.

struct TestView: View {

    @ObservedObject var basket = Basket()
    let sectionHeadings = ["FRESH", "NOT FRESH"]

    var body: some View {

        List {

            Button("Add a fresh potato") {
                self.basket.vegetables.append(Vegetable(name: "Potato", freshness: 1))
            }.foregroundColor(.blue)

            ForEach(0..<2) { i in
                Section(header: Text(self.sectionHeadings[i])) {
                    ForEach(self.basket.vegetables) { vegetable in
                        if vegetable.freshness == i {
                            Text(vegetable.name)
                        }
                    }
                }
            }                                            
        }
    }
}

With this modificaiton, the code will compile and the items are displayed in the correct sections, but when I click the button to "Add a fresh potato", nothing appears in the list. From a little experimenting with a Tab View, it seems that the button is adding a new vegetable item to basket but that the list isn't updating to reflect this, as if I press the button then change tabs and come back again I can see the new potato.

I would be hugely grateful if anyone could explain why the first option works but the second does not!

2      

hi,

i've played around with this issue recently ... it drove me crazy for awhile, and still does ... and the best i could come up with is to first, make Vegetable Hashable (nothing special to do here other than write struct Vegetable : Identifiable, Hashable { in the definition), and then base the List/ForEach/Section/ForEach structure on a list of lists (one list for each section, where each element is then a list of vegetables with the same freshness).

so the following code seems to work for me:

struct ContentView: View {
    @ObservedObject var basket = Basket()
    let sectionHeadings = ["FRESH", "NOT FRESH"]
    @State private var sectionData = [[Vegetable]]()

    var body: some View {

        List {
            Button("Add a fresh potato") {
                self.basket.vegetables.append(Vegetable(name: "Potato", freshness: 0))
                self.buildSections()
            }.foregroundColor(.blue)

            ForEach(sectionData, id:\.self) { vegetables in
                Section(header: Text(self.titleString(for: vegetables))) {
                    ForEach(vegetables) { vegetable in
                            Text(vegetable.name)
                    }
                }
            }
        }
        .onAppear(perform: buildSections)
    }

    func titleString(for vegetables: [Vegetable]) -> String {
        if let firstVegetable = vegetables.first {
            return sectionHeadings[firstVegetable.freshness]
        }
        return "Unknown"
    }

    func buildSections() {
        sectionData.removeAll()
        let d = Dictionary(grouping: basket.vegetables, by: { $0.freshness })
        let sortedKeys = d.keys.sorted()
        for key in sortedKeys {
            sectionData.append(d[key]!)
        }
    }

}

i do get a warning on the console ("Warning once only: UITableView was told to layout its visible cells ..."), but the code seems to work fine. this could well be one of those SwiftUI things that will get fixed real soon, and one of those messages many folks seem to tell you to ignore.

i'm hoping for an easier way to do this in the next version of SwiftUI.

hope that helps,

DMG

3      

This works perfectly - thank you so much for your help! I've now got this working in both the simplified version and my original project. As you say, hopefully the issue will be fixed soon, but in the meantime this does the job nicely.

Thanks again!

Alasdair

2      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.