Trying to make VStack scrollable, hides the items

76 views Asked by At

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
        }
    }
}
1

There are 1 answers

1
windowcow On BEST ANSWER
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()
        }
    }

}

v

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.