Code Monkey home page Code Monkey logo

swiftymenu's Introduction

SwiftyMenu

Cocoapod Swift Package Manager Version MIT License
Facebook: @KarimEbrahemAbdelaziz LinkedIn: @karimebrahem

SwiftyMenu is simple yet powerfull drop down menu component for iOS. It allow you to have drop down menu that doesn't appear over your views, which give you awesome user experience.

Screenshots

Requirements

  • Xcode 10.2+
  • Swift 5+
  • iOS 10+

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate SwiftyMenu into your Xcode project using CocoaPods, specify it in your Podfile:

pod 'SwiftyMenu', '~> 1.1.0'

Swift Package Manager

  1. Automatically in Xcode:
  • Click File > Swift Packages > Add Package Dependency...
  • Use the package URL https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu to add TimelaneCombine to your project.
  1. Manually in your Package.swift file add:
.package(url: "https://github.com/KarimEbrahemAbdelaziz/SwiftyMenu", from: "1.1.0")

Usage

SwiftyMenu supports initialization from Storyboard and Code.

Initialization

Storyboard

Setup your view controller:

// Connect view in storyboard with you outlet
@IBOutlet private weak var dropDownMenu: SwiftyMenu!

Then connect IBOutlet to Storyboard, and connect the Height Constraints of the menu as shown below.

Code

  1. Init SwiftyMenu
/// Init SwiftyMenu from Code
let dropDownCode = SwiftyMenu(frame: CGRect(x: 0, y: 0, width: 0, height: 40))
  1. Add SwiftyMenu as Subview
/// Add it as subview
view.addSubview(dropDownCode)
  1. Setup Constraints
/// Add constraints to SwiftyMenu
/// You must take care of `hegiht` constraint, please.
dropDownCode.translatesAutoresizingMaskIntoConstraints = false

let horizontalConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0)

let topConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.top, relatedBy: NSLayoutConstraint.Relation.equal, toItem: otherView, attribute: NSLayoutConstraint.Attribute.top, multiplier: 1, constant: 64)

let widthConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 255)

dropDownCode.heightConstraint = NSLayoutConstraint(item: dropDownCode, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 40)

NSLayoutConstraint.activate(
    [
        horizontalConstraint,
        topConstraint,
        widthConstraint,
        dropDownCode.heightConstraint
    ]
)

Configure DataSource

To configure SwiftyMenu DataSource, you'll need to prepare your Model to be able to presented and retrived from the menu. Then, create and assign the DataSource to SwiftyMenu.

Setup Models

We are supporting Generic Data Source, all you have to do is conforming to our Generic Protocol on which type you want to add to the menu.

  • Example of String
extension String: SwiftyMenuDisplayable {
    public var displayableValue: String {
        return self
    }
    
    public var retrivableValue: Any {
        return self
    }
}
  • Example of custom Struct
struct MealSize {
    let id: Int
    let name: String
}

extension MealSize: SwiftyMenuDisplayable {
    public var displayableValue: String {
        return self.name
    }

    public var retrievableValue: Any {
        return self.id
    }
}

Assign DataSource

  1. Create an Array of your models
/// Define menu data source
/// The data source type must conform to `SwiftyMenuDisplayable`
private let dropDownOptionsDataSource = [
    MealSize(id: 1, name: "Small"),
    MealSize(id: 2, name: "Medium"),
    MealSize(id: 3, name: "Large"),
    MealSize(id: 4, name: "Combo Large")
]
  1. Assign it to SwiftyMenu DataSource property
dropDownCode.items = dropDownOptionsDataSource

Capture Selection

SwiftyMenu supports 2 ways to capture the selected items, Delegate and Closures. You can use one or both of them at the same time.

Using Delegate

  1. Conform to SwiftyMenu Delegate protocol
