I have created a custom dropdown menu, but I am struggling to make the list of items scrollable.. I have tried to wrap in a List and, also a Scrollview, in various ways, but all lead to the DropDownView appearing to be empty. I remove said List or Scrollview, and the items return, but the VStack is not scrollable. Any ideas on how to make this scrollable, without breaking it? Thanks
struct DropDown: View {
/// Customization Properties
var hint: String
var options: [String]
var anchor: Anchor = .bottom
var maxWidth: CGFloat = 300
var cornerRadius: CGFloat = 15
@Binding var selection: String?
/// View Properties
@State private var showOptions: Bool = false
/// Environment Scheme
@Environment(\.colorScheme) private var scheme
@SceneStorage("drop_down_zindex") private var index = 1000.0
@State private var zIndex: Double = 1000.0
var listOptions = [
"YouTube",
"Instagram",
"X (Twitter)",
"Snapchat",
"TikTok",
"Facebook",
"Weibo",
"Wechat",
"Discord"
]
var body: some View {
GeometryReader {
let size = $0.size
VStack(spacing: 0) {
HStack(spacing: 0) {
Text(selection ?? hint)
.foregroundStyle(selection == nil ? .gray : .primary)
.lineLimit(1)
Spacer(minLength: 0)
Image(systemName: "chevron.down")
.foregroundStyle(.gray)
/// Rotating Icon
.rotationEffect(.init(degrees: showOptions ? -180 : 0))
}
.padding(.horizontal, 15)
.frame(width: size.width, height: size.height)
.background(scheme == .dark ? .black : .white)
.contentShape(.rect)
.onTapGesture {
index += 1
zIndex = index
withAnimation(.snappy) {
showOptions.toggle()
}
}
.zIndex(10)
if showOptions {
DropDownView()
}
}
.clipped()
/// Clips All Interaction within it's bounds
.contentShape(.rect)
.background((scheme == .dark ? Color.black : Color.white).shadow(.drop(color: .primary.opacity(0.15), radius: 4)), in: .rect(cornerRadius: cornerRadius))
.frame(height: size.height, alignment: anchor == .top ? .bottom : .top)
}
.frame(width: maxWidth, height: 50)
.zIndex(zIndex)
}
@ViewBuilder
func DropDownView() -> some View {
ScrollView(.vertical) {
VStack(spacing: 10) {
ForEach(listOptions, id: \.self) { option in
HStack(spacing: 0) {
Text(option)
.lineLimit(1)
Spacer(minLength: 0)
Image(systemName: "checkmark")
.font(.caption)
.opacity(selection == option ? 1 : 0)
}
.foregroundStyle(selection == option ? Color.primary : Color.gray)
.animation(.none, value: selection)
.frame(height: 40)
.contentShape(.rect)
.onTapGesture {
withAnimation(.snappy) {
selection = option
/// Closing Drop Down View
showOptions = false
}
}
}
.padding(.horizontal, 15)
.padding(.vertical, 5)
/// Adding Transition
.transition(.move(edge: anchor == .top ? .bottom : .top))
Spacer() // Add Spacer to make ScrollView take all available space
}
}
}
struct ContentView: View {
var listOptions = [
"YouTube",
"Instagram",
"X (Twitter)",
"Snapchat",
"TikTok",
"Facebook",
"Weibo",
"Wechat",
"Discord"
]
@State private var isToggled: Bool = false
var body: some View {
VStack(spacing: 0) {
/// DropDownView
Color.gray
.frame(width: 300, height: 100)
.onTapGesture {
withAnimation {
isToggled.toggle()
}
}
if isToggled {
ScrollView {
VStack(spacing: 5) {
ForEach(listOptions, id: \.self) { option in
Text(option)
.frame(width: 300)
}
}
}
.frame(height: 100)
.background(.yellow)
}
Spacer()
}
}
}
Is this what you were looking for? It's essential to ensure that your ScrollView has a defined ProposedSize. By setting .frame(height: 100), I've given the ScrollView a specific ProposedSize.
The content is scrollable because the ScrollContent (in this case, a VStack) exceeds the ScrollView's height of 100 (its size might be around 200, for instance).
In summary, the ScrollView has a height of 100, and the ScrollContent within it is larger than 100, enabling scrolling.
I suspect the issue you're encountering is due to not assigning a ProposedSize (in this case, height, since it's a vertical ScrollView) to your ScrollView.