NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

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

   

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

   

Hacking with Swift is sponsored by Judo

SPONSORED Let’s face it, SwiftUI previews are limited, slow, and painful. Judo takes a different approach to building visually—think Interface Builder for SwiftUI. Build your interface in a completely visual canvas, then drag and drop into your Xcode project and wire up button clicks to custom code. Download the Mac App and start your free trial today!

Try now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

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.