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
- I have a class that is managing creating or retrieving data for me asynchronously.
- I'm using that data to populate the list of the picker menu and/or popover list
- I have a
Stateproperty that holds onto the data in the view after being fetched - That data is fetched inside of a
.taskmodifier (also triedTaskinsideonAppear) - Lastly, I have a view that consists of a
VStackthat houses the button triggering the popover as well as a picker to act as the menu. Both are populated with the same data
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
- Putting the popover view into its own file
- Using
onAppearinstead of.task - Using a
VStackinstead of aListinside the popover - Making the results from the fetch sit inside the data manager class as an optional and then resetting any time the data fetch is completed
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)
}
}
- Run it as is
- Then try uncommenting the Picker and you should then see the data appear in both views
Best Guess 🤷♂️
- the popover doesn't get updated once the
Stateproperty changes (which would be a bug I think)