UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

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      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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.