extension ViewController: SwiftyMenuDelegate {
    // Get selected option from SwiftyMenu
    func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
        print("Selected item: \(item), at index: \(index)")
    }
    
    // NEW - Get selected option from SwiftyMenu
    func swiftyMenu(_ swiftyMenu: SwiftyMenu, didDeselectItem item: SwiftyMenuDisplayable, atIndex index: Int){
        print("deselected item: \(item), at index: \(index)")
    }
    
    // SwiftyMenu drop down menu will expand
    func swiftyMenu(willExpand swiftyMenu: SwiftyMenu) {
        print("SwiftyMenu willExpand.")
    }

    // SwiftyMenu drop down menu did expand
    func swiftyMenu(didExpand swiftyMenu: SwiftyMenu) {
        print("SwiftyMenu didExpand.")
    }

    // SwiftyMenu drop down menu will collapse
    func swiftyMenu(willCollapse swiftyMenu: SwiftyMenu) {
        print("SwiftyMenu willCollapse.")
    }

    // SwiftyMenu drop down menu did collapse
    func swiftyMenu(didCollapse swiftyMenu: SwiftyMenu) {
        print("SwiftyMenu didCollapse.")
    }
}
  1. Assign SwiftyMenu delegate
dropDownCode.delegate = self

Using Closures

You can use callbacks to know what happen:

/// SwiftyMenu also supports `CallBacks`
dropDownCode.didSelectItem = { menu, item, index in
    print("Selected \(item) at index: \(index)")
}

dropDownCode.willExpand = {
    print("SwiftyMenu Will Expand!")
}

dropDownCode.didExpand = {
    print("SwiftyMenu Expanded!")
}

dropDownCode.willCollapse = {
    print("SwiftyMenu Will Collapse!")
}

dropDownCode.didCollapse = {
    print("SwiftyMenu Collapsed!")
}

UI Customization

Having an amazing drop down menu is essential. So, there're a lot of UI customization for SwiftyMenu (More to be added soon).

To configure UI customization for SwiftyMenu:

  1. Create SwiftyMenuAttributes property
private var codeMenuAttributes = SwiftyMenuAttributes()
  1. Assign it to SwiftyMenu
/// Configure SwiftyMenu with the attributes
dropDownCode.configure(with: codeMenuAttributes)

Also before assigning it to SwiftyMenu you could customize the menu look using following attributes.

Placeholder

attributes.placeHolderStyle = .value(text: "Please Select Size", textColor: .lightGray)

Text Style

attributes.textStyle = .value(color: .gray, separator: " & ", font: .systemFont(ofSize: 12))

Scroll

attributes.scroll = .disabled

Selection Behavior

//NEW - Single selection disallowing deselection clicking the same item
attributes.multiSelect = .disabled(allowSingleDeselection: false)
//NEW - Single selection allowing deselection clicking the same item
attributes.multiSelect = .disabled(allowSingleDeselection: true)
//Multi selection enable
attributes.multiSelect = .enabled
attributes.hideOptionsWhenSelect = .enabled

Row Style

attributes.rowStyle = .value(height: 40, backgroundColor: .white, selectedColor: .white)

Frame Style

/// Rounded Corners 
attributes.roundCorners = .all(radius: 8)

/// Menu Maximum Height
attributes.height = .value(height: 300)

/// Menu Border
attributes.border = .value(color: .gray, width: 0.5)

Arrow Style

/// `SwiftyMenu` have default arrow
attributes.arrowStyle = .value(isEnabled: true)
/// NEW - Now able to add a custom image, tint color and space between icon and text
attributes.arrowStyle = .value(isEnabled: true, image: image, tintColor: .purple, spacingBetweenText: 10.0)

Separator Style

attributes.separatorStyle = .value(color: .black, isBlured: false, style: .singleLine)

Header Style

attributes.headerStyle = .value(backgroundColor: .white, height: 40)

Accessory

attributes.accessory = .disabled

Animation

attributes.expandingAnimation = .linear
attributes.expandingTiming = .value(duration: 0.5, delay: 0)

attributes.collapsingAnimation = .linear
attributes.collapsingTiming = .value(duration: 0.5, delay: 0)

Margin Horizontal

