swiftuipopoverswiftui-picker

Why does data not populate list inside popover in SwiftUI?


What I'm Looking For

I'm hoping someone can help me understand what's going on and why SwiftUI is behaving the way it is in this scenario.

What I was trying to do

I was originally trying to create a menu with a picker that has a list of calendars for the user to choose from. What was important to me was showing a colored circle to indicate the color of the calendar along with the title. At the time, I couldn't figure out how to get the icons colored in the picker menu. So, I thought a custom popover with a list would work. This is what led to me discovering this issue.

Setup

Problem

When the picker is commented out, the popover always shows up empty, but it retains a frame. When the picker is uncommented, the data not only shows up in the picker, but it shows up in the popover as well. This is what where I'm really stumped. Why does the list in the popover only populate when the Picker I there as well?

Things I've Tried

Code

Can easily copy and try for yourself

import SwiftUI

struct TestView: View {
    let dataManager = DataManager()
    @State private var items: [DataItem] = []
    @State private var popoverIsPresented = false
    
    var body: some View {
        VStack {
            Button {
                popoverIsPresented = true
            } label: {
                Text("Choose Item")
            }
            .popover(isPresented: $popoverIsPresented, content: {
                List {
                    ForEach(items) { item in
                        Text(item.title)
                    }
                }
            })
            
//          List {
//              ForEach(items) { item in
//                  Text(item.title)
//              }
//          }
        }
        .task { items = await dataManager.fetchData() }
    }
}

#Preview {
    TestView()
        .frame(minWidth: 300, minHeight: 300)
}

struct DataItem: Identifiable {
    let id = UUID()
    let title: String
}

class DataManager {
    
    func fetchData() async -> [DataItem] {
        Array(repeating: .init(title: "This is an example item"), count: 10)
    }
}
  1. Run it as is
  2. Then try uncommenting the Picker and you should then see the data appear in both views

Best Guess 🤷‍♂️

Images

With Picker commented out

With Picker left in


Solution

  • If I understand correctly, when you refer to a picker, you are actually referring to the List in your example, right?

    I was able to reproduce the issue by uncommenting the List. However, there is then an error in the console:

    ForEach<Array, UUID, Text>: the ID 1582CFA9-24C2-49BE-93DF-1D8B5FB949EA occurs multiple times within the collection, this will give undefined results!

    Let's fix this, to make sure it is not the cause of the problem. This means building the dummy array in a different way. For example:

    func fetchData() async -> [DataItem] {
    //    Array(repeating: .init(title: "This is an example item"), count: 10)
        var result = [DataItem]()
        for n in 0..<10 {
            result.append(DataItem(title: "This is an example item \(n)"))
        }
        return result
    }
    

    When the List is commented out, the Popover stays empty, as you described. My guess is that the body of the view is not being refreshed when the array is assigned to the state variable, because none of the visible content is dependent on the state variable. This may indeed be a bug, as you were suggesting.

    As a workaround, try making the body dependent on the state variable in some way. For example, you could show the count of items as a hidden Text element behind the Button:

    Button {
        popoverIsPresented = true
    } label: {
        Text("Choose Item")
    }
    .background { Text("\(items.count)").hidden() } // 👈 ADDED
    .popover(isPresented: $popoverIsPresented) {
        List {
            ForEach(items) { item in
                Text(item.title)
            }
        }
    }
    

    Regarding your original objectives:

    create a menu with a picker that has a list of calendars

    showing a colored circle to indicate the color of the calendar along with the title

    The answer to SwiftUI Picker issue with Rectangle() instead of Text() shows how this can be done using a regular Picker. See also Swift Picker Containing A View Not Showing The Color component.