├── .github └── contributing.md ├── .gitignore ├── .swift-version ├── Demo ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small-50@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x-1.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Spotlight-40.png │ │ ├── Icon-Spotlight-40@2x-1.png │ │ ├── Icon-Spotlight-40@2x.png │ │ ├── Icon-Spotlight-40@3x.png │ │ ├── Icon-iPadPro@2x.png │ │ ├── Icon.png │ │ └── Icon@2x.png │ ├── Contents.json │ ├── LaunchImage.launchimage │ │ ├── Contents.json │ │ ├── Splash_V3_1242x2208.png │ │ ├── Splash_V3_320x480.png │ │ ├── Splash_V3_640x1136-1.png │ │ ├── Splash_V3_640x1136.png │ │ ├── Splash_V3_640x960-1.png │ │ ├── Splash_V3_640x960.png │ │ └── Splash_V3_750x1334.png │ ├── logo_0.imageset │ │ ├── Contents.json │ │ └── logo_0.png │ ├── logo_1.imageset │ │ ├── Contents.json │ │ └── logo_1.png │ ├── logo_10.imageset │ │ ├── Contents.json │ │ └── logo_10.png │ ├── logo_2.imageset │ │ ├── Contents.json │ │ └── logo_2.png │ ├── logo_3.imageset │ │ ├── Contents.json │ │ └── logo_3.png │ ├── logo_4.imageset │ │ ├── Contents.json │ │ └── logo_4.png │ ├── logo_5.imageset │ │ ├── Contents.json │ │ └── logo_5.png │ ├── logo_6.imageset │ │ ├── Contents.json │ │ └── logo_6.png │ ├── logo_7.imageset │ │ ├── Contents.json │ │ └── logo_7.png │ ├── logo_8.imageset │ │ ├── Contents.json │ │ └── logo_8.png │ └── logo_9.imageset │ │ ├── Contents.json │ │ └── logo_9.png ├── MyCell.swift ├── MyCell.xib ├── NiceButton.swift └── ViewController.swift ├── DropDown.podspec ├── DropDown.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── DropDown.xcscmblueprint │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── DropDown.xcscheme ├── DropDown ├── DropDown.h ├── Info.plist ├── helpers │ ├── DPDConstants.swift │ ├── DPDKeyboardListener.swift │ └── DPDUIView+Extension.swift ├── resources │ └── DropDownCell.xib └── src │ ├── DropDown+Appearance.swift │ ├── DropDown.swift │ └── DropDownCell.swift ├── DropDownTests ├── DropDownTests.swift └── Info.plist ├── Info.plist ├── LICENSE ├── Package.swift ├── README.md └── Screenshots ├── 1.png ├── 2.png ├── 3.png ├── customCells ├── links.png └── xib.png └── logo.png /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ### What where you trying to do 2 | 3 | A few lines to explain the goal. 4 | 5 | ### What actually happened 6 | 7 | A few lines to explain the bug. 8 | 9 | ### What you think went wrong (optional) 10 | 11 | If you have an idea, don't hesitate to comment it here! 12 | 13 | ### How to reproduce the issue 14 | 15 | Put some code here if necessary. 16 | 17 | ### Details 18 | 19 | OS version, logs or screenshots. 20 | 21 | Thank you for your contribution! 👍 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Swift ### 2 | # Xcode 3 | # 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | # Pods/ 28 | 29 | # Carthage 30 | # 31 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 32 | # Carthage/Checkouts 33 | 34 | Carthage/Build 35 | 36 | # Mac OS X 37 | .DS_Store 38 | 39 | # SwiftPackageManager # 40 | Packages 41 | .build/ 42 | xcuserdata 43 | DerivedData/ 44 | #*.xcodeproj Keeping this here so we maintain Carthage support 45 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DropDown 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | DropDown.startListeningToKeyboard() 19 | 20 | return true 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Demo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 36 | 47 | 57 | 63 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 91 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 125 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "size" : "29x29", 20 | "idiom" : "iphone", 21 | "filename" : "Icon-Small@2x.png", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "size" : "29x29", 26 | "idiom" : "iphone", 27 | "filename" : "Icon-Small@3x.png", 28 | "scale" : "3x" 29 | }, 30 | { 31 | "size" : "40x40", 32 | "idiom" : "iphone", 33 | "filename" : "Icon-Spotlight-40@2x-1.png", 34 | "scale" : "2x" 35 | }, 36 | { 37 | "size" : "40x40", 38 | "idiom" : "iphone", 39 | "filename" : "Icon-Spotlight-40@3x.png", 40 | "scale" : "3x" 41 | }, 42 | { 43 | "size" : "57x57", 44 | "idiom" : "iphone", 45 | "filename" : "Icon.png", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "size" : "57x57", 50 | "idiom" : "iphone", 51 | "filename" : "Icon@2x.png", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "size" : "60x60", 56 | "idiom" : "iphone", 57 | "filename" : "Icon-60@2x.png", 58 | "scale" : "2x" 59 | }, 60 | { 61 | "size" : "60x60", 62 | "idiom" : "iphone", 63 | "filename" : "Icon-60@3x.png", 64 | "scale" : "3x" 65 | }, 66 | { 67 | "idiom" : "ipad", 68 | "size" : "20x20", 69 | "scale" : "1x" 70 | }, 71 | { 72 | "idiom" : "ipad", 73 | "size" : "20x20", 74 | "scale" : "2x" 75 | }, 76 | { 77 | "size" : "29x29", 78 | "idiom" : "ipad", 79 | "filename" : "Icon-Small.png", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "size" : "29x29", 84 | "idiom" : "ipad", 85 | "filename" : "Icon-Small@2x-1.png", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "size" : "40x40", 90 | "idiom" : "ipad", 91 | "filename" : "Icon-Spotlight-40.png", 92 | "scale" : "1x" 93 | }, 94 | { 95 | "size" : "40x40", 96 | "idiom" : "ipad", 97 | "filename" : "Icon-Spotlight-40@2x.png", 98 | "scale" : "2x" 99 | }, 100 | { 101 | "size" : "50x50", 102 | "idiom" : "ipad", 103 | "filename" : "Icon-Small-50.png", 104 | "scale" : "1x" 105 | }, 106 | { 107 | "size" : "50x50", 108 | "idiom" : "ipad", 109 | "filename" : "Icon-Small-50@2x.png", 110 | "scale" : "2x" 111 | }, 112 | { 113 | "size" : "72x72", 114 | "idiom" : "ipad", 115 | "filename" : "Icon-72.png", 116 | "scale" : "1x" 117 | }, 118 | { 119 | "size" : "72x72", 120 | "idiom" : "ipad", 121 | "filename" : "Icon-72@2x.png", 122 | "scale" : "2x" 123 | }, 124 | { 125 | "size" : "76x76", 126 | "idiom" : "ipad", 127 | "filename" : "Icon-76.png", 128 | "scale" : "1x" 129 | }, 130 | { 131 | "size" : "76x76", 132 | "idiom" : "ipad", 133 | "filename" : "Icon-76@2x.png", 134 | "scale" : "2x" 135 | }, 136 | { 137 | "size" : "83.5x83.5", 138 | "idiom" : "ipad", 139 | "filename" : "Icon-iPadPro@2x.png", 140 | "scale" : "2x" 141 | }, 142 | { 143 | "idiom" : "ios-marketing", 144 | "size" : "1024x1024", 145 | "scale" : "1x" 146 | } 147 | ], 148 | "info" : { 149 | "version" : 1, 150 | "author" : "xcode" 151 | }, 152 | "properties" : { 153 | "pre-rendered" : true 154 | } 155 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x-1.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-Spotlight-40@3x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon-iPadPro@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "736h", 7 | "filename" : "Splash_V3_1242x2208.png", 8 | "minimum-system-version" : "8.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "orientation" : "landscape", 14 | "idiom" : "iphone", 15 | "extent" : "full-screen", 16 | "minimum-system-version" : "8.0", 17 | "subtype" : "736h", 18 | "scale" : "3x" 19 | }, 20 | { 21 | "extent" : "full-screen", 22 | "idiom" : "iphone", 23 | "subtype" : "667h", 24 | "filename" : "Splash_V3_750x1334.png", 25 | "minimum-system-version" : "8.0", 26 | "orientation" : "portrait", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "orientation" : "portrait", 31 | "idiom" : "iphone", 32 | "extent" : "full-screen", 33 | "minimum-system-version" : "7.0", 34 | "filename" : "Splash_V3_640x960-1.png", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "extent" : "full-screen", 39 | "idiom" : "iphone", 40 | "subtype" : "retina4", 41 | "filename" : "Splash_V3_640x1136.png", 42 | "minimum-system-version" : "7.0", 43 | "orientation" : "portrait", 44 | "scale" : "2x" 45 | }, 46 | { 47 | "orientation" : "portrait", 48 | "idiom" : "ipad", 49 | "extent" : "full-screen", 50 | "minimum-system-version" : "7.0", 51 | "scale" : "1x" 52 | }, 53 | { 54 | "orientation" : "landscape", 55 | "idiom" : "ipad", 56 | "extent" : "full-screen", 57 | "minimum-system-version" : "7.0", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "orientation" : "portrait", 62 | "idiom" : "ipad", 63 | "extent" : "full-screen", 64 | "minimum-system-version" : "7.0", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "orientation" : "landscape", 69 | "idiom" : "ipad", 70 | "extent" : "full-screen", 71 | "minimum-system-version" : "7.0", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "orientation" : "portrait", 76 | "idiom" : "iphone", 77 | "extent" : "full-screen", 78 | "filename" : "Splash_V3_320x480.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "orientation" : "portrait", 83 | "idiom" : "iphone", 84 | "extent" : "full-screen", 85 | "filename" : "Splash_V3_640x960.png", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "orientation" : "portrait", 90 | "idiom" : "iphone", 91 | "extent" : "full-screen", 92 | "filename" : "Splash_V3_640x1136-1.png", 93 | "subtype" : "retina4", 94 | "scale" : "2x" 95 | }, 96 | { 97 | "orientation" : "portrait", 98 | "idiom" : "ipad", 99 | "extent" : "to-status-bar", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "orientation" : "portrait", 104 | "idiom" : "ipad", 105 | "extent" : "full-screen", 106 | "scale" : "1x" 107 | }, 108 | { 109 | "orientation" : "landscape", 110 | "idiom" : "ipad", 111 | "extent" : "to-status-bar", 112 | "scale" : "1x" 113 | }, 114 | { 115 | "orientation" : "landscape", 116 | "idiom" : "ipad", 117 | "extent" : "full-screen", 118 | "scale" : "1x" 119 | }, 120 | { 121 | "orientation" : "portrait", 122 | "idiom" : "ipad", 123 | "extent" : "to-status-bar", 124 | "scale" : "2x" 125 | }, 126 | { 127 | "orientation" : "portrait", 128 | "idiom" : "ipad", 129 | "extent" : "full-screen", 130 | "scale" : "2x" 131 | }, 132 | { 133 | "orientation" : "landscape", 134 | "idiom" : "ipad", 135 | "extent" : "to-status-bar", 136 | "scale" : "2x" 137 | }, 138 | { 139 | "orientation" : "landscape", 140 | "idiom" : "ipad", 141 | "extent" : "full-screen", 142 | "scale" : "2x" 143 | } 144 | ], 145 | "info" : { 146 | "version" : 1, 147 | "author" : "xcode" 148 | } 149 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_1242x2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_1242x2208.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_320x480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_320x480.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x1136-1.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x1136.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x960-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x960-1.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_640x960.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_750x1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/LaunchImage.launchimage/Splash_V3_750x1334.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_0.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_0.imageset/logo_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_0.imageset/logo_0.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_1.imageset/logo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_1.imageset/logo_1.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_10.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_10.imageset/logo_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_10.imageset/logo_10.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_2.imageset/logo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_2.imageset/logo_2.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_3.imageset/logo_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_3.imageset/logo_3.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_4.imageset/logo_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_4.imageset/logo_4.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_5.imageset/logo_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_5.imageset/logo_5.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_6.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_6.imageset/logo_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_6.imageset/logo_6.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_7.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_7.imageset/logo_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_7.imageset/logo_7.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_8.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_8.imageset/logo_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_8.imageset/logo_8.png -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo_9.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/Images.xcassets/logo_9.imageset/logo_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Demo/Images.xcassets/logo_9.imageset/logo_9.png -------------------------------------------------------------------------------- /Demo/MyCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyCell.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 17/08/16. 6 | // Copyright © 2016 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DropDown 11 | 12 | class MyCell: DropDownCell { 13 | 14 | @IBOutlet weak var logoImageView: UIImageView! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Demo/MyCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Demo/NiceButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NiceButton.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 06/06/16. 6 | // Copyright © 2016 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NiceButton: UIButton { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | 16 | let view = UIView() 17 | view.backgroundColor = UIColor(red: 0.6494, green: 0.8155, blue: 1.0, alpha: 1.0) 18 | 19 | view.translatesAutoresizingMaskIntoConstraints = false 20 | addSubview(view) 21 | 22 | view.addConstraint(NSLayoutConstraint( 23 | item: view, 24 | attribute: .height, 25 | relatedBy: .equal, 26 | toItem: nil, 27 | attribute: .height, 28 | multiplier: 1, 29 | constant: 1 30 | ) 31 | ) 32 | 33 | addConstraint(NSLayoutConstraint( 34 | item: view, 35 | attribute: .left, 36 | relatedBy: .equal, 37 | toItem: self, 38 | attribute: .left, 39 | multiplier: 1, 40 | constant: 0 41 | ) 42 | ) 43 | 44 | addConstraint(NSLayoutConstraint( 45 | item: view, 46 | attribute: .right, 47 | relatedBy: .equal, 48 | toItem: self, 49 | attribute: .right, 50 | multiplier: 1, 51 | constant: 0 52 | ) 53 | ) 54 | 55 | addConstraint(NSLayoutConstraint( 56 | item: view, 57 | attribute: .bottom, 58 | relatedBy: .equal, 59 | toItem: self, 60 | attribute: .bottom, 61 | multiplier: 1, 62 | constant: 0 63 | ) 64 | ) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DropDown 11 | 12 | class ViewController: UIViewController { 13 | 14 | //MARK: - Properties 15 | 16 | @IBOutlet weak var chooseArticleButton: UIButton! 17 | @IBOutlet weak var amountButton: UIButton! 18 | @IBOutlet weak var chooseButton: UIButton! 19 | @IBOutlet weak var centeredDropDownButton: UIButton! 20 | @IBOutlet weak var rightBarButton: UIBarButtonItem! 21 | let textField = UITextField() 22 | 23 | //MARK: - DropDown's 24 | 25 | let chooseArticleDropDown = DropDown() 26 | let amountDropDown = DropDown() 27 | let chooseDropDown = DropDown() 28 | let centeredDropDown = DropDown() 29 | let rightBarDropDown = DropDown() 30 | 31 | lazy var dropDowns: [DropDown] = { 32 | return [ 33 | self.chooseArticleDropDown, 34 | self.amountDropDown, 35 | self.chooseDropDown, 36 | self.centeredDropDown, 37 | self.rightBarDropDown 38 | ] 39 | }() 40 | 41 | //MARK: - Actions 42 | 43 | @IBAction func chooseArticle(_ sender: AnyObject) { 44 | chooseArticleDropDown.show() 45 | } 46 | 47 | @IBAction func changeAmount(_ sender: AnyObject) { 48 | amountDropDown.show() 49 | } 50 | 51 | @IBAction func choose(_ sender: AnyObject) { 52 | chooseDropDown.show() 53 | } 54 | 55 | @IBAction func showCenteredDropDown(_ sender: AnyObject) { 56 | centeredDropDown.show() 57 | } 58 | 59 | @IBAction func showBarButtonDropDown(_ sender: AnyObject) { 60 | rightBarDropDown.show() 61 | } 62 | 63 | @IBAction func changeDIsmissMode(_ sender: UISegmentedControl) { 64 | switch sender.selectedSegmentIndex { 65 | case 0: dropDowns.forEach { $0.dismissMode = .automatic } 66 | case 1: dropDowns.forEach { $0.dismissMode = .onTap } 67 | default: break; 68 | } 69 | } 70 | 71 | @IBAction func changeDirection(_ sender: UISegmentedControl) { 72 | switch sender.selectedSegmentIndex { 73 | case 0: dropDowns.forEach { $0.direction = .any } 74 | case 1: dropDowns.forEach { $0.direction = .bottom } 75 | case 2: dropDowns.forEach { $0.direction = .top } 76 | default: break; 77 | } 78 | } 79 | 80 | @IBAction func changeUI(_ sender: UISegmentedControl) { 81 | switch sender.selectedSegmentIndex { 82 | case 0: setupDefaultDropDown() 83 | case 1: customizeDropDown(self) 84 | default: break; 85 | } 86 | } 87 | 88 | @IBAction func showKeyboard(_ sender: AnyObject) { 89 | textField.becomeFirstResponder() 90 | } 91 | 92 | @IBAction func hideKeyboard(_ sender: AnyObject) { 93 | view.endEditing(false) 94 | } 95 | 96 | func setupDefaultDropDown() { 97 | DropDown.setupDefaultAppearance() 98 | 99 | dropDowns.forEach { 100 | $0.cellNib = UINib(nibName: "DropDownCell", bundle: Bundle(for: DropDownCell.self)) 101 | $0.customCellConfiguration = nil 102 | } 103 | } 104 | 105 | func customizeDropDown(_ sender: AnyObject) { 106 | let appearance = DropDown.appearance() 107 | 108 | appearance.cellHeight = 60 109 | appearance.backgroundColor = UIColor(white: 1, alpha: 1) 110 | appearance.selectionBackgroundColor = UIColor(red: 0.6494, green: 0.8155, blue: 1.0, alpha: 0.2) 111 | // appearance.separatorColor = UIColor(white: 0.7, alpha: 0.8) 112 | appearance.cornerRadius = 10 113 | appearance.shadowColor = UIColor(white: 0.6, alpha: 1) 114 | appearance.shadowOpacity = 0.9 115 | appearance.shadowRadius = 25 116 | appearance.animationduration = 0.25 117 | appearance.textColor = .darkGray 118 | // appearance.textFont = UIFont(name: "Georgia", size: 14) 119 | 120 | if #available(iOS 11.0, *) { 121 | appearance.setupMaskedCorners([.layerMaxXMaxYCorner, .layerMinXMaxYCorner]) 122 | } 123 | 124 | dropDowns.forEach { 125 | /*** FOR CUSTOM CELLS ***/ 126 | $0.cellNib = UINib(nibName: "MyCell", bundle: nil) 127 | 128 | $0.customCellConfiguration = { (index: Index, item: String, cell: DropDownCell) -> Void in 129 | guard let cell = cell as? MyCell else { return } 130 | 131 | // Setup your custom UI components 132 | cell.logoImageView.image = UIImage(named: "logo_\(index % 10)") 133 | } 134 | /*** ---------------- ***/ 135 | } 136 | } 137 | 138 | //MARK: - UIViewController 139 | 140 | override func viewDidLoad() { 141 | super.viewDidLoad() 142 | 143 | setupDropDowns() 144 | dropDowns.forEach { $0.dismissMode = .onTap } 145 | dropDowns.forEach { $0.direction = .any } 146 | 147 | view.addSubview(textField) 148 | } 149 | 150 | //MARK: - Setup 151 | 152 | func setupDropDowns() { 153 | setupChooseArticleDropDown() 154 | setupAmountDropDown() 155 | setupChooseDropDown() 156 | setupCenteredDropDown() 157 | setupRightBarDropDown() 158 | } 159 | 160 | func setupChooseArticleDropDown() { 161 | chooseArticleDropDown.anchorView = chooseArticleButton 162 | 163 | // Will set a custom with instead of anchor view width 164 | // dropDown.width = 100 165 | 166 | // By default, the dropdown will have its origin on the top left corner of its anchor view 167 | // So it will come over the anchor view and hide it completely 168 | // If you want to have the dropdown underneath your anchor view, you can do this: 169 | chooseArticleDropDown.bottomOffset = CGPoint(x: 0, y: chooseArticleButton.bounds.height) 170 | 171 | // You can also use localizationKeysDataSource instead. Check the docs. 172 | chooseArticleDropDown.dataSource = [ 173 | "iPhone SE | Black | 64G", 174 | "Samsung S7", 175 | "Huawei P8 Lite Smartphone 4G", 176 | "Asus Zenfone Max 4G", 177 | "Apple Watwh | Sport Edition" 178 | ] 179 | 180 | // Action triggered on selection 181 | chooseArticleDropDown.selectionAction = { [weak self] (index, item) in 182 | self?.chooseArticleButton.setTitle(item, for: .normal) 183 | } 184 | 185 | chooseArticleDropDown.multiSelectionAction = { [weak self] (indices, items) in 186 | print("Muti selection action called with: \(items)") 187 | if items.isEmpty { 188 | self?.chooseArticleButton.setTitle("", for: .normal) 189 | } 190 | } 191 | 192 | // Action triggered on dropdown cancelation (hide) 193 | // dropDown.cancelAction = { [unowned self] in 194 | // // You could for example deselect the selected item 195 | // self.dropDown.deselectRowAtIndexPath(self.dropDown.indexForSelectedRow) 196 | // self.actionButton.setTitle("Canceled", forState: .Normal) 197 | // } 198 | 199 | // You can manually select a row if needed 200 | // dropDown.selectRowAtIndex(3) 201 | } 202 | 203 | func setupAmountDropDown() { 204 | amountDropDown.anchorView = amountButton 205 | 206 | // By default, the dropdown will have its origin on the top left corner of its anchor view 207 | // So it will come over the anchor view and hide it completely 208 | // If you want to have the dropdown underneath your anchor view, you can do this: 209 | amountDropDown.bottomOffset = CGPoint(x: 0, y: amountButton.bounds.height) 210 | 211 | // You can also use localizationKeysDataSource instead. Check the docs. 212 | amountDropDown.dataSource = [ 213 | "10 €", 214 | "20 €", 215 | "30 €", 216 | "40 €", 217 | "50 €", 218 | "60 €", 219 | "70 €", 220 | "80 €", 221 | "90 €", 222 | "100 €", 223 | "110 €", 224 | "120 €" 225 | ] 226 | 227 | // Action triggered on selection 228 | amountDropDown.selectionAction = { [weak self] (index, item) in 229 | self?.amountButton.setTitle(item, for: .normal) 230 | } 231 | } 232 | 233 | func setupChooseDropDown() { 234 | chooseDropDown.anchorView = chooseButton 235 | 236 | // By default, the dropdown will have its origin on the top left corner of its anchor view 237 | // So it will come over the anchor view and hide it completely 238 | // If you want to have the dropdown underneath your anchor view, you can do this: 239 | chooseDropDown.bottomOffset = CGPoint(x: 0, y: chooseButton.bounds.height) 240 | 241 | // You can also use localizationKeysDataSource instead. Check the docs. 242 | chooseDropDown.dataSource = [ 243 | "Lorem ipsum dolor", 244 | "sit amet consectetur", 245 | "cadipisci en..." 246 | ] 247 | 248 | // Action triggered on selection 249 | chooseDropDown.selectionAction = { [weak self] (index, item) in 250 | self?.chooseButton.setTitle(item, for: .normal) 251 | } 252 | } 253 | 254 | func setupCenteredDropDown() { 255 | // Not setting the anchor view makes the drop down centered on screen 256 | // centeredDropDown.anchorView = centeredDropDownButton 257 | 258 | // You can also use localizationKeysDataSource instead. Check the docs. 259 | centeredDropDown.dataSource = [ 260 | "The drop down", 261 | "Is centered on", 262 | "the view because", 263 | "it has no anchor view defined.", 264 | "Click anywhere to dismiss." 265 | ] 266 | 267 | centeredDropDown.selectionAction = { [weak self] (index, item) in 268 | self?.centeredDropDownButton.setTitle(item, for: .normal) 269 | } 270 | } 271 | 272 | func setupRightBarDropDown() { 273 | rightBarDropDown.anchorView = rightBarButton 274 | 275 | // You can also use localizationKeysDataSource instead. Check the docs. 276 | rightBarDropDown.dataSource = [ 277 | "Menu 1", 278 | "Menu 2", 279 | "Menu 3", 280 | "Menu 4" 281 | ] 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /DropDown.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "DropDown" 4 | s.version = "2.3.13" 5 | s.summary = "A Material Design drop down" 6 | 7 | s.description = <<-DESC 8 | This drop down is to overcome the loss of usability and user experience due to the UIPickerView. Material Design did a good job there so this drop down is very inspired by it. It appears at the right location instead of the bottom of the screen as default with UIPickerView and if possible, all options are displayed at once. 9 | DESC 10 | 11 | s.homepage = "https://github.com/AssistoLab/DropDown" 12 | s.screenshots = "https://github.com/AssistoLab/DropDown/blob/master/Screenshots/1.png?raw=true", "https://github.com/AssistoLab/DropDown/blob/master/Screenshots/2.png?raw=true" 13 | 14 | s.license = { :type => "MIT", :file => "LICENSE" } 15 | 16 | s.author = { "kevin-hirsch" => "kevin.hirsch.be@gmail.com" } 17 | s.social_media_url = "http://twitter.com/kevinh6113" 18 | 19 | s.platform = :ios, '8.0' 20 | s.source = { 21 | :git => "https://github.com/AssistoLab/DropDown.git", 22 | :tag => "v#{s.version.to_s}" 23 | } 24 | 25 | s.source_files = "DropDown/src", "DropDown/src/**/*.{h,m}", "DropDown/helpers", "DropDown/helpers/**/*.{h,m}" 26 | s.resources = "DropDown/resources/*.{png,xib}" 27 | s.requires_arc = true 28 | 29 | s.swift_version = '5.0' 30 | end 31 | -------------------------------------------------------------------------------- /DropDown.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A76442B1B676C2300BF1A2D /* DropDownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A76442A1B676C2300BF1A2D /* DropDownTests.swift */; }; 11 | 0A8518D51D6480190089529A /* MyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8518D41D6480190089529A /* MyCell.swift */; }; 12 | 0A8518D71D6481E30089529A /* MyCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A8518D61D6481E30089529A /* MyCell.xib */; }; 13 | 0AB5D8881D0EEEFF002D3A17 /* DPDConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D87E1D0EEEFF002D3A17 /* DPDConstants.swift */; }; 14 | 0AB5D8891D0EEEFF002D3A17 /* DPDKeyboardListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D87F1D0EEEFF002D3A17 /* DPDKeyboardListener.swift */; }; 15 | 0AB5D88A1D0EEEFF002D3A17 /* DPDUIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8801D0EEEFF002D3A17 /* DPDUIView+Extension.swift */; }; 16 | 0AB5D88B1D0EEEFF002D3A17 /* DropDownCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0AB5D8821D0EEEFF002D3A17 /* DropDownCell.xib */; }; 17 | 0AB5D88C1D0EEEFF002D3A17 /* DropDown+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8841D0EEEFF002D3A17 /* DropDown+Appearance.swift */; }; 18 | 0AB5D88D1D0EEEFF002D3A17 /* DropDown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8851D0EEEFF002D3A17 /* DropDown.swift */; }; 19 | 0AB5D88E1D0EEEFF002D3A17 /* DropDownCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8861D0EEEFF002D3A17 /* DropDownCell.swift */; }; 20 | 0AB5D88F1D0EEEFF002D3A17 /* DropDown.h in Headers */ = {isa = PBXBuildFile; fileRef = 0AB5D8871D0EEEFF002D3A17 /* DropDown.h */; settings = {ATTRIBUTES = (Public, ); }; }; 21 | 0AB5D8901D0EEFA8002D3A17 /* DropDown.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AB5D8711D0EEECD002D3A17 /* DropDown.framework */; }; 22 | 0AB5D8911D0EEFA8002D3A17 /* DropDown.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0AB5D8711D0EEECD002D3A17 /* DropDown.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 23 | 0AB5D8981D0EF15D002D3A17 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8951D0EF15D002D3A17 /* AppDelegate.swift */; }; 24 | 0AB5D8991D0EF15D002D3A17 /* NiceButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8961D0EF15D002D3A17 /* NiceButton.swift */; }; 25 | 0AB5D89A1D0EF15D002D3A17 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */; }; 26 | 0AB5D8A11D0EF173002D3A17 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0AB5D89F1D0EF173002D3A17 /* Main.storyboard */; }; 27 | 0AC1C33A1B6B884100A8DC0D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AC1C3391B6B884100A8DC0D /* Images.xcassets */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 0A7644251B676C2300BF1A2D /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 0A7644071B676C2300BF1A2D /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 0A76440E1B676C2300BF1A2D; 36 | remoteInfo = DropDown; 37 | }; 38 | 0AB5D8921D0EEFA8002D3A17 /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 0A7644071B676C2300BF1A2D /* Project object */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 0AB5D8701D0EEECD002D3A17; 43 | remoteInfo = DropDown; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXCopyFilesBuildPhase section */ 48 | 0AB5D8941D0EEFA8002D3A17 /* Embed Frameworks */ = { 49 | isa = PBXCopyFilesBuildPhase; 50 | buildActionMask = 2147483647; 51 | dstPath = ""; 52 | dstSubfolderSpec = 10; 53 | files = ( 54 | 0AB5D8911D0EEFA8002D3A17 /* DropDown.framework in Embed Frameworks */, 55 | ); 56 | name = "Embed Frameworks"; 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXCopyFilesBuildPhase section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | 0A76440F1B676C2300BF1A2D /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 0A7644241B676C2300BF1A2D /* DropDownTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DropDownTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 0A7644291B676C2300BF1A2D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 65 | 0A76442A1B676C2300BF1A2D /* DropDownTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropDownTests.swift; sourceTree = ""; }; 66 | 0A8518D41D6480190089529A /* MyCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyCell.swift; sourceTree = ""; }; 67 | 0A8518D61D6481E30089529A /* MyCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MyCell.xib; sourceTree = ""; }; 68 | 0AB5D8521D0EEC2A002D3A17 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = /Users/kevin/Documents/Xcode/DropDown/Info.plist; sourceTree = ""; }; 69 | 0AB5D8711D0EEECD002D3A17 /* DropDown.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DropDown.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 0AB5D8751D0EEECD002D3A17 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 0AB5D87E1D0EEEFF002D3A17 /* DPDConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPDConstants.swift; sourceTree = ""; }; 72 | 0AB5D87F1D0EEEFF002D3A17 /* DPDKeyboardListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPDKeyboardListener.swift; sourceTree = ""; }; 73 | 0AB5D8801D0EEEFF002D3A17 /* DPDUIView+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DPDUIView+Extension.swift"; sourceTree = ""; }; 74 | 0AB5D8821D0EEEFF002D3A17 /* DropDownCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DropDownCell.xib; sourceTree = ""; }; 75 | 0AB5D8841D0EEEFF002D3A17 /* DropDown+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DropDown+Appearance.swift"; sourceTree = ""; }; 76 | 0AB5D8851D0EEEFF002D3A17 /* DropDown.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDown.swift; sourceTree = ""; }; 77 | 0AB5D8861D0EEEFF002D3A17 /* DropDownCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownCell.swift; sourceTree = ""; }; 78 | 0AB5D8871D0EEEFF002D3A17 /* DropDown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropDown.h; sourceTree = ""; }; 79 | 0AB5D8951D0EF15D002D3A17 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 80 | 0AB5D8961D0EF15D002D3A17 /* NiceButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NiceButton.swift; sourceTree = ""; }; 81 | 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 82 | 0AB5D8A01D0EF173002D3A17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 83 | 0AC1C3391B6B884100A8DC0D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Demo/Images.xcassets; sourceTree = ""; }; 84 | /* End PBXFileReference section */ 85 | 86 | /* Begin PBXFrameworksBuildPhase section */ 87 | 0A76440C1B676C2300BF1A2D /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 0AB5D8901D0EEFA8002D3A17 /* DropDown.framework in Frameworks */, 92 | ); 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | 0A7644211B676C2300BF1A2D /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | 0AB5D86D1D0EEECD002D3A17 /* Frameworks */ = { 103 | isa = PBXFrameworksBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | ); 107 | runOnlyForDeploymentPostprocessing = 0; 108 | }; 109 | /* End PBXFrameworksBuildPhase section */ 110 | 111 | /* Begin PBXGroup section */ 112 | 0A7644061B676C2300BF1A2D = { 113 | isa = PBXGroup; 114 | children = ( 115 | 0AB5D8721D0EEECD002D3A17 /* DropDown */, 116 | 0A7644111B676C2300BF1A2D /* Demo */, 117 | 0AC1C3391B6B884100A8DC0D /* Images.xcassets */, 118 | 0A7644271B676C2300BF1A2D /* DropDownTests */, 119 | 0A7644101B676C2300BF1A2D /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 0A7644101B676C2300BF1A2D /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 0A76440F1B676C2300BF1A2D /* Demo.app */, 127 | 0A7644241B676C2300BF1A2D /* DropDownTests.xctest */, 128 | 0AB5D8711D0EEECD002D3A17 /* DropDown.framework */, 129 | ); 130 | name = Products; 131 | sourceTree = ""; 132 | }; 133 | 0A7644111B676C2300BF1A2D /* Demo */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 0AB5D8951D0EF15D002D3A17 /* AppDelegate.swift */, 137 | 0AB5D8971D0EF15D002D3A17 /* ViewController.swift */, 138 | 0A8518D41D6480190089529A /* MyCell.swift */, 139 | 0A8518D61D6481E30089529A /* MyCell.xib */, 140 | 0AB5D89F1D0EF173002D3A17 /* Main.storyboard */, 141 | 0AB5D8961D0EF15D002D3A17 /* NiceButton.swift */, 142 | 0A7644121B676C2300BF1A2D /* Supporting Files */, 143 | ); 144 | path = Demo; 145 | sourceTree = ""; 146 | }; 147 | 0A7644121B676C2300BF1A2D /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 0AB5D8521D0EEC2A002D3A17 /* Info.plist */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 0A7644271B676C2300BF1A2D /* DropDownTests */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 0A76442A1B676C2300BF1A2D /* DropDownTests.swift */, 159 | 0A7644281B676C2300BF1A2D /* Supporting Files */, 160 | ); 161 | path = DropDownTests; 162 | sourceTree = ""; 163 | }; 164 | 0A7644281B676C2300BF1A2D /* Supporting Files */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 0A7644291B676C2300BF1A2D /* Info.plist */, 168 | ); 169 | name = "Supporting Files"; 170 | sourceTree = ""; 171 | }; 172 | 0AB5D8721D0EEECD002D3A17 /* DropDown */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | 0AB5D8871D0EEEFF002D3A17 /* DropDown.h */, 176 | 0AB5D8751D0EEECD002D3A17 /* Info.plist */, 177 | 0AB5D87D1D0EEEFF002D3A17 /* helpers */, 178 | 0AB5D8811D0EEEFF002D3A17 /* resources */, 179 | 0AB5D8831D0EEEFF002D3A17 /* src */, 180 | ); 181 | path = DropDown; 182 | sourceTree = ""; 183 | }; 184 | 0AB5D87D1D0EEEFF002D3A17 /* helpers */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 0AB5D87E1D0EEEFF002D3A17 /* DPDConstants.swift */, 188 | 0AB5D87F1D0EEEFF002D3A17 /* DPDKeyboardListener.swift */, 189 | 0AB5D8801D0EEEFF002D3A17 /* DPDUIView+Extension.swift */, 190 | ); 191 | path = helpers; 192 | sourceTree = ""; 193 | }; 194 | 0AB5D8811D0EEEFF002D3A17 /* resources */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 0AB5D8821D0EEEFF002D3A17 /* DropDownCell.xib */, 198 | ); 199 | path = resources; 200 | sourceTree = ""; 201 | }; 202 | 0AB5D8831D0EEEFF002D3A17 /* src */ = { 203 | isa = PBXGroup; 204 | children = ( 205 | 0AB5D8841D0EEEFF002D3A17 /* DropDown+Appearance.swift */, 206 | 0AB5D8851D0EEEFF002D3A17 /* DropDown.swift */, 207 | 0AB5D8861D0EEEFF002D3A17 /* DropDownCell.swift */, 208 | ); 209 | path = src; 210 | sourceTree = ""; 211 | }; 212 | /* End PBXGroup section */ 213 | 214 | /* Begin PBXHeadersBuildPhase section */ 215 | 0AB5D86E1D0EEECD002D3A17 /* Headers */ = { 216 | isa = PBXHeadersBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | 0AB5D88F1D0EEEFF002D3A17 /* DropDown.h in Headers */, 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXHeadersBuildPhase section */ 224 | 225 | /* Begin PBXNativeTarget section */ 226 | 0A76440E1B676C2300BF1A2D /* Demo */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = 0A76442E1B676C2300BF1A2D /* Build configuration list for PBXNativeTarget "Demo" */; 229 | buildPhases = ( 230 | 0A76440B1B676C2300BF1A2D /* Sources */, 231 | 0A76440C1B676C2300BF1A2D /* Frameworks */, 232 | 0A76440D1B676C2300BF1A2D /* Resources */, 233 | 0AB5D8941D0EEFA8002D3A17 /* Embed Frameworks */, 234 | ); 235 | buildRules = ( 236 | ); 237 | dependencies = ( 238 | 0AB5D8931D0EEFA8002D3A17 /* PBXTargetDependency */, 239 | ); 240 | name = Demo; 241 | productName = DropDown; 242 | productReference = 0A76440F1B676C2300BF1A2D /* Demo.app */; 243 | productType = "com.apple.product-type.application"; 244 | }; 245 | 0A7644231B676C2300BF1A2D /* DropDownTests */ = { 246 | isa = PBXNativeTarget; 247 | buildConfigurationList = 0A7644311B676C2300BF1A2D /* Build configuration list for PBXNativeTarget "DropDownTests" */; 248 | buildPhases = ( 249 | 0A7644201B676C2300BF1A2D /* Sources */, 250 | 0A7644211B676C2300BF1A2D /* Frameworks */, 251 | 0A7644221B676C2300BF1A2D /* Resources */, 252 | ); 253 | buildRules = ( 254 | ); 255 | dependencies = ( 256 | 0A7644261B676C2300BF1A2D /* PBXTargetDependency */, 257 | ); 258 | name = DropDownTests; 259 | productName = DropDownTests; 260 | productReference = 0A7644241B676C2300BF1A2D /* DropDownTests.xctest */; 261 | productType = "com.apple.product-type.bundle.unit-test"; 262 | }; 263 | 0AB5D8701D0EEECD002D3A17 /* DropDown */ = { 264 | isa = PBXNativeTarget; 265 | buildConfigurationList = 0AB5D87A1D0EEECD002D3A17 /* Build configuration list for PBXNativeTarget "DropDown" */; 266 | buildPhases = ( 267 | 0AB5D86C1D0EEECD002D3A17 /* Sources */, 268 | 0AB5D86D1D0EEECD002D3A17 /* Frameworks */, 269 | 0AB5D86E1D0EEECD002D3A17 /* Headers */, 270 | 0AB5D86F1D0EEECD002D3A17 /* Resources */, 271 | ); 272 | buildRules = ( 273 | ); 274 | dependencies = ( 275 | ); 276 | name = DropDown; 277 | productName = DropDown; 278 | productReference = 0AB5D8711D0EEECD002D3A17 /* DropDown.framework */; 279 | productType = "com.apple.product-type.framework"; 280 | }; 281 | /* End PBXNativeTarget section */ 282 | 283 | /* Begin PBXProject section */ 284 | 0A7644071B676C2300BF1A2D /* Project object */ = { 285 | isa = PBXProject; 286 | attributes = { 287 | LastSwiftUpdateCheck = 0730; 288 | LastUpgradeCheck = 1020; 289 | ORGANIZATIONNAME = "Kevin Hirsch"; 290 | TargetAttributes = { 291 | 0A76440E1B676C2300BF1A2D = { 292 | CreatedOnToolsVersion = 6.4; 293 | LastSwiftMigration = 1020; 294 | }; 295 | 0A7644231B676C2300BF1A2D = { 296 | CreatedOnToolsVersion = 6.4; 297 | LastSwiftMigration = 1020; 298 | TestTargetID = 0A76440E1B676C2300BF1A2D; 299 | }; 300 | 0AB5D8701D0EEECD002D3A17 = { 301 | CreatedOnToolsVersion = 7.3.1; 302 | LastSwiftMigration = 1020; 303 | }; 304 | }; 305 | }; 306 | buildConfigurationList = 0A76440A1B676C2300BF1A2D /* Build configuration list for PBXProject "DropDown" */; 307 | compatibilityVersion = "Xcode 3.2"; 308 | developmentRegion = en; 309 | hasScannedForEncodings = 0; 310 | knownRegions = ( 311 | en, 312 | Base, 313 | ); 314 | mainGroup = 0A7644061B676C2300BF1A2D; 315 | productRefGroup = 0A7644101B676C2300BF1A2D /* Products */; 316 | projectDirPath = ""; 317 | projectRoot = ""; 318 | targets = ( 319 | 0A76440E1B676C2300BF1A2D /* Demo */, 320 | 0A7644231B676C2300BF1A2D /* DropDownTests */, 321 | 0AB5D8701D0EEECD002D3A17 /* DropDown */, 322 | ); 323 | }; 324 | /* End PBXProject section */ 325 | 326 | /* Begin PBXResourcesBuildPhase section */ 327 | 0A76440D1B676C2300BF1A2D /* Resources */ = { 328 | isa = PBXResourcesBuildPhase; 329 | buildActionMask = 2147483647; 330 | files = ( 331 | 0AC1C33A1B6B884100A8DC0D /* Images.xcassets in Resources */, 332 | 0A8518D71D6481E30089529A /* MyCell.xib in Resources */, 333 | 0AB5D8A11D0EF173002D3A17 /* Main.storyboard in Resources */, 334 | ); 335 | runOnlyForDeploymentPostprocessing = 0; 336 | }; 337 | 0A7644221B676C2300BF1A2D /* Resources */ = { 338 | isa = PBXResourcesBuildPhase; 339 | buildActionMask = 2147483647; 340 | files = ( 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | 0AB5D86F1D0EEECD002D3A17 /* Resources */ = { 345 | isa = PBXResourcesBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | 0AB5D88B1D0EEEFF002D3A17 /* DropDownCell.xib in Resources */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | /* End PBXResourcesBuildPhase section */ 353 | 354 | /* Begin PBXSourcesBuildPhase section */ 355 | 0A76440B1B676C2300BF1A2D /* Sources */ = { 356 | isa = PBXSourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | 0AB5D89A1D0EF15D002D3A17 /* ViewController.swift in Sources */, 360 | 0A8518D51D6480190089529A /* MyCell.swift in Sources */, 361 | 0AB5D8991D0EF15D002D3A17 /* NiceButton.swift in Sources */, 362 | 0AB5D8981D0EF15D002D3A17 /* AppDelegate.swift in Sources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | 0A7644201B676C2300BF1A2D /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | 0A76442B1B676C2300BF1A2D /* DropDownTests.swift in Sources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | 0AB5D86C1D0EEECD002D3A17 /* Sources */ = { 375 | isa = PBXSourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | 0AB5D88D1D0EEEFF002D3A17 /* DropDown.swift in Sources */, 379 | 0AB5D88C1D0EEEFF002D3A17 /* DropDown+Appearance.swift in Sources */, 380 | 0AB5D88A1D0EEEFF002D3A17 /* DPDUIView+Extension.swift in Sources */, 381 | 0AB5D8881D0EEEFF002D3A17 /* DPDConstants.swift in Sources */, 382 | 0AB5D88E1D0EEEFF002D3A17 /* DropDownCell.swift in Sources */, 383 | 0AB5D8891D0EEEFF002D3A17 /* DPDKeyboardListener.swift in Sources */, 384 | ); 385 | runOnlyForDeploymentPostprocessing = 0; 386 | }; 387 | /* End PBXSourcesBuildPhase section */ 388 | 389 | /* Begin PBXTargetDependency section */ 390 | 0A7644261B676C2300BF1A2D /* PBXTargetDependency */ = { 391 | isa = PBXTargetDependency; 392 | target = 0A76440E1B676C2300BF1A2D /* Demo */; 393 | targetProxy = 0A7644251B676C2300BF1A2D /* PBXContainerItemProxy */; 394 | }; 395 | 0AB5D8931D0EEFA8002D3A17 /* PBXTargetDependency */ = { 396 | isa = PBXTargetDependency; 397 | target = 0AB5D8701D0EEECD002D3A17 /* DropDown */; 398 | targetProxy = 0AB5D8921D0EEFA8002D3A17 /* PBXContainerItemProxy */; 399 | }; 400 | /* End PBXTargetDependency section */ 401 | 402 | /* Begin PBXVariantGroup section */ 403 | 0AB5D89F1D0EF173002D3A17 /* Main.storyboard */ = { 404 | isa = PBXVariantGroup; 405 | children = ( 406 | 0AB5D8A01D0EF173002D3A17 /* Base */, 407 | ); 408 | name = Main.storyboard; 409 | sourceTree = ""; 410 | }; 411 | /* End PBXVariantGroup section */ 412 | 413 | /* Begin XCBuildConfiguration section */ 414 | 0A76442C1B676C2300BF1A2D /* Debug */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 419 | CLANG_CXX_LIBRARY = "libc++"; 420 | CLANG_ENABLE_MODULES = YES; 421 | CLANG_ENABLE_OBJC_ARC = YES; 422 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 423 | CLANG_WARN_BOOL_CONVERSION = YES; 424 | CLANG_WARN_COMMA = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 428 | CLANG_WARN_EMPTY_BODY = YES; 429 | CLANG_WARN_ENUM_CONVERSION = YES; 430 | CLANG_WARN_INFINITE_RECURSION = YES; 431 | CLANG_WARN_INT_CONVERSION = YES; 432 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 433 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 434 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNREACHABLE_CODE = YES; 440 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 442 | COPY_PHASE_STRIP = NO; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_STRICT_OBJC_MSGSEND = YES; 445 | ENABLE_TESTABILITY = YES; 446 | GCC_C_LANGUAGE_STANDARD = gnu99; 447 | GCC_DYNAMIC_NO_PIC = NO; 448 | GCC_NO_COMMON_BLOCKS = YES; 449 | GCC_OPTIMIZATION_LEVEL = 0; 450 | GCC_PREPROCESSOR_DEFINITIONS = ( 451 | "DEBUG=1", 452 | "$(inherited)", 453 | ); 454 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 455 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 456 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 457 | GCC_WARN_UNDECLARED_SELECTOR = YES; 458 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 459 | GCC_WARN_UNUSED_FUNCTION = YES; 460 | GCC_WARN_UNUSED_VARIABLE = YES; 461 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 462 | MTL_ENABLE_DEBUG_INFO = YES; 463 | ONLY_ACTIVE_ARCH = YES; 464 | SDKROOT = iphoneos; 465 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 466 | SWIFT_VERSION = 4.2; 467 | }; 468 | name = Debug; 469 | }; 470 | 0A76442D1B676C2300BF1A2D /* Release */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | ALWAYS_SEARCH_USER_PATHS = NO; 474 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 475 | CLANG_CXX_LIBRARY = "libc++"; 476 | CLANG_ENABLE_MODULES = YES; 477 | CLANG_ENABLE_OBJC_ARC = YES; 478 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 479 | CLANG_WARN_BOOL_CONVERSION = YES; 480 | CLANG_WARN_COMMA = YES; 481 | CLANG_WARN_CONSTANT_CONVERSION = YES; 482 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 483 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 484 | CLANG_WARN_EMPTY_BODY = YES; 485 | CLANG_WARN_ENUM_CONVERSION = YES; 486 | CLANG_WARN_INFINITE_RECURSION = YES; 487 | CLANG_WARN_INT_CONVERSION = YES; 488 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 489 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 490 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 491 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 492 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 493 | CLANG_WARN_STRICT_PROTOTYPES = YES; 494 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 495 | CLANG_WARN_UNREACHABLE_CODE = YES; 496 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 497 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 498 | COPY_PHASE_STRIP = NO; 499 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 500 | ENABLE_NS_ASSERTIONS = NO; 501 | ENABLE_STRICT_OBJC_MSGSEND = YES; 502 | GCC_C_LANGUAGE_STANDARD = gnu99; 503 | GCC_NO_COMMON_BLOCKS = YES; 504 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 505 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 506 | GCC_WARN_UNDECLARED_SELECTOR = YES; 507 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 508 | GCC_WARN_UNUSED_FUNCTION = YES; 509 | GCC_WARN_UNUSED_VARIABLE = YES; 510 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 511 | MTL_ENABLE_DEBUG_INFO = NO; 512 | SDKROOT = iphoneos; 513 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 514 | SWIFT_VERSION = 4.2; 515 | VALIDATE_PRODUCT = YES; 516 | }; 517 | name = Release; 518 | }; 519 | 0A76442F1B676C2300BF1A2D /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 523 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 524 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 525 | CLANG_ENABLE_MODULES = YES; 526 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 528 | PRODUCT_BUNDLE_IDENTIFIER = com.assistoLab.Demo; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 531 | SWIFT_VERSION = 5.0; 532 | TARGETED_DEVICE_FAMILY = "1,2"; 533 | }; 534 | name = Debug; 535 | }; 536 | 0A7644301B676C2300BF1A2D /* Release */ = { 537 | isa = XCBuildConfiguration; 538 | buildSettings = { 539 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 540 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 541 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 542 | CLANG_ENABLE_MODULES = YES; 543 | INFOPLIST_FILE = "$(SRCROOT)/Info.plist"; 544 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 545 | PRODUCT_BUNDLE_IDENTIFIER = com.assistoLab.Demo; 546 | PRODUCT_NAME = "$(TARGET_NAME)"; 547 | SWIFT_VERSION = 5.0; 548 | TARGETED_DEVICE_FAMILY = "1,2"; 549 | }; 550 | name = Release; 551 | }; 552 | 0A7644321B676C2300BF1A2D /* Debug */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | BUNDLE_LOADER = "$(TEST_HOST)"; 556 | GCC_PREPROCESSOR_DEFINITIONS = ( 557 | "DEBUG=1", 558 | "$(inherited)", 559 | ); 560 | INFOPLIST_FILE = DropDownTests/Info.plist; 561 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 562 | PRODUCT_BUNDLE_IDENTIFIER = "com.kevin.hirsch.$(PRODUCT_NAME:rfc1034identifier)"; 563 | PRODUCT_NAME = "$(TARGET_NAME)"; 564 | SWIFT_VERSION = 5.0; 565 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 566 | }; 567 | name = Debug; 568 | }; 569 | 0A7644331B676C2300BF1A2D /* Release */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | BUNDLE_LOADER = "$(TEST_HOST)"; 573 | INFOPLIST_FILE = DropDownTests/Info.plist; 574 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 575 | PRODUCT_BUNDLE_IDENTIFIER = "com.kevin.hirsch.$(PRODUCT_NAME:rfc1034identifier)"; 576 | PRODUCT_NAME = "$(TARGET_NAME)"; 577 | SWIFT_VERSION = 5.0; 578 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Demo.app/Demo"; 579 | }; 580 | name = Release; 581 | }; 582 | 0AB5D87B1D0EEECD002D3A17 /* Debug */ = { 583 | isa = XCBuildConfiguration; 584 | buildSettings = { 585 | CLANG_ANALYZER_NONNULL = YES; 586 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 587 | CURRENT_PROJECT_VERSION = 1; 588 | DEBUG_INFORMATION_FORMAT = dwarf; 589 | DEFINES_MODULE = YES; 590 | DYLIB_COMPATIBILITY_VERSION = 1; 591 | DYLIB_CURRENT_VERSION = 1; 592 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 593 | ENABLE_TESTABILITY = YES; 594 | INFOPLIST_FILE = DropDown/Info.plist; 595 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 596 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 597 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 598 | PRODUCT_BUNDLE_IDENTIFIER = com.assistoLab.DropDown; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | SKIP_INSTALL = YES; 601 | SWIFT_VERSION = 5.0; 602 | TARGETED_DEVICE_FAMILY = "1,2"; 603 | VERSIONING_SYSTEM = "apple-generic"; 604 | VERSION_INFO_PREFIX = ""; 605 | }; 606 | name = Debug; 607 | }; 608 | 0AB5D87C1D0EEECD002D3A17 /* Release */ = { 609 | isa = XCBuildConfiguration; 610 | buildSettings = { 611 | CLANG_ANALYZER_NONNULL = YES; 612 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 613 | CURRENT_PROJECT_VERSION = 1; 614 | DEFINES_MODULE = YES; 615 | DYLIB_COMPATIBILITY_VERSION = 1; 616 | DYLIB_CURRENT_VERSION = 1; 617 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 618 | INFOPLIST_FILE = DropDown/Info.plist; 619 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 620 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 621 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 622 | PRODUCT_BUNDLE_IDENTIFIER = com.assistoLab.DropDown; 623 | PRODUCT_NAME = "$(TARGET_NAME)"; 624 | SKIP_INSTALL = YES; 625 | SWIFT_VERSION = 5.0; 626 | TARGETED_DEVICE_FAMILY = "1,2"; 627 | VERSIONING_SYSTEM = "apple-generic"; 628 | VERSION_INFO_PREFIX = ""; 629 | }; 630 | name = Release; 631 | }; 632 | /* End XCBuildConfiguration section */ 633 | 634 | /* Begin XCConfigurationList section */ 635 | 0A76440A1B676C2300BF1A2D /* Build configuration list for PBXProject "DropDown" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | 0A76442C1B676C2300BF1A2D /* Debug */, 639 | 0A76442D1B676C2300BF1A2D /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | 0A76442E1B676C2300BF1A2D /* Build configuration list for PBXNativeTarget "Demo" */ = { 645 | isa = XCConfigurationList; 646 | buildConfigurations = ( 647 | 0A76442F1B676C2300BF1A2D /* Debug */, 648 | 0A7644301B676C2300BF1A2D /* Release */, 649 | ); 650 | defaultConfigurationIsVisible = 0; 651 | defaultConfigurationName = Release; 652 | }; 653 | 0A7644311B676C2300BF1A2D /* Build configuration list for PBXNativeTarget "DropDownTests" */ = { 654 | isa = XCConfigurationList; 655 | buildConfigurations = ( 656 | 0A7644321B676C2300BF1A2D /* Debug */, 657 | 0A7644331B676C2300BF1A2D /* Release */, 658 | ); 659 | defaultConfigurationIsVisible = 0; 660 | defaultConfigurationName = Release; 661 | }; 662 | 0AB5D87A1D0EEECD002D3A17 /* Build configuration list for PBXNativeTarget "DropDown" */ = { 663 | isa = XCConfigurationList; 664 | buildConfigurations = ( 665 | 0AB5D87B1D0EEECD002D3A17 /* Debug */, 666 | 0AB5D87C1D0EEECD002D3A17 /* Release */, 667 | ); 668 | defaultConfigurationIsVisible = 0; 669 | defaultConfigurationName = Release; 670 | }; 671 | /* End XCConfigurationList section */ 672 | }; 673 | rootObject = 0A7644071B676C2300BF1A2D /* Project object */; 674 | } 675 | -------------------------------------------------------------------------------- /DropDown.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DropDown.xcodeproj/project.xcworkspace/xcshareddata/DropDown.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "6575E401F0E075B1FD6E179DAD8CCDF3853A7A8E", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "E4EC3EC9984A396270972820B35B740A9BEF1CCB" : 0, 8 | "6575E401F0E075B1FD6E179DAD8CCDF3853A7A8E" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "26F545BD-A325-473F-B7B3-A037533DDB11", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "E4EC3EC9984A396270972820B35B740A9BEF1CCB" : "", 13 | "6575E401F0E075B1FD6E179DAD8CCDF3853A7A8E" : "DropDown\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "DropDown", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "DropDown.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/kevin-hirsch\/DropDown.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "6575E401F0E075B1FD6E179DAD8CCDF3853A7A8E" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/kevin-hirsch\/FloatingLabel.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "E4EC3EC9984A396270972820B35B740A9BEF1CCB" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /DropDown.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DropDown.xcodeproj/xcshareddata/xcschemes/DropDown.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /DropDown/DropDown.h: -------------------------------------------------------------------------------- 1 | // 2 | // DropDown.h 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 13/06/16. 6 | // Copyright © 2016 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DropDown. 12 | FOUNDATION_EXPORT double DropDownVersionNumber; 13 | 14 | //! Project version string for DropDown. 15 | FOUNDATION_EXPORT const unsigned char DropDownVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /DropDown/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.3.12 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /DropDown/helpers/DPDConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | internal struct DPDConstant { 14 | 15 | internal struct KeyPath { 16 | 17 | static let Frame = "frame" 18 | 19 | } 20 | 21 | internal struct ReusableIdentifier { 22 | 23 | static let DropDownCell = "DropDownCell" 24 | 25 | } 26 | 27 | internal struct UI { 28 | 29 | static let TextColor = UIColor.black 30 | static let SelectedTextColor = UIColor.black 31 | static let TextFont = UIFont.systemFont(ofSize: 15) 32 | static let BackgroundColor = UIColor(white: 0.94, alpha: 1) 33 | static let SelectionBackgroundColor = UIColor(white: 0.89, alpha: 1) 34 | static let SeparatorColor = UIColor.clear 35 | static let CornerRadius: CGFloat = 2 36 | static let RowHeight: CGFloat = 44 37 | static let HeightPadding: CGFloat = 20 38 | 39 | struct Shadow { 40 | 41 | static let Color = UIColor.darkGray 42 | static let Offset = CGSize.zero 43 | static let Opacity: Float = 0.4 44 | static let Radius: CGFloat = 8 45 | 46 | } 47 | 48 | } 49 | 50 | internal struct Animation { 51 | 52 | static let Duration = 0.15 53 | static let EntranceOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseOut] 54 | static let ExitOptions: UIView.AnimationOptions = [.allowUserInteraction, .curveEaseIn] 55 | static let DownScaleTransform = CGAffineTransform(scaleX: 0.9, y: 0.9) 56 | 57 | } 58 | 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /DropDown/helpers/DPDKeyboardListener.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardListener.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 30/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | internal final class KeyboardListener { 14 | 15 | static let sharedInstance = KeyboardListener() 16 | 17 | fileprivate(set) var isVisible = false 18 | fileprivate(set) var keyboardFrame = CGRect.zero 19 | fileprivate var isListening = false 20 | 21 | deinit { 22 | stopListeningToKeyboard() 23 | } 24 | 25 | } 26 | 27 | //MARK: - Notifications 28 | 29 | extension KeyboardListener { 30 | 31 | func startListeningToKeyboard() { 32 | if isListening { 33 | return 34 | } 35 | 36 | isListening = true 37 | 38 | NotificationCenter.default.addObserver( 39 | self, 40 | selector: #selector(keyboardWillShow(_:)), 41 | name: UIResponder.keyboardWillShowNotification, 42 | object: nil) 43 | NotificationCenter.default.addObserver( 44 | self, 45 | selector: #selector(keyboardWillHide(_:)), 46 | name: UIResponder.keyboardWillHideNotification, 47 | object: nil) 48 | } 49 | 50 | func stopListeningToKeyboard() { 51 | NotificationCenter.default.removeObserver(self) 52 | } 53 | 54 | @objc 55 | fileprivate func keyboardWillShow(_ notification: Notification) { 56 | isVisible = true 57 | keyboardFrame = keyboardFrame(fromNotification: notification) 58 | } 59 | 60 | @objc 61 | fileprivate func keyboardWillHide(_ notification: Notification) { 62 | isVisible = false 63 | keyboardFrame = keyboardFrame(fromNotification: notification) 64 | } 65 | 66 | fileprivate func keyboardFrame(fromNotification notification: Notification) -> CGRect { 67 | return ((notification as NSNotification).userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero 68 | } 69 | 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /DropDown/helpers/DPDUIView+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Constraints.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | //MARK: - Constraints 14 | 15 | internal extension UIView { 16 | 17 | func addConstraints(format: String, options: NSLayoutConstraint.FormatOptions = [], metrics: [String: AnyObject]? = nil, views: [String: UIView]) { 18 | addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: options, metrics: metrics, views: views)) 19 | } 20 | 21 | func addUniversalConstraints(format: String, options: NSLayoutConstraint.FormatOptions = [], metrics: [String: AnyObject]? = nil, views: [String: UIView]) { 22 | addConstraints(format: "H:\(format)", options: options, metrics: metrics, views: views) 23 | addConstraints(format: "V:\(format)", options: options, metrics: metrics, views: views) 24 | } 25 | 26 | } 27 | 28 | 29 | 30 | //MARK: - Bounds 31 | 32 | internal extension UIView { 33 | 34 | var windowFrame: CGRect? { 35 | return superview?.convert(frame, to: nil) 36 | } 37 | 38 | } 39 | 40 | internal extension UIWindow { 41 | 42 | static func visibleWindow() -> UIWindow? { 43 | var currentWindow = UIApplication.shared.keyWindow 44 | 45 | if currentWindow == nil { 46 | let frontToBackWindows = Array(UIApplication.shared.windows.reversed()) 47 | 48 | for window in frontToBackWindows { 49 | if window.windowLevel == UIWindow.Level.normal { 50 | currentWindow = window 51 | break 52 | } 53 | } 54 | } 55 | 56 | return currentWindow 57 | } 58 | 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /DropDown/resources/DropDownCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /DropDown/src/DropDown+Appearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropDown+Appearance.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 13/06/16. 6 | // Copyright © 2016 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | extension DropDown { 14 | 15 | public class func setupDefaultAppearance() { 16 | let appearance = DropDown.appearance() 17 | 18 | appearance.cellHeight = DPDConstant.UI.RowHeight 19 | appearance.backgroundColor = DPDConstant.UI.BackgroundColor 20 | appearance.selectionBackgroundColor = DPDConstant.UI.SelectionBackgroundColor 21 | appearance.separatorColor = DPDConstant.UI.SeparatorColor 22 | appearance.cornerRadius = DPDConstant.UI.CornerRadius 23 | appearance.shadowColor = DPDConstant.UI.Shadow.Color 24 | appearance.shadowOffset = DPDConstant.UI.Shadow.Offset 25 | appearance.shadowOpacity = DPDConstant.UI.Shadow.Opacity 26 | appearance.shadowRadius = DPDConstant.UI.Shadow.Radius 27 | appearance.animationduration = DPDConstant.Animation.Duration 28 | appearance.textColor = DPDConstant.UI.TextColor 29 | appearance.selectedTextColor = DPDConstant.UI.SelectedTextColor 30 | appearance.textFont = DPDConstant.UI.TextFont 31 | } 32 | 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /DropDown/src/DropDown.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropDown.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | public typealias Index = Int 14 | public typealias Closure = () -> Void 15 | public typealias SelectionClosure = (Index, String) -> Void 16 | public typealias MultiSelectionClosure = ([Index], [String]) -> Void 17 | public typealias ConfigurationClosure = (Index, String) -> String 18 | public typealias CellConfigurationClosure = (Index, String, DropDownCell) -> Void 19 | private typealias ComputeLayoutTuple = (x: CGFloat, y: CGFloat, width: CGFloat, offscreenHeight: CGFloat) 20 | 21 | /// Can be `UIView` or `UIBarButtonItem`. 22 | @objc 23 | public protocol AnchorView: class { 24 | 25 | var plainView: UIView { get } 26 | 27 | } 28 | 29 | extension UIView: AnchorView { 30 | 31 | public var plainView: UIView { 32 | return self 33 | } 34 | 35 | } 36 | 37 | extension UIBarButtonItem: AnchorView { 38 | 39 | public var plainView: UIView { 40 | return value(forKey: "view") as! UIView 41 | } 42 | 43 | } 44 | 45 | /// A Material Design drop down in replacement for `UIPickerView`. 46 | public final class DropDown: UIView { 47 | 48 | //TODO: handle iOS 7 landscape mode 49 | 50 | /// The dismiss mode for a drop down. 51 | public enum DismissMode { 52 | 53 | /// A tap outside the drop down is required to dismiss. 54 | case onTap 55 | 56 | /// No tap is required to dismiss, it will dimiss when interacting with anything else. 57 | case automatic 58 | 59 | /// Not dismissable by the user. 60 | case manual 61 | 62 | } 63 | 64 | /// The direction where the drop down will show from the `anchorView`. 65 | public enum Direction { 66 | 67 | /// The drop down will show below the anchor view when possible, otherwise above if there is more place than below. 68 | case any 69 | 70 | /// The drop down will show above the anchor view or will not be showed if not enough space. 71 | case top 72 | 73 | /// The drop down will show below or will not be showed if not enough space. 74 | case bottom 75 | 76 | } 77 | 78 | //MARK: - Properties 79 | 80 | /// The current visible drop down. There can be only one visible drop down at a time. 81 | public static weak var VisibleDropDown: DropDown? 82 | 83 | //MARK: UI 84 | fileprivate let dismissableView = UIView() 85 | fileprivate let tableViewContainer = UIView() 86 | fileprivate let tableView = UITableView() 87 | fileprivate var templateCell: DropDownCell! 88 | fileprivate lazy var arrowIndication: UIImageView = { 89 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 20, height: 10), false, 0) 90 | let path = UIBezierPath() 91 | path.move(to: CGPoint(x: 0, y: 10)) 92 | path.addLine(to: CGPoint(x: 20, y: 10)) 93 | path.addLine(to: CGPoint(x: 10, y: 0)) 94 | path.addLine(to: CGPoint(x: 0, y: 10)) 95 | UIColor.black.setFill() 96 | path.fill() 97 | let img = UIGraphicsGetImageFromCurrentImageContext() 98 | UIGraphicsEndImageContext() 99 | let tintImg = img?.withRenderingMode(.alwaysTemplate) 100 | let imgv = UIImageView(image: tintImg) 101 | imgv.frame = CGRect(x: 0, y: -10, width: 15, height: 10) 102 | return imgv 103 | }() 104 | 105 | 106 | /// The view to which the drop down will displayed onto. 107 | public weak var anchorView: AnchorView? { 108 | didSet { setNeedsUpdateConstraints() } 109 | } 110 | 111 | /** 112 | The possible directions where the drop down will be showed. 113 | 114 | See `Direction` enum for more info. 115 | */ 116 | public var direction = Direction.any 117 | 118 | /** 119 | The offset point relative to `anchorView` when the drop down is shown above the anchor view. 120 | 121 | By default, the drop down is showed onto the `anchorView` with the top 122 | left corner for its origin, so an offset equal to (0, 0). 123 | You can change here the default drop down origin. 124 | */ 125 | public var topOffset: CGPoint = .zero { 126 | didSet { setNeedsUpdateConstraints() } 127 | } 128 | 129 | /** 130 | The offset point relative to `anchorView` when the drop down is shown below the anchor view. 131 | 132 | By default, the drop down is showed onto the `anchorView` with the top 133 | left corner for its origin, so an offset equal to (0, 0). 134 | You can change here the default drop down origin. 135 | */ 136 | public var bottomOffset: CGPoint = .zero { 137 | didSet { setNeedsUpdateConstraints() } 138 | } 139 | 140 | /** 141 | The offset from the bottom of the window when the drop down is shown below the anchor view. 142 | DropDown applies this offset only if keyboard is hidden. 143 | */ 144 | public var offsetFromWindowBottom = CGFloat(0) { 145 | didSet { setNeedsUpdateConstraints() } 146 | } 147 | 148 | /** 149 | The width of the drop down. 150 | 151 | Defaults to `anchorView.bounds.width - offset.x`. 152 | */ 153 | public var width: CGFloat? { 154 | didSet { setNeedsUpdateConstraints() } 155 | } 156 | 157 | /** 158 | arrowIndication.x 159 | 160 | arrowIndication will be add to tableViewContainer when configured 161 | */ 162 | public var arrowIndicationX: CGFloat? { 163 | didSet { 164 | if let arrowIndicationX = arrowIndicationX { 165 | tableViewContainer.addSubview(arrowIndication) 166 | arrowIndication.tintColor = tableViewBackgroundColor 167 | arrowIndication.frame.origin.x = arrowIndicationX 168 | } else { 169 | arrowIndication.removeFromSuperview() 170 | } 171 | } 172 | } 173 | 174 | //MARK: Constraints 175 | fileprivate var heightConstraint: NSLayoutConstraint! 176 | fileprivate var widthConstraint: NSLayoutConstraint! 177 | fileprivate var xConstraint: NSLayoutConstraint! 178 | fileprivate var yConstraint: NSLayoutConstraint! 179 | 180 | //MARK: Appearance 181 | @objc public dynamic var cellHeight = DPDConstant.UI.RowHeight { 182 | willSet { tableView.rowHeight = newValue } 183 | didSet { reloadAllComponents() } 184 | } 185 | 186 | @objc fileprivate dynamic var tableViewBackgroundColor = DPDConstant.UI.BackgroundColor { 187 | willSet { 188 | tableView.backgroundColor = newValue 189 | if arrowIndicationX != nil { arrowIndication.tintColor = newValue } 190 | } 191 | } 192 | 193 | public override var backgroundColor: UIColor? { 194 | get { return tableViewBackgroundColor } 195 | set { tableViewBackgroundColor = newValue! } 196 | } 197 | 198 | /** 199 | The color of the dimmed background (behind the drop down, covering the entire screen). 200 | */ 201 | public var dimmedBackgroundColor = UIColor.clear { 202 | willSet { super.backgroundColor = newValue } 203 | } 204 | 205 | /** 206 | The background color of the selected cell in the drop down. 207 | 208 | Changing the background color automatically reloads the drop down. 209 | */ 210 | @objc public dynamic var selectionBackgroundColor = DPDConstant.UI.SelectionBackgroundColor 211 | 212 | /** 213 | The separator color between cells. 214 | 215 | Changing the separator color automatically reloads the drop down. 216 | */ 217 | @objc public dynamic var separatorColor = DPDConstant.UI.SeparatorColor { 218 | willSet { tableView.separatorColor = newValue } 219 | didSet { reloadAllComponents() } 220 | } 221 | 222 | /** 223 | The corner radius of DropDown. 224 | 225 | Changing the corner radius automatically reloads the drop down. 226 | */ 227 | @objc public dynamic var cornerRadius = DPDConstant.UI.CornerRadius { 228 | willSet { 229 | tableViewContainer.layer.cornerRadius = newValue 230 | tableView.layer.cornerRadius = newValue 231 | } 232 | didSet { reloadAllComponents() } 233 | } 234 | 235 | /** 236 | Alias method for `cornerRadius` variable to avoid ambiguity. 237 | */ 238 | @objc public dynamic func setupCornerRadius(_ radius: CGFloat) { 239 | tableViewContainer.layer.cornerRadius = radius 240 | tableView.layer.cornerRadius = radius 241 | reloadAllComponents() 242 | } 243 | 244 | /** 245 | The masked corners of DropDown. 246 | 247 | Changing the masked corners automatically reloads the drop down. 248 | */ 249 | @available(iOS 11.0, *) 250 | @objc public dynamic func setupMaskedCorners(_ cornerMask: CACornerMask) { 251 | tableViewContainer.layer.maskedCorners = cornerMask 252 | tableView.layer.maskedCorners = cornerMask 253 | reloadAllComponents() 254 | } 255 | 256 | /** 257 | The color of the shadow. 258 | 259 | Changing the shadow color automatically reloads the drop down. 260 | */ 261 | @objc public dynamic var shadowColor = DPDConstant.UI.Shadow.Color { 262 | willSet { tableViewContainer.layer.shadowColor = newValue.cgColor } 263 | didSet { reloadAllComponents() } 264 | } 265 | 266 | /** 267 | The offset of the shadow. 268 | 269 | Changing the shadow color automatically reloads the drop down. 270 | */ 271 | @objc public dynamic var shadowOffset = DPDConstant.UI.Shadow.Offset { 272 | willSet { tableViewContainer.layer.shadowOffset = newValue } 273 | didSet { reloadAllComponents() } 274 | } 275 | 276 | /** 277 | The opacity of the shadow. 278 | 279 | Changing the shadow opacity automatically reloads the drop down. 280 | */ 281 | @objc public dynamic var shadowOpacity = DPDConstant.UI.Shadow.Opacity { 282 | willSet { tableViewContainer.layer.shadowOpacity = newValue } 283 | didSet { reloadAllComponents() } 284 | } 285 | 286 | /** 287 | The radius of the shadow. 288 | 289 | Changing the shadow radius automatically reloads the drop down. 290 | */ 291 | @objc public dynamic var shadowRadius = DPDConstant.UI.Shadow.Radius { 292 | willSet { tableViewContainer.layer.shadowRadius = newValue } 293 | didSet { reloadAllComponents() } 294 | } 295 | 296 | /** 297 | The duration of the show/hide animation. 298 | */ 299 | @objc public dynamic var animationduration = DPDConstant.Animation.Duration 300 | 301 | /** 302 | The option of the show animation. Global change. 303 | */ 304 | public static var animationEntranceOptions = DPDConstant.Animation.EntranceOptions 305 | 306 | /** 307 | The option of the hide animation. Global change. 308 | */ 309 | public static var animationExitOptions = DPDConstant.Animation.ExitOptions 310 | 311 | /** 312 | The option of the show animation. Only change the caller. To change all drop down's use the static var. 313 | */ 314 | public var animationEntranceOptions: UIView.AnimationOptions = DropDown.animationEntranceOptions 315 | 316 | /** 317 | The option of the hide animation. Only change the caller. To change all drop down's use the static var. 318 | */ 319 | public var animationExitOptions: UIView.AnimationOptions = DropDown.animationExitOptions 320 | 321 | /** 322 | The downScale transformation of the tableview when the DropDown is appearing 323 | */ 324 | public var downScaleTransform = DPDConstant.Animation.DownScaleTransform { 325 | willSet { tableViewContainer.transform = newValue } 326 | } 327 | 328 | /** 329 | The color of the text for each cells of the drop down. 330 | 331 | Changing the text color automatically reloads the drop down. 332 | */ 333 | @objc public dynamic var textColor = DPDConstant.UI.TextColor { 334 | didSet { reloadAllComponents() } 335 | } 336 | 337 | /** 338 | The color of the text for selected cells of the drop down. 339 | 340 | Changing the text color automatically reloads the drop down. 341 | */ 342 | @objc public dynamic var selectedTextColor = DPDConstant.UI.SelectedTextColor { 343 | didSet { reloadAllComponents() } 344 | } 345 | 346 | /** 347 | The font of the text for each cells of the drop down. 348 | 349 | Changing the text font automatically reloads the drop down. 350 | */ 351 | @objc public dynamic var textFont = DPDConstant.UI.TextFont { 352 | didSet { reloadAllComponents() } 353 | } 354 | 355 | /** 356 | The NIB to use for DropDownCells 357 | 358 | Changing the cell nib automatically reloads the drop down. 359 | */ 360 | public var cellNib = UINib(nibName: "DropDownCell", bundle: bundle) { 361 | didSet { 362 | tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell) 363 | templateCell = nil 364 | reloadAllComponents() 365 | } 366 | } 367 | 368 | /// Correctly specify Bundle for Swift Packages 369 | fileprivate static var bundle: Bundle { 370 | #if SWIFT_PACKAGE 371 | return Bundle.module 372 | #else 373 | return Bundle(for: DropDownCell.self) 374 | #endif 375 | } 376 | 377 | //MARK: Content 378 | 379 | /** 380 | The data source for the drop down. 381 | 382 | Changing the data source automatically reloads the drop down. 383 | */ 384 | public var dataSource = [String]() { 385 | didSet { 386 | deselectRows(at: selectedRowIndices) 387 | reloadAllComponents() 388 | } 389 | } 390 | 391 | /** 392 | The localization keys for the data source for the drop down. 393 | 394 | Changing this value automatically reloads the drop down. 395 | This has uses for setting accibility identifiers on the drop down cells (same ones as the localization keys). 396 | */ 397 | public var localizationKeysDataSource = [String]() { 398 | didSet { 399 | dataSource = localizationKeysDataSource.map { NSLocalizedString($0, comment: "") } 400 | } 401 | } 402 | 403 | /// The indicies that have been selected 404 | fileprivate var selectedRowIndices = Set() 405 | 406 | /** 407 | The format for the cells' text. 408 | 409 | By default, the cell's text takes the plain `dataSource` value. 410 | Changing `cellConfiguration` automatically reloads the drop down. 411 | */ 412 | public var cellConfiguration: ConfigurationClosure? { 413 | didSet { reloadAllComponents() } 414 | } 415 | 416 | /** 417 | A advanced formatter for the cells. Allows customization when custom cells are used 418 | 419 | Changing `customCellConfiguration` automatically reloads the drop down. 420 | */ 421 | public var customCellConfiguration: CellConfigurationClosure? { 422 | didSet { reloadAllComponents() } 423 | } 424 | 425 | /// The action to execute when the user selects a cell. 426 | public var selectionAction: SelectionClosure? 427 | 428 | /** 429 | The action to execute when the user selects multiple cells. 430 | 431 | Providing an action will turn on multiselection mode. 432 | The single selection action will still be called if provided. 433 | */ 434 | public var multiSelectionAction: MultiSelectionClosure? 435 | 436 | /// The action to execute when the drop down will show. 437 | public var willShowAction: Closure? 438 | 439 | /// The action to execute when the user cancels/hides the drop down. 440 | public var cancelAction: Closure? 441 | 442 | /// The dismiss mode of the drop down. Default is `OnTap`. 443 | public var dismissMode = DismissMode.onTap { 444 | willSet { 445 | if newValue == .onTap { 446 | let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissableViewTapped)) 447 | dismissableView.addGestureRecognizer(gestureRecognizer) 448 | } else if let gestureRecognizer = dismissableView.gestureRecognizers?.first { 449 | dismissableView.removeGestureRecognizer(gestureRecognizer) 450 | } 451 | } 452 | } 453 | 454 | fileprivate var minHeight: CGFloat { 455 | return tableView.rowHeight 456 | } 457 | 458 | fileprivate var didSetupConstraints = false 459 | 460 | //MARK: - Init's 461 | 462 | deinit { 463 | stopListeningToNotifications() 464 | } 465 | 466 | /** 467 | Creates a new instance of a drop down. 468 | Don't forget to setup the `dataSource`, 469 | the `anchorView` and the `selectionAction` 470 | at least before calling `show()`. 471 | */ 472 | public convenience init() { 473 | self.init(frame: .zero) 474 | } 475 | 476 | /** 477 | Creates a new instance of a drop down. 478 | 479 | - parameter anchorView: The view to which the drop down will displayed onto. 480 | - parameter selectionAction: The action to execute when the user selects a cell. 481 | - parameter dataSource: The data source for the drop down. 482 | - parameter topOffset: The offset point relative to `anchorView` used when drop down is displayed on above the anchor view. 483 | - parameter bottomOffset: The offset point relative to `anchorView` used when drop down is displayed on below the anchor view. 484 | - parameter cellConfiguration: The format for the cells' text. 485 | - parameter cancelAction: The action to execute when the user cancels/hides the drop down. 486 | 487 | - returns: A new instance of a drop down customized with the above parameters. 488 | */ 489 | public convenience init(anchorView: AnchorView, selectionAction: SelectionClosure? = nil, dataSource: [String] = [], topOffset: CGPoint? = nil, bottomOffset: CGPoint? = nil, cellConfiguration: ConfigurationClosure? = nil, cancelAction: Closure? = nil) { 490 | self.init(frame: .zero) 491 | 492 | self.anchorView = anchorView 493 | self.selectionAction = selectionAction 494 | self.dataSource = dataSource 495 | self.topOffset = topOffset ?? .zero 496 | self.bottomOffset = bottomOffset ?? .zero 497 | self.cellConfiguration = cellConfiguration 498 | self.cancelAction = cancelAction 499 | } 500 | 501 | override public init(frame: CGRect) { 502 | super.init(frame: frame) 503 | setup() 504 | } 505 | 506 | public required init?(coder aDecoder: NSCoder) { 507 | super.init(coder: aDecoder) 508 | setup() 509 | } 510 | 511 | } 512 | 513 | //MARK: - Setup 514 | 515 | private extension DropDown { 516 | 517 | func setup() { 518 | tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell) 519 | 520 | DispatchQueue.main.async { 521 | //HACK: If not done in dispatch_async on main queue `setupUI` will have no effect 522 | self.updateConstraintsIfNeeded() 523 | self.setupUI() 524 | } 525 | 526 | tableView.rowHeight = cellHeight 527 | setHiddentState() 528 | isHidden = true 529 | 530 | dismissMode = .onTap 531 | 532 | tableView.delegate = self 533 | tableView.dataSource = self 534 | 535 | startListeningToKeyboard() 536 | 537 | accessibilityIdentifier = "drop_down" 538 | } 539 | 540 | func setupUI() { 541 | super.backgroundColor = dimmedBackgroundColor 542 | 543 | tableViewContainer.layer.masksToBounds = false 544 | tableViewContainer.layer.cornerRadius = cornerRadius 545 | tableViewContainer.layer.shadowColor = shadowColor.cgColor 546 | tableViewContainer.layer.shadowOffset = shadowOffset 547 | tableViewContainer.layer.shadowOpacity = shadowOpacity 548 | tableViewContainer.layer.shadowRadius = shadowRadius 549 | 550 | tableView.backgroundColor = tableViewBackgroundColor 551 | tableView.separatorColor = separatorColor 552 | tableView.layer.cornerRadius = cornerRadius 553 | tableView.layer.masksToBounds = true 554 | } 555 | 556 | } 557 | 558 | //MARK: - UI 559 | 560 | extension DropDown { 561 | 562 | public override func updateConstraints() { 563 | if !didSetupConstraints { 564 | setupConstraints() 565 | } 566 | 567 | didSetupConstraints = true 568 | 569 | let layout = computeLayout() 570 | 571 | if !layout.canBeDisplayed { 572 | super.updateConstraints() 573 | hide() 574 | 575 | return 576 | } 577 | 578 | xConstraint.constant = layout.x 579 | yConstraint.constant = layout.y 580 | widthConstraint.constant = layout.width 581 | heightConstraint.constant = layout.visibleHeight 582 | 583 | tableView.isScrollEnabled = layout.offscreenHeight > 0 584 | 585 | DispatchQueue.main.async { [weak self] in 586 | self?.tableView.flashScrollIndicators() 587 | } 588 | 589 | super.updateConstraints() 590 | } 591 | 592 | fileprivate func setupConstraints() { 593 | translatesAutoresizingMaskIntoConstraints = false 594 | 595 | // Dismissable view 596 | addSubview(dismissableView) 597 | dismissableView.translatesAutoresizingMaskIntoConstraints = false 598 | 599 | addUniversalConstraints(format: "|[dismissableView]|", views: ["dismissableView": dismissableView]) 600 | 601 | 602 | // Table view container 603 | addSubview(tableViewContainer) 604 | tableViewContainer.translatesAutoresizingMaskIntoConstraints = false 605 | 606 | xConstraint = NSLayoutConstraint( 607 | item: tableViewContainer, 608 | attribute: .leading, 609 | relatedBy: .equal, 610 | toItem: self, 611 | attribute: .leading, 612 | multiplier: 1, 613 | constant: 0) 614 | addConstraint(xConstraint) 615 | 616 | yConstraint = NSLayoutConstraint( 617 | item: tableViewContainer, 618 | attribute: .top, 619 | relatedBy: .equal, 620 | toItem: self, 621 | attribute: .top, 622 | multiplier: 1, 623 | constant: 0) 624 | addConstraint(yConstraint) 625 | 626 | widthConstraint = NSLayoutConstraint( 627 | item: tableViewContainer, 628 | attribute: .width, 629 | relatedBy: .equal, 630 | toItem: nil, 631 | attribute: .notAnAttribute, 632 | multiplier: 1, 633 | constant: 0) 634 | tableViewContainer.addConstraint(widthConstraint) 635 | 636 | heightConstraint = NSLayoutConstraint( 637 | item: tableViewContainer, 638 | attribute: .height, 639 | relatedBy: .equal, 640 | toItem: nil, 641 | attribute: .notAnAttribute, 642 | multiplier: 1, 643 | constant: 0) 644 | tableViewContainer.addConstraint(heightConstraint) 645 | 646 | // Table view 647 | tableViewContainer.addSubview(tableView) 648 | tableView.translatesAutoresizingMaskIntoConstraints = false 649 | 650 | tableViewContainer.addUniversalConstraints(format: "|[tableView]|", views: ["tableView": tableView]) 651 | } 652 | 653 | public override func layoutSubviews() { 654 | super.layoutSubviews() 655 | 656 | // When orientation changes, layoutSubviews is called 657 | // We update the constraint to update the position 658 | setNeedsUpdateConstraints() 659 | 660 | let shadowPath = UIBezierPath(roundedRect: tableViewContainer.bounds, cornerRadius: cornerRadius) 661 | tableViewContainer.layer.shadowPath = shadowPath.cgPath 662 | } 663 | 664 | fileprivate func computeLayout() -> (x: CGFloat, y: CGFloat, width: CGFloat, offscreenHeight: CGFloat, visibleHeight: CGFloat, canBeDisplayed: Bool, Direction: Direction) { 665 | var layout: ComputeLayoutTuple = (0, 0, 0, 0) 666 | var direction = self.direction 667 | 668 | guard let window = UIWindow.visibleWindow() else { return (0, 0, 0, 0, 0, false, direction) } 669 | 670 | barButtonItemCondition: if let anchorView = anchorView as? UIBarButtonItem { 671 | let isRightBarButtonItem = anchorView.plainView.frame.minX > window.frame.midX 672 | 673 | guard isRightBarButtonItem else { break barButtonItemCondition } 674 | 675 | let width = self.width ?? fittingWidth() 676 | let anchorViewWidth = anchorView.plainView.frame.width 677 | let x = -(width - anchorViewWidth) 678 | 679 | bottomOffset = CGPoint(x: x, y: 0) 680 | } 681 | 682 | if anchorView == nil { 683 | layout = computeLayoutBottomDisplay(window: window) 684 | direction = .any 685 | } else { 686 | switch direction { 687 | case .any: 688 | layout = computeLayoutBottomDisplay(window: window) 689 | direction = .bottom 690 | 691 | if layout.offscreenHeight > 0 { 692 | let topLayout = computeLayoutForTopDisplay(window: window) 693 | 694 | if topLayout.offscreenHeight < layout.offscreenHeight { 695 | layout = topLayout 696 | direction = .top 697 | } 698 | } 699 | case .bottom: 700 | layout = computeLayoutBottomDisplay(window: window) 701 | direction = .bottom 702 | case .top: 703 | layout = computeLayoutForTopDisplay(window: window) 704 | direction = .top 705 | } 706 | } 707 | 708 | constraintWidthToFittingSizeIfNecessary(layout: &layout) 709 | constraintWidthToBoundsIfNecessary(layout: &layout, in: window) 710 | 711 | let visibleHeight = tableHeight - layout.offscreenHeight 712 | let canBeDisplayed = visibleHeight >= minHeight 713 | 714 | return (layout.x, layout.y, layout.width, layout.offscreenHeight, visibleHeight, canBeDisplayed, direction) 715 | } 716 | 717 | fileprivate func computeLayoutBottomDisplay(window: UIWindow) -> ComputeLayoutTuple { 718 | var offscreenHeight: CGFloat = 0 719 | 720 | let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - bottomOffset.x 721 | 722 | let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? window.frame.midX - (width / 2) 723 | let anchorViewY = anchorView?.plainView.windowFrame?.minY ?? window.frame.midY - (tableHeight / 2) 724 | 725 | let x = anchorViewX + bottomOffset.x 726 | let y = anchorViewY + bottomOffset.y 727 | 728 | let maxY = y + tableHeight 729 | let windowMaxY = window.bounds.maxY - DPDConstant.UI.HeightPadding - offsetFromWindowBottom 730 | 731 | let keyboardListener = KeyboardListener.sharedInstance 732 | let keyboardMinY = keyboardListener.keyboardFrame.minY - DPDConstant.UI.HeightPadding 733 | 734 | if keyboardListener.isVisible && maxY > keyboardMinY { 735 | offscreenHeight = abs(maxY - keyboardMinY) 736 | } else if maxY > windowMaxY { 737 | offscreenHeight = abs(maxY - windowMaxY) 738 | } 739 | 740 | return (x, y, width, offscreenHeight) 741 | } 742 | 743 | fileprivate func computeLayoutForTopDisplay(window: UIWindow) -> ComputeLayoutTuple { 744 | var offscreenHeight: CGFloat = 0 745 | 746 | let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? 0 747 | let anchorViewMaxY = anchorView?.plainView.windowFrame?.maxY ?? 0 748 | 749 | let x = anchorViewX + topOffset.x 750 | var y = (anchorViewMaxY + topOffset.y) - tableHeight 751 | 752 | let windowY = window.bounds.minY + DPDConstant.UI.HeightPadding 753 | 754 | if y < windowY { 755 | offscreenHeight = abs(y - windowY) 756 | y = windowY 757 | } 758 | 759 | let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - topOffset.x 760 | 761 | return (x, y, width, offscreenHeight) 762 | } 763 | 764 | fileprivate func fittingWidth() -> CGFloat { 765 | if templateCell == nil { 766 | templateCell = (cellNib.instantiate(withOwner: nil, options: nil)[0] as! DropDownCell) 767 | } 768 | 769 | var maxWidth: CGFloat = 0 770 | 771 | for index in 0.. maxWidth { 777 | maxWidth = width 778 | } 779 | } 780 | 781 | return maxWidth 782 | } 783 | 784 | fileprivate func constraintWidthToBoundsIfNecessary(layout: inout ComputeLayoutTuple, in window: UIWindow) { 785 | let windowMaxX = window.bounds.maxX 786 | let maxX = layout.x + layout.width 787 | 788 | if maxX > windowMaxX { 789 | let delta = maxX - windowMaxX 790 | let newOrigin = layout.x - delta 791 | 792 | if newOrigin > 0 { 793 | layout.x = newOrigin 794 | } else { 795 | layout.x = 0 796 | layout.width += newOrigin // newOrigin is negative, so this operation is a substraction 797 | } 798 | } 799 | } 800 | 801 | fileprivate func constraintWidthToFittingSizeIfNecessary(layout: inout ComputeLayoutTuple) { 802 | guard width == nil else { return } 803 | 804 | if layout.width < fittingWidth() { 805 | layout.width = fittingWidth() 806 | } 807 | } 808 | 809 | } 810 | 811 | //MARK: - Actions 812 | 813 | extension DropDown { 814 | 815 | /** 816 | An Objective-C alias for the show() method which converts the returned tuple into an NSDictionary. 817 | 818 | - returns: An NSDictionary with a value for the "canBeDisplayed" Bool, and possibly for the "offScreenHeight" Optional(CGFloat). 819 | */ 820 | @objc(show) 821 | public func objc_show() -> NSDictionary { 822 | let (canBeDisplayed, offScreenHeight) = show() 823 | 824 | var info = [AnyHashable: Any]() 825 | info["canBeDisplayed"] = canBeDisplayed 826 | if let offScreenHeight = offScreenHeight { 827 | info["offScreenHeight"] = offScreenHeight 828 | } 829 | 830 | return NSDictionary(dictionary: info) 831 | } 832 | 833 | /** 834 | Shows the drop down if enough height. 835 | 836 | - returns: Wether it succeed and how much height is needed to display all cells at once. 837 | */ 838 | @discardableResult 839 | public func show(onTopOf window: UIWindow? = nil, beforeTransform transform: CGAffineTransform? = nil, anchorPoint: CGPoint? = nil) -> (canBeDisplayed: Bool, offscreenHeight: CGFloat?) { 840 | if self == DropDown.VisibleDropDown && DropDown.VisibleDropDown?.isHidden == false { // added condition - DropDown.VisibleDropDown?.isHidden == false -> to resolve forever hiding dropdown issue when continuous taping on button - Kartik Patel - 2016-12-29 841 | return (true, 0) 842 | } 843 | 844 | if let visibleDropDown = DropDown.VisibleDropDown { 845 | visibleDropDown.cancel() 846 | } 847 | 848 | willShowAction?() 849 | 850 | DropDown.VisibleDropDown = self 851 | 852 | setNeedsUpdateConstraints() 853 | 854 | let visibleWindow = window != nil ? window : UIWindow.visibleWindow() 855 | visibleWindow?.addSubview(self) 856 | visibleWindow?.bringSubviewToFront(self) 857 | 858 | self.translatesAutoresizingMaskIntoConstraints = false 859 | visibleWindow?.addUniversalConstraints(format: "|[dropDown]|", views: ["dropDown": self]) 860 | 861 | let layout = computeLayout() 862 | 863 | if !layout.canBeDisplayed { 864 | hide() 865 | return (layout.canBeDisplayed, layout.offscreenHeight) 866 | } 867 | 868 | isHidden = false 869 | 870 | if anchorPoint != nil { 871 | tableViewContainer.layer.anchorPoint = anchorPoint! 872 | } 873 | 874 | if transform != nil { 875 | tableViewContainer.transform = transform! 876 | } else { 877 | tableViewContainer.transform = downScaleTransform 878 | } 879 | 880 | layoutIfNeeded() 881 | 882 | UIView.animate( 883 | withDuration: animationduration, 884 | delay: 0, 885 | options: animationEntranceOptions, 886 | animations: { [weak self] in 887 | self?.setShowedState() 888 | }, 889 | completion: nil) 890 | 891 | accessibilityViewIsModal = true 892 | UIAccessibility.post(notification: .screenChanged, argument: self) 893 | 894 | //deselectRows(at: selectedRowIndices) 895 | selectRows(at: selectedRowIndices) 896 | 897 | return (layout.canBeDisplayed, layout.offscreenHeight) 898 | } 899 | 900 | public override func accessibilityPerformEscape() -> Bool { 901 | switch dismissMode { 902 | case .automatic, .onTap: 903 | cancel() 904 | return true 905 | case .manual: 906 | return false 907 | } 908 | } 909 | 910 | /// Hides the drop down. 911 | public func hide() { 912 | if self == DropDown.VisibleDropDown { 913 | /* 914 | If one drop down is showed and another one is not 915 | but we call `hide()` on the hidden one: 916 | we don't want it to set the `VisibleDropDown` to nil. 917 | */ 918 | DropDown.VisibleDropDown = nil 919 | } 920 | 921 | if isHidden { 922 | return 923 | } 924 | 925 | UIView.animate( 926 | withDuration: animationduration, 927 | delay: 0, 928 | options: animationExitOptions, 929 | animations: { [weak self] in 930 | self?.setHiddentState() 931 | }, 932 | completion: { [weak self] finished in 933 | guard let `self` = self else { return } 934 | 935 | self.isHidden = true 936 | self.removeFromSuperview() 937 | UIAccessibility.post(notification: .screenChanged, argument: nil) 938 | }) 939 | } 940 | 941 | fileprivate func cancel() { 942 | hide() 943 | cancelAction?() 944 | } 945 | 946 | fileprivate func setHiddentState() { 947 | alpha = 0 948 | } 949 | 950 | fileprivate func setShowedState() { 951 | alpha = 1 952 | tableViewContainer.transform = CGAffineTransform.identity 953 | } 954 | 955 | } 956 | 957 | //MARK: - UITableView 958 | 959 | extension DropDown { 960 | 961 | /** 962 | Reloads all the cells. 963 | 964 | It should not be necessary in most cases because each change to 965 | `dataSource`, `textColor`, `textFont`, `selectionBackgroundColor` 966 | and `cellConfiguration` implicitly calls `reloadAllComponents()`. 967 | */ 968 | public func reloadAllComponents() { 969 | DispatchQueue.executeOnMainThread { 970 | self.tableView.reloadData() 971 | self.setNeedsUpdateConstraints() 972 | } 973 | } 974 | 975 | /// (Pre)selects a row at a certain index. 976 | public func selectRow(at index: Index?, scrollPosition: UITableView.ScrollPosition = .none) { 977 | if let index = index { 978 | tableView.selectRow( 979 | at: IndexPath(row: index, section: 0), animated: true, scrollPosition: scrollPosition 980 | ) 981 | selectedRowIndices.insert(index) 982 | } else { 983 | deselectRows(at: selectedRowIndices) 984 | selectedRowIndices.removeAll() 985 | } 986 | } 987 | 988 | public func selectRows(at indices: Set?) { 989 | indices?.forEach { 990 | selectRow(at: $0) 991 | } 992 | 993 | // if we are in multi selection mode then reload data so that all selections are shown 994 | if multiSelectionAction != nil { 995 | tableView.reloadData() 996 | } 997 | } 998 | 999 | public func deselectRow(at index: Index?) { 1000 | guard let index = index 1001 | , index >= 0 1002 | else { return } 1003 | 1004 | // remove from indices 1005 | if let selectedRowIndex = selectedRowIndices.firstIndex(where: { $0 == index }) { 1006 | selectedRowIndices.remove(at: selectedRowIndex) 1007 | } 1008 | 1009 | tableView.deselectRow(at: IndexPath(row: index, section: 0), animated: true) 1010 | } 1011 | 1012 | // de-selects the rows at the indices provided 1013 | public func deselectRows(at indices: Set?) { 1014 | indices?.forEach { 1015 | deselectRow(at: $0) 1016 | } 1017 | } 1018 | 1019 | /// Returns the index of the selected row. 1020 | public var indexForSelectedRow: Index? { 1021 | return (tableView.indexPathForSelectedRow as NSIndexPath?)?.row 1022 | } 1023 | 1024 | /// Returns the selected item. 1025 | public var selectedItem: String? { 1026 | guard let row = (tableView.indexPathForSelectedRow as NSIndexPath?)?.row else { return nil } 1027 | 1028 | return dataSource[row] 1029 | } 1030 | 1031 | /// Returns the height needed to display all cells. 1032 | fileprivate var tableHeight: CGFloat { 1033 | return tableView.rowHeight * CGFloat(dataSource.count) 1034 | } 1035 | 1036 | //MARK: Objective-C methods for converting the Swift type Index 1037 | @objc public func selectRow(_ index: Int, scrollPosition: UITableView.ScrollPosition = .none) { 1038 | self.selectRow(at:Index(index), scrollPosition: scrollPosition) 1039 | } 1040 | 1041 | @objc public func clearSelection() { 1042 | self.selectRow(at:nil) 1043 | } 1044 | 1045 | @objc public func deselectRow(_ index: Int) { 1046 | tableView.deselectRow(at: IndexPath(row: Index(index), section: 0), animated: true) 1047 | } 1048 | 1049 | @objc public var indexPathForSelectedRow: NSIndexPath? { 1050 | return tableView.indexPathForSelectedRow as NSIndexPath? 1051 | } 1052 | } 1053 | 1054 | //MARK: - UITableViewDataSource - UITableViewDelegate 1055 | 1056 | extension DropDown: UITableViewDataSource, UITableViewDelegate { 1057 | 1058 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1059 | return dataSource.count 1060 | } 1061 | 1062 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 1063 | let cell = tableView.dequeueReusableCell(withIdentifier: DPDConstant.ReusableIdentifier.DropDownCell, for: indexPath) as! DropDownCell 1064 | let index = (indexPath as NSIndexPath).row 1065 | 1066 | configureCell(cell, at: index) 1067 | 1068 | return cell 1069 | } 1070 | 1071 | fileprivate func configureCell(_ cell: DropDownCell, at index: Int) { 1072 | if index >= 0 && index < localizationKeysDataSource.count { 1073 | cell.accessibilityIdentifier = localizationKeysDataSource[index] 1074 | } 1075 | 1076 | cell.optionLabel.textColor = textColor 1077 | cell.optionLabel.font = textFont 1078 | cell.selectedBackgroundColor = selectionBackgroundColor 1079 | cell.highlightTextColor = selectedTextColor 1080 | cell.normalTextColor = textColor 1081 | 1082 | if let cellConfiguration = cellConfiguration { 1083 | cell.optionLabel.text = cellConfiguration(index, dataSource[index]) 1084 | } else { 1085 | cell.optionLabel.text = dataSource[index] 1086 | } 1087 | 1088 | customCellConfiguration?(index, dataSource[index], cell) 1089 | } 1090 | 1091 | public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 1092 | cell.isSelected = selectedRowIndices.first{ $0 == (indexPath as NSIndexPath).row } != nil 1093 | } 1094 | 1095 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 1096 | let selectedRowIndex = (indexPath as NSIndexPath).row 1097 | 1098 | 1099 | // are we in multi-selection mode? 1100 | if let multiSelectionCallback = multiSelectionAction { 1101 | // if already selected then deselect 1102 | if selectedRowIndices.first(where: { $0 == selectedRowIndex}) != nil { 1103 | deselectRow(at: selectedRowIndex) 1104 | 1105 | let selectedRowIndicesArray = Array(selectedRowIndices) 1106 | let selectedRows = selectedRowIndicesArray.map { dataSource[$0] } 1107 | multiSelectionCallback(selectedRowIndicesArray, selectedRows) 1108 | return 1109 | } 1110 | else { 1111 | selectedRowIndices.insert(selectedRowIndex) 1112 | 1113 | let selectedRowIndicesArray = Array(selectedRowIndices) 1114 | let selectedRows = selectedRowIndicesArray.map { dataSource[$0] } 1115 | 1116 | selectionAction?(selectedRowIndex, dataSource[selectedRowIndex]) 1117 | multiSelectionCallback(selectedRowIndicesArray, selectedRows) 1118 | tableView.reloadData() 1119 | return 1120 | } 1121 | } 1122 | 1123 | // Perform single selection logic 1124 | selectedRowIndices.removeAll() 1125 | selectedRowIndices.insert(selectedRowIndex) 1126 | selectionAction?(selectedRowIndex, dataSource[selectedRowIndex]) 1127 | 1128 | if let _ = anchorView as? UIBarButtonItem { 1129 | // DropDown's from UIBarButtonItem are menus so we deselect the selected menu right after selection 1130 | deselectRow(at: selectedRowIndex) 1131 | } 1132 | 1133 | hide() 1134 | 1135 | } 1136 | 1137 | } 1138 | 1139 | //MARK: - Auto dismiss 1140 | 1141 | extension DropDown { 1142 | 1143 | public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 1144 | let view = super.hitTest(point, with: event) 1145 | 1146 | if dismissMode == .automatic && view === dismissableView { 1147 | cancel() 1148 | return nil 1149 | } else { 1150 | return view 1151 | } 1152 | } 1153 | 1154 | @objc 1155 | fileprivate func dismissableViewTapped() { 1156 | cancel() 1157 | } 1158 | 1159 | } 1160 | 1161 | //MARK: - Keyboard events 1162 | 1163 | extension DropDown { 1164 | 1165 | /** 1166 | Starts listening to keyboard events. 1167 | Allows the drop down to display correctly when keyboard is showed. 1168 | */ 1169 | @objc public static func startListeningToKeyboard() { 1170 | KeyboardListener.sharedInstance.startListeningToKeyboard() 1171 | } 1172 | 1173 | fileprivate func startListeningToKeyboard() { 1174 | KeyboardListener.sharedInstance.startListeningToKeyboard() 1175 | 1176 | NotificationCenter.default.addObserver( 1177 | self, 1178 | selector: #selector(keyboardUpdate), 1179 | name: UIResponder.keyboardWillShowNotification, 1180 | object: nil) 1181 | NotificationCenter.default.addObserver( 1182 | self, 1183 | selector: #selector(keyboardUpdate), 1184 | name: UIResponder.keyboardWillHideNotification, 1185 | object: nil) 1186 | } 1187 | 1188 | fileprivate func stopListeningToNotifications() { 1189 | NotificationCenter.default.removeObserver(self) 1190 | } 1191 | 1192 | @objc 1193 | fileprivate func keyboardUpdate() { 1194 | self.setNeedsUpdateConstraints() 1195 | } 1196 | 1197 | } 1198 | 1199 | private extension DispatchQueue { 1200 | static func executeOnMainThread(_ closure: @escaping Closure) { 1201 | if Thread.isMainThread { 1202 | closure() 1203 | } else { 1204 | main.async(execute: closure) 1205 | } 1206 | } 1207 | } 1208 | 1209 | #endif 1210 | -------------------------------------------------------------------------------- /DropDown/src/DropDownCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropDownCellTableViewCell.swift 3 | // DropDown 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | 11 | import UIKit 12 | 13 | open class DropDownCell: UITableViewCell { 14 | 15 | //UI 16 | @IBOutlet open weak var optionLabel: UILabel! 17 | 18 | var selectedBackgroundColor: UIColor? 19 | var highlightTextColor: UIColor? 20 | var normalTextColor: UIColor? 21 | 22 | } 23 | 24 | //MARK: - UI 25 | 26 | extension DropDownCell { 27 | 28 | override open func awakeFromNib() { 29 | super.awakeFromNib() 30 | 31 | backgroundColor = .clear 32 | } 33 | 34 | override open var isSelected: Bool { 35 | willSet { 36 | setSelected(newValue, animated: false) 37 | } 38 | } 39 | 40 | override open var isHighlighted: Bool { 41 | willSet { 42 | setSelected(newValue, animated: false) 43 | } 44 | } 45 | 46 | override open func setHighlighted(_ highlighted: Bool, animated: Bool) { 47 | setSelected(highlighted, animated: animated) 48 | } 49 | 50 | override open func setSelected(_ selected: Bool, animated: Bool) { 51 | let executeSelection: () -> Void = { [weak self] in 52 | guard let `self` = self else { return } 53 | 54 | if let selectedBackgroundColor = self.selectedBackgroundColor { 55 | if selected { 56 | self.backgroundColor = selectedBackgroundColor 57 | self.optionLabel.textColor = self.highlightTextColor 58 | } else { 59 | self.backgroundColor = .clear 60 | self.optionLabel.textColor = self.normalTextColor 61 | } 62 | } 63 | } 64 | 65 | if animated { 66 | UIView.animate(withDuration: 0.3, animations: { 67 | executeSelection() 68 | }) 69 | } else { 70 | executeSelection() 71 | } 72 | 73 | accessibilityTraits = selected ? .selected : .none 74 | } 75 | 76 | } 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /DropDownTests/DropDownTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropDownTests.swift 3 | // DropDownTests 4 | // 5 | // Created by Kevin Hirsch on 28/07/15. 6 | // Copyright (c) 2015 Kevin Hirsch. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class DropDownTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /DropDownTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDisplayName 6 | DropDown 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 kevin-hirsch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "DropDown", 6 | platforms: [ 7 | .iOS(.v9) 8 | ], 9 | products: [ 10 | .library( 11 | name: "DropDown", 12 | targets: ["DropDown"] 13 | ) 14 | ], 15 | targets: [ 16 | .target( 17 | name: "DropDown", 18 | dependencies: [], 19 | path: "DropDown", 20 | exclude: ["Info.plist", "DropDown.h"], 21 | resources: [ 22 | .process("DropDown/resources") 23 | ] 24 | ) 25 | ], 26 | swiftLanguageVersions: [.v5] 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![DropDown](Screenshots/logo.png) 2 | 3 | [![Twitter: @kevinh6113](http://img.shields.io/badge/contact-%40kevinh6113-70a1fb.svg?style=flat)](https://twitter.com/kevinh6113) 4 | [![License: MIT](http://img.shields.io/badge/license-MIT-70a1fb.svg?style=flat)](https://github.com/AssistoLab/DropDown/blob/master/README.md) 5 | [![Version](http://img.shields.io/badge/version-2.3.13-green.svg?style=flat)](https://github.com/AssistoLab/DropDown) 6 | [![Cocoapods](http://img.shields.io/badge/Cocoapods-available-green.svg?style=flat)](http://cocoadocs.org/docsets/DropDown/) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 8 | 9 | 10 | A Material Design drop down for iOS written in Swift. 11 | *** 12 | 13 | [![](Screenshots/1.png)](Screenshots/1.png) 14 | [![](Screenshots/2.png)](Screenshots/2.png) 15 | [![](Screenshots/3.png)](Screenshots/3.png) 16 | 17 | ## Demo 18 | 19 | Do `pod try DropDown` in your console and run the project to try a demo. 20 | To install [CocoaPods](http://www.cocoapods.org), run `sudo gem install cocoapods` in your console. 21 | 22 | ## Installation 📱 23 | 24 | `DropDown` supports Swift 5.0 since version `2.3.13`. 25 | `DropDown` supports Swift 4.2 since version `2.3.4`. 26 | 27 | If you need Swift 4.0, use version 2.3.2: 28 | - Manually: use tag `2.3.2` 29 | - CocoaPods: `pod 'DropDown', '2.3.2'` 30 | - Carthage: `github "AssistoLab/DropDown" == 2.3.2` 31 | 32 | ### CocoaPods 33 | 34 | Use [CocoaPods](http://www.cocoapods.org). 35 | 36 | 1. Add `pod 'DropDown'` to your *Podfile*. 37 | 2. Install the pod(s) by running `pod install`. 38 | 3. Add `import DropDown` in the .swift files where you want to use it 39 | 40 | ### Carthage 41 | 42 | Use [Carthage](https://github.com/Carthage/Carthage). 43 | 44 | 1. Create a file name `Cartfile`. 45 | 2. Add the line `github "AssistoLab/DropDown"`. 46 | 3. Run `carthage update`. 47 | 4. Drag the built `DropDown.framework` into your Xcode project. 48 | 49 | ### Source files 50 | 51 | A regular way to use DropDown in your project would be using Embedded Framework. There are two approaches, using source code and adding submodule. 52 | 53 | Add source code: 54 | 55 | 1. Download the [latest code version](http://github.com/AssistoLab/DropDown/archive/master.zip). 56 | 2. Unzip the download file, copy `DropDown` folder to your project folder 57 | 58 | Add submodule 59 | 60 | 1. In your favorite terminal, `cd` into your top-level project directory, and entering the following command: 61 | ``` bash 62 | $ git submodule add git@github.com:AssistoLab/DropDown.git 63 | ``` 64 | 65 | After you get the source code either by adding it directly or using submodule, then do the following steps: 66 | 67 | - Open `DropDown` folder, and drag `DropDown.xcodeproj` into the file navigator of your app project, under you app project. 68 | - In Xcode, navigate to the target configuration window by clicking the blue project icon, and selecting the application target under the "Targets" heading in the sidebar. 69 | - Open "Build Phases" panel in the tab bar at the top of the window, expend the "Target Dependencies" group and add `DropDown.framework` under DropDown icon in the popup window by clicking `+`. Similarly, you can also add `DropDown.framework` in "Embedded Binaries" under "General" tab. 70 | 71 | ## Basic usage ✨ 72 | 73 | ```swift 74 | let dropDown = DropDown() 75 | 76 | // The view to which the drop down will appear on 77 | dropDown.anchorView = view // UIView or UIBarButtonItem 78 | 79 | // The list of items to display. Can be changed dynamically 80 | dropDown.dataSource = ["Car", "Motorcycle", "Truck"] 81 | ``` 82 | 83 | Optional properties: 84 | 85 | ```swift 86 | // Action triggered on selection 87 | dropDown.selectionAction = { [unowned self] (index: Int, item: String) in 88 | print("Selected item: \(item) at index: \(index)") 89 | } 90 | 91 | // Will set a custom width instead of the anchor view width 92 | dropDownLeft.width = 200 93 | ``` 94 | 95 | Display actions: 96 | 97 | ```swift 98 | dropDown.show() 99 | dropDown.hide() 100 | ``` 101 | 102 | ## Important ⚠️ 103 | 104 | Don't forget to put: 105 | 106 | ```swift 107 | DropDown.startListeningToKeyboard() 108 | ``` 109 | 110 | in your `AppDelegate`'s `didFinishLaunching` method so that the drop down will handle its display with the keyboard displayed even the first time a drop down is showed. 111 | 112 | ## Advanced usage 🛠 113 | 114 | ### Direction 115 | 116 | The drop down can be shown below or above the anchor view with: 117 | ```swift 118 | dropDown.direction = .any 119 | ``` 120 | 121 | With `.any` the drop down will try to displa itself below the anchor view when possible, otherwise above if there is more place than below. 122 | You can restrict the possible directions by using `.top` or `.bottom`. 123 | 124 | ### Offset 125 | 126 | By default, the drop down will be shown onto to anchor view. It will hide it. 127 | If you need the drop down to be below your anchor view when the direction of the drop down is `.bottom`, you can precise an offset like this: 128 | 129 | ```swift 130 | // Top of drop down will be below the anchorView 131 | dropDown.bottomOffset = CGPoint(x: 0, y:(dropDown.anchorView?.plainView.bounds.height)!) 132 | ``` 133 | 134 | If you set the drop down direction to `.any` or `.top` you can also precise the offset when the drop down will shown above like this: 135 | 136 | ```swift 137 | // When drop down is displayed with `Direction.top`, it will be above the anchorView 138 | dropDown.topOffset = CGPoint(x: 0, y:-(dropDown.anchorView?.plainView.bounds.height)!) 139 | ``` 140 | *Note the minus sign here that is use to offset to the top.* 141 | 142 | ### Cell configuration 143 | 144 | #### Formatted text 145 | 146 | By default, the cells in the drop down have the `dataSource` values as text. 147 | If you want a custom formatted text for the cells, you can set `cellConfiguration` like this: 148 | 149 | ```swift 150 | dropDown.cellConfiguration = { [unowned self] (index, item) in 151 | return "- \(item) (option \(index))" 152 | } 153 | ``` 154 | 155 | #### Custom cell 156 | 157 | You can also create your own custom cell, from your .xib file. To have something like this for example: 158 |
[![](Screenshots/3.png)](Screenshots/3.png) 159 | 160 | You can check out a concrete example in the Demo inside this project (go to `ViewController.swift`, line 125). 161 | 162 | For this you have to: 163 | 164 | - Create a [`DropDownCell`](DropDown/src/DropDownCell.swift) subclass (e.g. *MyCell.swift*) 165 | ```swift 166 | class MyCell: DropDownCell { 167 | @IBOutlet weak var logoImageView: UIImageView! 168 | } 169 | ``` 170 | - Create your custom xib (e.g. *MyCell.xib*) and design your cell view in it 171 | - Link the cell in your xib to your custom class 172 | - At least have a label in your xib to link to the [`optionLabel`](DropDown/src/DropDownCell.swift#L14) `IBOutlet` in code (`optionLabel` is a property of `DropDownCell`) 173 |
[![](Screenshots/customCells/links.png)](Screenshots/customCells/links.png) 174 |
[![](Screenshots/customCells/xib.png)](Screenshots/customCells/xib.png) 175 | - Then, you simply need to do this: 176 | ```swift 177 | let dropDown = DropDown() 178 | 179 | // The view to which the drop down will appear on 180 | dropDown.anchorView = view // UIView or UIBarButtonItem 181 | 182 | // The list of items to display. Can be changed dynamically 183 | dropDown.dataSource = ["Car", "Motorcycle", "Truck"] 184 | 185 | /*** IMPORTANT PART FOR CUSTOM CELLS ***/ 186 | dropDown.cellNib = UINib(nibName: "MyCell", bundle: nil) 187 | 188 | dropDown.customCellConfiguration = { (index: Index, item: String, cell: DropDownCell) -> Void in 189 | guard let cell = cell as? MyCell else { return } 190 | 191 | // Setup your custom UI components 192 | cell.logoImageView.image = UIImage(named: "logo_\(index)") 193 | } 194 | /*** END - IMPORTANT PART FOR CUSTOM CELLS ***/ 195 | ``` 196 | - And you're good to go! 🙆 197 | 198 | For a complete example, don't hesitate to check the demo app and code. 199 | 200 | ### Events 201 | 202 | ```swift 203 | dropDown.cancelAction = { [unowned self] in 204 | println("Drop down dismissed") 205 | } 206 | 207 | dropDown.willShowAction = { [unowned self] in 208 | println("Drop down will show") 209 | } 210 | ``` 211 | 212 | ### Dismiss modes 213 | 214 | ```swift 215 | dropDown.dismissMode = .onTap 216 | ``` 217 | 218 | You have 3 dismiss mode with the `DismissMode` enum: 219 | 220 | - `onTap`: A tap oustide the drop down is needed to dismiss it (Default) 221 | - `automatic`: No tap is needed to dismiss the drop down. As soon as the user interact with anything else than the drop down, the drop down is dismissed 222 | - `manual`: The drop down can only be dismissed manually (in code) 223 | 224 | ### Others 225 | 226 | You can manually (pre)select a row with: 227 | 228 | ```swift 229 | dropDown.selectRow(at: 3) 230 | ``` 231 | 232 | The data source is reloaded automatically when changing the `dataSource` property. 233 | If needed, you can reload the data source manually by doing: 234 | 235 | ```swift 236 | dropDown.reloadAllComponents() 237 | ``` 238 | 239 | You can get info about the selected item at any time with this: 240 | 241 | ```swift 242 | dropDown.selectedItem // String? 243 | dropDown.indexForSelectedRow // Int? 244 | ``` 245 | 246 | ## Customize UI 🖌 247 | 248 | You can customize these properties of the drop down: 249 | 250 | - `textFont`: the font of the text for each cells of the drop down. 251 | - `textColor`: the color of the text for each cells of the drop down. 252 | - `selectedTextColor`: the color of the text for selected cells of the drop down. 253 | - `backgroundColor`: the background color of the drop down. 254 | - `selectionBackgroundColor`: the background color of the selected cell in the drop down. 255 | - `cellHeight`: the height of the drop down cells. 256 | - `dimmedBackgroundColor`: the color of the background (behind the drop down, covering the entire screen). 257 | - `cornerRadius`: the corner radius of the drop down (see [info](#Issues) below if you encounter any issue) 258 | - `setupMaskedCorners`: the masked corners of the dropdown. Use this along with `cornerRadius` to set the corner radius only on certain corners. 259 | 260 | You can change them through each instance of `DropDown` or via `UIAppearance` like this for example: 261 | 262 | ```swift 263 | DropDown.appearance().textColor = UIColor.black 264 | DropDown.appearance().selectedTextColor = UIColor.red 265 | DropDown.appearance().textFont = UIFont.systemFont(ofSize: 15) 266 | DropDown.appearance().backgroundColor = UIColor.white 267 | DropDown.appearance().selectionBackgroundColor = UIColor.lightGray 268 | DropDown.appearance().cellHeight = 60 269 | ``` 270 | 271 | ## Expert mode 🤓 272 | 273 | when calling the `show` method, it returns a tuple like this: 274 | 275 | ```swift 276 | (canBeDisplayed: Bool, offscreenHeight: CGFloat?) 277 | ``` 278 | 279 | - `canBeDisplayed`: Tells if there is enough height to display the drop down. If its value is `false`, the drop down is not showed. 280 | - `offscreenHeight`: If the drop down was not able to show all cells from the data source at once, `offscreenHeight` will contain the height needed to display all cells at once (without having to scroll through them). This can be used in a scroll view or table view to scroll enough before showing the drop down. 281 | 282 | ## Issues 283 | 284 | If you experience the compiler error *"Ambiguous use of 'cornerRadius'"* on line: 285 | ```swift 286 | DropDown.appearance().cornerRadius = 10 287 | ``` 288 | 289 | Please use intead: 290 | ```swift 291 | DropDown.appearance().setupCornerRadius(10) // available since v2.3.6 292 | ``` 293 | 294 | ## Requirements 295 | 296 | * Xcode 8+ 297 | * Swift 3.0 298 | * iOS 8+ 299 | * ARC 300 | 301 | ## License 302 | 303 | This project is under MIT license. For more information, see `LICENSE` file. 304 | 305 | ## Credits 306 | 307 | DropDown was inspired by the Material Design version of the [Simple Menu](http://www.google.com/design/spec/components/menus.html#menus-simple-menus). 308 | 309 | DropDown was done to integrate in a project I work on:
310 | [![Assisto](https://assis.to/images/logouser_dark.png)](https://assis.to) 311 | 312 | It will be updated when necessary and fixes will be done as soon as discovered to keep it up to date. 313 | 314 | I work at
315 | [![Pinch](http://pinch.eu/img/pinch-logo.png)](http://pinch.eu) 316 | 317 | You can find me on Twitter [@kevinh6113](https://twitter.com/kevinh6113). 318 | 319 | Enjoy! 320 | -------------------------------------------------------------------------------- /Screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/1.png -------------------------------------------------------------------------------- /Screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/2.png -------------------------------------------------------------------------------- /Screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/3.png -------------------------------------------------------------------------------- /Screenshots/customCells/links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/customCells/links.png -------------------------------------------------------------------------------- /Screenshots/customCells/xib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/customCells/xib.png -------------------------------------------------------------------------------- /Screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AssistoLab/DropDown/2ab6f6ce19f0117d1a76ea043ef8f57722c65d16/Screenshots/logo.png --------------------------------------------------------------------------------