/// Margin leading and trailing for title / placeholder
attributes.titleMarginHorizontal = .value(leading: 10, trailing: 20)
/// Margin leading and trailing for item list
attributes.itemMarginHorizontal = .value(leading: 10, trailing: 10)

Error Info

/// Default use the .red color
attributes.errorInfo = .default
/// Custom color for placeholder and icon if it's enabled
attributes.errorInfo = .custom(placeholderTextColor: .red, iconTintColor: .red)

Set Error

/// Enabling this will show the config in errorInfo
dropDown.setError(hasError: true)

New Functionalities

  • Custom arrow and color and space between arrow and title
  • Fix multi select title: showing now sorted by index
  • Margin leading and trailing for title and items
  • Handling deselection for multi and single selection
  • Seting Error with custom color for title and arrow

Example Project

You could check the full Example project Here.

You could check the full new Example project Here New Example.

TODO

  • Automate release new version to Cocoapods from Github Actions.
  • Add CHANGELOG file for the project.
  • Allow custom header and options cells.
  • Allow different interactions to dismiss SwiftyMenu.
  • Allow to customize the default seperator.
  • Support Generic DataSource.
  • Support multi selection in SwiftMenu ๐Ÿ”ฅ.
  • Support multi SwiftyMenu in one screen.
  • Support stack view and add example.
  • Support call backs and delegation.
  • Support different types of Animations.
  • Add different customization to colors for default cells.
  • Margin horizontal for title and item
  • Custom arrow and tint color

And much more ideas to make it solid drop down menu for iOS projects ๐Ÿ˜Ž๐Ÿ’ช๐Ÿป

Android

Author

Karim Ebrahem, [email protected]

License

SwiftyMenu is available under the MIT license. See the LICENSE file for more info.

Credits

You can find me on Twitter @k_ebrahem_.

It will be updated when necessary and fixes will be done as soon as discovered to keep it up to date.

Enjoy!

swiftymenu's People

Contributors

amrangry avatar aramy23 avatar blerdfoniqi avatar ipodishima avatar karimebrahemabdelaziz avatar mostafanafie avatar nowjordanhappy avatar rursache avatar zyadgalal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swiftymenu's Issues

Single Selection Menu Changing Choice

Using the drop down menu and having the menu toggle so that when you make one selection, the menu closes works. The issue is if I then want to change my selection, I will tap the menu, and I see the arrow flip, but the menu does not drop down. Tap it once more and it will drop down again to allow you to make that selection again.

It is a simple thing that does not inhibit the use of the menu at all, still something that does slow the user down.
Otherwise, love this!
Note:

  • Only happens in single selection menu
  • Only happens when a selection of an item is made
  • Double tap to bring the options back up again
  • You do see the arrow flip for each tap

  • No big, just a little style issue
recording_swiftymenu_issue.mov

It seems like There is a problem with the SPM Installation

Hi :) First of all, thank you for creating a GREAT library.

Anyway, I tried to install SwiftyMenu(version 1.0.1) on the project using SPM.

