Dynamically Hiding Empty Sections of List in SwiftUI

162 views Asked by At

I have a dynamic SwiftUI List that populates utilizing an enum for States. Then it cycles through my dictionary of data looking for a state match to display data. All of this works perfectly. What I am looking to do but struggling with is to hide those states that are then empty.

The section of code in question is:

@State private var bucketList: [BucketListItems] = []
...
List {
    ForEach(USStates.allCases) { states in
        Section {
            ForEach (bucketList) { item in
                if (item.state == states.abrev) {
                    NavigationLink(destination: ItemDetails(blItem: item.recordID)) {
                        Label(item.name, systemImage: item.monogram)
                                    .frame(height: 50)
                            .foregroundColor(item.completedate=="0000-00-00" ? .white : .red)
                    }
                }
            }
        } header: {
            Text(states.rawValue)
        }
    }
}

When a state doesn't have a match, then I want that entire section to be hidden.

Some additional relevant code:

struct BucketListItems: Encodable, Decodable, Identifiable, Hashable {
    let id = UUID()
    var recordID: Int
    var url: String
    var user: Int
    var name: String
    var category: String
    var state: String
    var latitude: String
    var longitude: String
    var description: String
    var completedate: String
}
4

There are 4 answers

0
workingdog support Ukraine On BEST ANSWER

Since you do not show a minimum example code (ie, we have to guess what USStates.allCases are etc...),

you could try these approaches, assuming that states is an [String] type.

One is to test the state before the Section, such as:

 if !bucketList.filter({states.contains($0.state)}).isEmpty {
     Section { ....

 }
 

Another approach is to do the filtering in the ForEach, such as:

 ForEach(bucketList.filter({states.contains($0.state)})) { item in
    // ...
 }
1
spentag On

This is not a SwiftUI problem, it is a data filtering problem. Don't loop through states which have no business showing in this view.

You could instead have a separate var which only provides states which are in the bucketList- instead of using .allCases in this unfiltered fashion.

Now, in your code (Or better yet, in your viewModel or presenter), you could do this:

var filteredStates: [USStates] {
    USStates.allCases.filter { state in
        bucketList.contains(state)
        # note: maybe you need to compare `abbrev` here- I see from your code they are likely different types.
    }
}

and then use that instead of USStates.allCases for iteration in the List.

List {
    ForEach(filteredStates) { states in /* ... */ }
    /** etc... **/

}
1
rushisangani On

You can declare a computed variable displaySections similar to below:

// <your states enum cases here>
var sections: [String] = ["Section 1", "Section 2", "Section 4"]

// <Your bucket list here> 
var list = PremiumKeyFeatures.list

var displaySections: [String] {
    sections.filter { list.map { $0.section }.contains($0) }
    // apply filter and remove empty sections accordingly
}

And then display it.

var body: some View {
    List {
        ForEach(displaySections, id: \.self) { title in
            Section {
                ForEach (list) { item in
                    if title == item.section {
                        Text(item.title)
                    }
                }
            } header: {
                Text(title)
            }
        }
    }
1
You Can Do It Duffy Moon On

In my case I wasn't able to get the above solutions to work. However, I was able to figure out a way that worked for me.

First, I created an empty array to add the states to. I then removed duplicates and sorted the results:

filteredStates = allStates.removeDuplicates()
    filteredStates.sort()

Then in my list I used:

List {
    ForEach(0..<filteredStates.count, id: \.self) { states in
        Section {
            ForEach (bucketList) { item in
                if (item.state == filteredStates[states]) {

This resulted in the filtered list that I was looking for. I appreciate everyone for offering suggestions and I'm sure those will work for others. This was specific to my dataset I'm sure.