swiftuiswiftui-listswiftui-scrollview

Trying to make VStack scrollable, hides the items


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

Solution

  • 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.