But the files before the modification were installed in the app, so it seems that the same error still occurs.
(Image #1 ; In this SwiftyMenu File)
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-26 11 55 58
(Image #2-1 ; Actually included file(Package.swift) in the project)
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-26 11 58 15
(Image #2-2 ; Actually included file() in the project)
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-26 11 58 26
(Image #3 ; Error occurs like #37 )
แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2023-03-26 11 58 35

Could you please check if there is any problem?

Thanks :)

Default Value

Hi there,
Thank you for your efforts!

How can we set Default Value?

We have an array ([Model(name:"CC"), Model(name:"FC"), Model(name:"FF")]) so we want to display "CC" on landing controller. We are able to set the index ("selectedIndex") but how can we set the text of that index?

Here is my code:

private func setupAssetOwnerDDMenu() {

    dropdown.delegate = self
    dropdown.configure(with: dropdownMenuAttributes)

    dropdown.items = dropdownOptionsDataSource
    
    dropdown.selectedIndex = 0
    **dropdown.selectButton  (select button is a private property so I can not set title)**
}

somting like z-index

i was adding the menu
and the height is 200
it expand over a save button
if i try to select the item that covers the save button
the save button is clicked and not the MenuItem

Thanks

SwiftyMenu/SwiftyMenu.swift:410: Fatal error:Unexpectedly found nil while implicitly unwrapping an Optional value

In my project,I try init this menu by code and by storyboard , all of them clash when self.willExpand() in the file from pods.
my code is
`func setupMenu(){

    dropList.delegate = self
    dropList.items = sortTypeItems

    dropList.configure(with: menuAttributes)
    dropList.willExpand = {
        print("****SwiftyMenu Code Will Expand!****")
    }
}` 

and terminal print "SwiftyMenu Code Will Expand!" successful,then next second,project get clash

Code initialization SwiftyMenu problem

There seems to be a problem when I initialize SwiftyMenu with code.

    lazy var menuV: SwiftyMenu = {
        let view = SwiftyMenu(frame: .zero)
        let attributes = SwiftyMenuAttributes()
        view.configure(with: attributes)
        view.items = ["Times Cost","Recents","Save Money"]
        return view
    }()
        self.view.addSubview(menuV)
        menuV.snp.makeConstraints { make in
            make.right.equalTo(self.view.snp.right).offset(XBScale(-15))
            make.top.bottom.equalTo(titleLab)
            make.width.equalTo(XBScale(150))
        }

When I click on the menu an error occurs.

SwiftyMenu/SwiftyMenu.swift:410: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

/// Called to Expand `SwiftyMenu`.
    private func expandSwiftyMenu() {
        delegate?.swiftyMenu(willExpand: self)
        self.willExpand()
/// This is line 410
        heightConstraint.constant = attributes.height.listHeightValue == 0 || !attributes.scroll.isEnabled || (CGFloat(Double(attributes.rowStyle.rowStyleValues.height) * Double(items.count + 1)) < CGFloat(attributes.height.listHeightValue)) ? CGFloat(Double(attributes.rowStyle.rowStyleValues.height) * Double(items.count + 1)) : CGFloat(attributes.height.listHeightValue)

        switch attributes.expandingAnimation {
        case .linear:
            UIView.animate(withDuration: attributes.expandingTiming.animationTimingValues.duration,
                           delay: attributes.expandingTiming.animationTimingValues.delay,
                           animations: animationBlock,
                           completion: expandingAnimationCompletionBlock)
            
        case .spring(level: let powerLevel):
            let damping = CGFloat(0.5 / powerLevel.rawValue)
            let initialVelocity = CGFloat(0.5 * powerLevel.rawValue)
            
            UIView.animate(withDuration: attributes.expandingTiming.animationTimingValues.duration,
                           delay: attributes.expandingTiming.animationTimingValues.delay,
                           usingSpringWithDamping: damping,
                           initialSpringVelocity: initialVelocity,
                           options: [],
                           animations: animationBlock,
                           completion: expandingAnimationCompletionBlock)
        }
    }

Menu hiding behind another field

Hey,
My drop menu hides behind another view. How can i resolve this?
Attaching screen shot of better understanding.
THANKS in advance.
Screenshot 2023-10-17 at 8 31 37 PM

Error when initializing from code

Hello,

I have implemented your library using CocoaPods using Xcode 11.4. I am testing on the simulator with iOS 13.4.

I am trying to use your library from code without storyboard at all but it doesn't seem to be working. I am not sure if it is because I am trying to add the menu to a table view cell, but no matter what I do I get a white empty box on the UI side of things and when I am debugging I always see this:

expression produced error: error: /var/folders/1w/ltx7q76130s12wkrq5_tjdxm0000gp/T/expr56-31d3d2..swift:1:88: error: 'SwiftyMenuDisplayable' is not a member type of 'SwiftyMenu'
Swift._DebuggerSupport.stringForPrintObject(Swift.UnsafePointer<Swift.Array<SwiftyMenu.SwiftyMenuDisplayable>>(bitPattern: 0x1090356e0)!.pointee)
                                                                            ~~~~~~~~~~ ^

SwiftyMenu.SwiftyMenu:1:20: note: 'SwiftyMenu' declared here
final public class SwiftyMenu : UIView {

Clicking any menu crashes the application:

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Pods/SwiftyMenu/SwiftyMenu/Classes/SwiftyMenu.swift, line 470
2020-03-30 11:01:34.684213-0400 ARC Bookshelf[22331:3996509] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Pods/SwiftyMenu/SwiftyMenu/Classes/SwiftyMenu.swift, line 470

Some of my code on how I am implementing the menu:

class DropdownMenuCellView: MenuCellView, SwiftyMenuDelegate {
    
    var dropdownMenu: SwiftyMenu?
    var icon: UIImageView?

    private let alphaNormal: CGFloat = 0.0
    private let alphaSelected: CGFloat = 1.0
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        dropdownMenu = SwiftyMenu(frame: CGRect(x: 10.0, y: 0.0, width: self.frame.width - 10.0, height: 30.0))
        dropdownMenu?.delegate = self
        
        dropdownMenu?.isMultiSelect = false
        //dropdownMenu?.scrollingEnabled = false
        
        dropdownMenu?.rowBackgroundColor = .menuBackground
        
        dropdownMenu?.cornerRadius = 0.0
        
        self.addSubview(dropdownMenu!)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    func swiftyMenu(_ swiftyMenu: SwiftyMenu, didSelectItem item: SwiftyMenuDisplayable, atIndex index: Int) {
        // TODO
    }
    
}

At quick glance I believe this is related to it being implemented through CocoaPods, since it seems to act as if the module is missing. I am using !use_frameworks in my Podfile. I am not sure if your library does not work in that scenario.

.didSelectItem not being called

Hello Karim,
First, I would like to thank you for SwiftyMenu, great work !!
I have an issue that I can not understand!!
I followed your steps to create a simple dropdown list. All SwiftyMenuDelegate methods been called except .didSelectItem
Here's my code

@IBOutlet weak var dopdownMenu: SwiftyMenu! //typo in the name :)

 override func viewDidLoad() {
        super.viewDidLoad()
        
        
        
        let arr = ["one", "two", "three"]
        dopdownMenu.items = arr
        /// SwiftyMenu also supports `CallBacks`
        dopdownMenu.didSelectItem = { _, item, index in
            print("Selected \(item) at index: \(index)")
            print(item.displayableValue)
        }

        dopdownMenu.willExpand = {
            print("SwiftyMenu Will Expand!")
        }

        dopdownMenu.didExpand = {
            print("SwiftyMenu Expanded!")
        }

        dopdownMenu.willCollapse = {
            print("SwiftyMenu Will Collapse!")
            
        }

        dopdownMenu.didCollapse = {
            print("SwiftyMenu Collapsed!")
        }
    }

//extension
//
//  String+Extensions.swift
//  
//
//  Created by Sam on 2022-03-10.
//

import Foundation
import SwiftyMenu

extension String: SwiftyMenuDisplayable {
    public var displayableValue: String {
        return self
    }

    public var retrievableValue: Any {
        return self
    }
}

As you can see, I did create a string extension and set the height constraint on the storyboard. What I'm missing here?
I downloaded your sample project and it works just fine. I tried to debug and compare the difference between my simple app and yours but no luck!

Thanks in advance!
Regards,
Sam

Wasn't able to setup the view after it first initialized. So I made a fix myself.

For my application, I have the menu show a border for certain cases or hide it. But the problem I ran into was that it would never re-setup the view. Just by making the setupView() function public, I was able to call it from my application and had no problems. Not sure if it would be better to change this or make a new function to do the same thing but wanted to point it out.

attributes.roundCorners.cornerValue ?? 0

layer.cornerRadius = attributes.roundCorners.cornerValue ?? 0

Error on this line: SwiftyMenu/SwiftyMenu.swift:260: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

Option to Expand/Collapse menu from code

Hi, is there by any chance a way to manually trigger expanding/collapsing the menu? As I see from the code, the methods to achieve this are set to private... or is there a specific interface for this? Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.