I have been brainstorming ideas for implementing a button dropdown view similar to what you see in a default Picker. I have been experimenting with this in general to find a way to overlay a view on top of a parent view like the Picker does. I implemented a DropDownMenu View but it doesn't hit the notes that I want out of it
struct DropdownMenu<T, Views>: View {
var title: String
var selection: Binding<T>
var options: [T]
init(title: String, selection: Binding<T>, options: [T], @ViewBuilder views: @escaping () -> TupleView<Views>) {
self.title = title
self.selection = selection
self.options = options
self.content = views().getViews
}
@State private var showMenu: Bool = false
var content: [AnyView] = []
var body: some View {
Button {
showMenu = true
} label: {
Text(title)
.frame(minWidth: 100)
}
.buttonStyle(BorderedButtonStyle())
.overlay {
ScrollView {
VStack {
ForEach(content.indices, id: \.self) { index in
Button {
selection.wrappedValue = options[index]
showMenu = false
} label: {
content[index]
.frame(width: 350)
.frame(minHeight: 65)
}
}
}
}
.frame(width: 350, height: 225)
.background(.ultraThinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 10))
.zIndex(99999)
.opacity(showMenu ? 1 : 0)
}
}
}
The problem with this view is that its content is not able to overlay over the content inside the parent, even if I set the zIndex very high. Also, the view itself requires me to pass a list of my available options and requires me to wrap provided views in buttons using this janky way of converting passed views to AnyViews. Finally, this overlay will not know when it's near the edge of the screen so it can't move itself to make sure it fits.
When I consider an ideal solution, it seems like I would need to implement a Layout instead. This solves my view being able to present anywhere within the screen's bounds and it allows me to use a layoutValue to store a tag value like .tag does for the Picker. The problem is that with a Layout there seems to be no way to respond to on-tap events on a subview to tell the DropDownView to stop showing the overlay Layout and update a binding variable. Unless there is some way to access a layoutValue from a View there really is no way to create simplified view definitions like SwiftUI gives us with Picker and whatnot right?
Is there any way in the current version of SwiftUI to implement a view that can overlay over any view, take a modifier-like tag to set a value associated with its option views, and have the ability to update a binding selection variable as the Picker does? Basically, is there a way to implement a completely custom Picker in the current version of SwiftUI
popover
?zIndex
needs to be set on all of the content that is supposed to be below the menu. Alternatively, you could separate the button from the menu and have the parent control the visibility of the menu. Using aZStack
would be a way of doing this.tag
, see stackoverflow.com/q/77757819/5133585