WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

How to keep drop-down menu "in place" when pressed and cover up the view below

Forums > SwiftUI

So I've got a custom drop down menu that I've created since the stock Picker isn't very nice to look at and isn't the most customizable piece of UI. It functions how I need to but one "bug" I've noticed is that when I press the drop-down, it shoots every view above it higher onto the screen/view. And thus it has become less of a dropdown and more of a push-up menu, there "drop-down" should also cover the view/field below it.

struct DropdownMenu: View {
//    Used to show or hide drop-down menu options
    @State private var isOptionsPresented: Bool = false

//    Used to bind user selection
    @Binding var selectedOption: DropdownMenuOption?

    let placeholder: String
    let options: [DropdownMenuOption]
    var body: some View {
        Button(action: {
            self.isOptionsPresented.toggle()
        }) {
            HStack {
                Text(selectedOption == nil ? placeholder : selectedOption!.option)
                    .fontWeight(.medium)
                    .foregroundColor(selectedOption == nil ? .gray : .black)
                Spacer()
                Image(systemName: self.isOptionsPresented ? "chevron.up" : "chevron.down")
                    .fontWeight(.medium)
                    .foregroundColor(.black)
            }
        }
        .padding()
        .overlay {
            RoundedRectangle(cornerRadius: 4)
                .stroke(.gray, lineWidth: 2)
        }
        .overlay(alignment: .top) {
            VStack {
                if self.isOptionsPresented {
                    Spacer(minLength: 64)
                    DropdownMenuList(options: self.options, onSelectedAction: { option in
                        self.isOptionsPresented = false
                        self.selectedOption = option
                    })
                }
            }
        }
        .padding(.horizontal)
        .padding( .bottom, self.isOptionsPresented ? CGFloat(self.options.count * 32 ) > 160
                  ? 192
                  : CGFloat(self.options.count * 32) + 32
                  : 0
        )
    }
}

and then the dropdown menu list

struct DropdownMenuList: View {
//    the dropdown menu list options
    let options: [DropdownMenuOption]
//    an action called when user select an option
    let onSelectedAction: (_ option: DropdownMenuOption) -> Void

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 02) {
                ForEach(options) { option in
                    DropdownMenuListRow(option: option, onSelectedAction: self.onSelectedAction)
                }
            }
        }
        .frame(height: CGFloat(self.options.count * 32 ) > 160 ? 160 : CGFloat(self.options.count * 32))
        .padding(.vertical, 04)
        .overlay {
            RoundedRectangle(cornerRadius: 04)
                .stroke(.gray, lineWidth: 02)
        }
    }
}

2      

Hi,

I had to improvise some things but it should work like intended,

struct ContentView: View {
    @State private var selectedOption: DropdownMenuOption? = DropdownMenuOption.option1

    var body: some View {
        VStack {
            Text("Hello World!")

            DropdownMenu(selectedOption: $selectedOption, placeholder: "Placeholder", options: [DropdownMenuOption.option1, DropdownMenuOption.option2])
                .zIndex(1)

            Text("Hello Again")
        }
    }
}
struct DropdownMenu: View {
//    Used to show or hide drop-down menu options
    @State private var isOptionsPresented: Bool = false

//    Used to bind user selection
    @Binding var selectedOption: DropdownMenuOption?

    let placeholder: String
    let options: [DropdownMenuOption]

    var body: some View {
        Button(action: {
            self.isOptionsPresented.toggle()
        }) {

              HStack {
                  Text(selectedOption == nil ? placeholder : selectedOption!.rawValue)
                      .fontWeight(.medium)
                      .foregroundColor(selectedOption == nil ? .gray : .black)
                  Spacer()
                  Image(systemName: self.isOptionsPresented ? "chevron.up" : "chevron.down")
                      .fontWeight(.medium)
                      .foregroundColor(.black)
              }
              .padding(.horizontal)
              .frame(height: 60)
              .overlay {
                  RoundedRectangle(cornerRadius: 4)
                      .stroke(.gray, lineWidth: 2)
              }
              .padding(.horizontal)

        }
        .overlay(alignment: .top) {
            VStack {
                if self.isOptionsPresented {
                    Spacer(minLength: 64)
                    DropdownMenuList(options: self.options, onSelectedAction: { option in
                        self.isOptionsPresented = false
                        self.selectedOption = option
                    })
                }
            }
            .padding(.horizontal)
            .padding( .bottom, self.isOptionsPresented ? CGFloat(self.options.count * 32 ) > 160
                      ? 192
                      : CGFloat(self.options.count * 32) + 32
                      : 0
            )
        }
    }
}
struct DropdownMenuList: View {
//    the dropdown menu list options
    let options: [DropdownMenuOption]
//    an action called when user select an option
    let onSelectedAction: (_ option: DropdownMenuOption) -> Void

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 02) {
                ForEach(options, id: \.self) { option in
                    Text(option.rawValue)
                        .foregroundColor(.primary)
                }
                .padding(.horizontal)
            }
        }
        .frame(height: 60)
        .background(.white)
        .overlay {
            RoundedRectangle(cornerRadius: 04)
                .stroke(.gray, lineWidth: 02)
        }
    }
}

2      

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.