skip to Main Content

I have tried to create a drop-down list, but the list doesn’t look good and when opening, it keeps changing the size of the HStack


ZStack {
                VStack() {
                    HStack(spacing: 0) {
                        HStack {
                            Text(filters[selectedFilter].name)
                                .foregroundColor(Color.black)
                                .font(.system(size: 25))
                            Image(systemName: "chevron.right")
                                .aspectRatio(contentMode: .fit)
                                .font(.system(size: 25, weight: .none))
                        }
                        .padding(10)
                        .background(.ultraThinMaterial, in : RoundedRectangle(cornerRadius: 45.0))
                        .onTapGesture {
                            withAnimation(Animation.spring().speed(2)) {
                                showOptions.toggle()
                            }
                        }
                        
                        if showOptions {
                            VStack() {
                                ForEach(filters.indices, id: .self) { i in
                                    Text(filters[i].name)
                                        .onTapGesture {
                                            showOptions.toggle()
                                            selectedFilter = i
                                        }
                                }
                            }
                            .background(.ultraThinMaterial, in : RoundedRectangle(cornerRadius: 45.0))
                        }

I posted some pics of the result when collapsed

enter image description here

below when expanded

enter image description here

I do not want the dropdown on the right side but below. Also as you can see, the entire icons are moving down. I need the icons to stay on top and just have a simple dropdown when clicking on the text on the left

Any idea?
Thanks

2

Answers


  1. The other buttons move downwards because the HStack‘s alignment is .center.

    If you set the alignment to .top, the other buttons will not move downwards:

    @State var showingDropdown = false
    
    var body: some View {
        VStack {
            HStack(alignment: .top) {
                (Text("Surf ") + Text(Image(systemName: "chevron.right")))
                    .padding()
                    .background(.thinMaterial)
                    .cornerRadius(30)
                    .onTapGesture {
                        withAnimation {
                            showingDropdown.toggle()
                        }
                    }
                if showingDropdown {
                    VStack(spacing: 5) {
                        ForEach(options, id: .self) { option in
                            Text(option)
                        }
                    }
                    .padding()
                    .background(.thinMaterial)
                    .cornerRadius(30)
                }
            }
            .padding()
            Spacer()
        }
    }
    

    If you want the dropdown to appear directly below "Surf >", wrap them both in a VStack(alignment: .leading). Again, the alignment matters here.

    That said, the easier way to make a dropdown is with a Menu:

    let options = ["option1", "option2", "foo", "bar", "baz", "something else"]
    
    Menu {
        ForEach(options, id: .self) { option in
            Button(option) {
                // ...
            }
        }
    } label: {
        (Text("Surf ") + Text(Image(systemName: "chevron.right")))
    }
    .padding()
    .background(.thinMaterial)
    .cornerRadius(30)
    

    Since this looks like a picker, also consider using a Picker with a style of .menu:

    @State var pickedOption = "option1"
    
    Picker("Some Picker", selection: $pickedOption) {
        ForEach(options, id: .self) { option in
            Text(option)
        }
    }
    .padding()
    .background(.thinMaterial)
    .cornerRadius(30)
    .pickerStyle(.menu)
    
    Login or Signup to reply.
  2. First create a custom preference key.

    struct BoundsPreferenceKey: PreferenceKey {
        static var defaultValue: [Anchor<CGRect>] = [] 
        
        static func reduce(value: inout [Anchor<CGRect>], nextValue: () -> [Anchor<CGRect>]) {
            value.append(contentsOf:nextValue())
        }
    }
    

    Then add anchor preference key to the view you want. In current case the Surf Button

    (Text("Surf ") + Text(Image(systemName: "chevron.right")))
                    .padding()
                    .background(.thinMaterial)
                    .cornerRadius(30)
                    .anchorPreference(key: BoundsPreferenceKey.self, //anchor preference  
                                          value: .bounds) { [$0] }
                    .onTapGesture {
                        withAnimation {
                            showingDropdown.toggle()
                        }
                    }
    

    and finally add an overlayPreferenceValue to your VStack

     VStack {
            HStack(alignment: .top) {
                (Text("Surf ") + Text(Image(systemName: "chevron.right")))
                    .padding()
                    .background(.thinMaterial)
                    .cornerRadius(30)
                    .onTapGesture {
                        withAnimation {
                            showingDropdown.toggle()
                        }
                    }
               
            }
            .padding()
            Spacer()
        }
        .overlayPreferenceValue(BoundsPreferenceKey.self) { // Add this 
                pref in
                GeometryReader { geometry in
                    if showingDropdown { // flag to make it conditional otherwise this will be visible 
                        pref.map{
                            
                            YourMenuView // Replace this with your view for the menu
                                .offset(x: geometry[$0].minX,
                                        y: geometry[$0].minY + (geometry[$0].height + 8)) // replace 8 with any number you want this will act as padding
                            
                        }[0]
                    } else {
                        EmptyView()
                    }
                    
                }
        }
        
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search