1

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
}
5
  • Do you have VD of current vs expected output?
    – pkc456
    Commented Jan 8 at 15:59
  • And an if clause around Section that checks to see if there's content
    – jnpdx
    Commented Jan 8 at 16:13
  • it's best to process your data first before you declare the UI for it, usually that's done in a computed var that is passed into a child View's init
    – malhal
    Commented Jan 8 at 17:00
  • As mentioned in the comments, you could try something simple like: if (bucketList.contains(where: {$0.state == states})) { Section { ... Adjust the equality as needed. Commented Jan 8 at 23:25
  • @workingdogsupportUkraine - I'm sure I'm missing something simple here, but I still get the following error: Binary operator '==' cannot be applied to operands of type 'String' and '[String]' Commented Jan 9 at 14:57

4 Answers 4

1

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
  • I tried this several ways but always get an error. I dumbed it down as much as possible so my enum is now structured as: enum USStates: String { ... case FL ... } . However, the line: USStates.allCases.filter { ... I get the error USStates has no member filter Commented Jan 8 at 19:51
0

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
    // ...
 }
0

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
  • in the line, var list = PremiumKeyFeatures.list what does the ".list" do? I can't find any documentation on that. If I add that to my bucketList var list = bucketList.list, I receive an error of Value of type '[BucketListItems]' has no dynamic member 'list' using key path from root type '[BucketListItems]' Commented Jan 8 at 19:56
0

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.

1
  • Note, using range/index like you do, is not stable and is not recommended (not best practice). Commented Jan 10 at 1:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.