├── DropDownMenuKit.xcassets
├── Contents.json
└── Ionicons-chevron-up.imageset
│ ├── chevron-up.pdf
│ └── Contents.json
├── Example
├── Assets.xcassets
│ ├── Contents.json
│ ├── Ionicons-ios-search.imageset
│ │ ├── ios-search.pdf
│ │ └── Contents.json
│ ├── Ionicons-ios-checkmark-outline.imageset
│ │ ├── ios-checkmark-outline.pdf
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── AppDelegate.swift
├── Info.plist
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
└── ViewController.swift
├── DropDownMenuKit.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── DropDownMenuKit.xcscheme
│ │ └── DropDownMenuExample.xcscheme
└── project.pbxproj
├── .travis.yml
├── DropDownMenu.h
├── TODO.md
├── Info.plist
├── DropDownMenuKit.podspec
├── NEWS.md
├── .gitignore
├── DropDownMenuCell.swift
├── LICENSE
├── README.md
├── DropDownTitleView.swift
└── DropDownMenu.swift
/DropDownMenuKit.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Ionicons-ios-search.imageset/ios-search.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qmathe/DropDownMenuKit/HEAD/Example/Assets.xcassets/Ionicons-ios-search.imageset/ios-search.pdf
--------------------------------------------------------------------------------
/DropDownMenuKit.xcassets/Ionicons-chevron-up.imageset/chevron-up.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qmathe/DropDownMenuKit/HEAD/DropDownMenuKit.xcassets/Ionicons-chevron-up.imageset/chevron-up.pdf
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Ionicons-ios-checkmark-outline.imageset/ios-checkmark-outline.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qmathe/DropDownMenuKit/HEAD/Example/Assets.xcassets/Ionicons-ios-checkmark-outline.imageset/ios-checkmark-outline.pdf
--------------------------------------------------------------------------------
/DropDownMenuKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DropDownMenuKit.xcassets/Ionicons-chevron-up.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "chevron-up.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Ionicons-ios-search.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ios-search.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Ionicons-ios-checkmark-outline.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ios-checkmark-outline.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | notifications:
3 | email:
4 | recipients:
5 | - quentin.mathe@gmail.com
6 | xcode_project: DropDownMenuKit.xcodeproj
7 | xcode_scheme: DropDownMenuExample
8 | osx_image: xcode10.2
9 | script: xcodebuild -project DropDownMenuKit.xcodeproj -scheme DropDownMenuExample build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
10 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Quentin Mathe
3 |
4 | Date: August 2016
5 | License: MIT
6 | */
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | return true
17 | }
18 |
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/DropDownMenu.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Quentin Mathe
3 |
4 | Date: August 2016
5 | License: MIT
6 | */
7 |
8 | #import
9 |
10 | //! Project version number for DropDownMenu.
11 | FOUNDATION_EXPORT double DropDownMenuVersionNumber;
12 |
13 | //! Project version string for DropDownMenu.
14 | FOUNDATION_EXPORT const unsigned char DropDownMenuVersionString[];
15 |
16 | // In this header, you should import all the public headers of your framework using statements like #import
17 |
18 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | DropDownMenuKit TODO
2 | ====================
3 |
4 | - Add DropDownMenu.titleView to let the menu manages the title view status (up vs down)
5 | - Add DropDownMenuDelegate.needsBackgroundTransition to let the client cancels the background alpha animation when a menu is hidden and another one is shown immediately
6 | - Add some presentation options for DropDownMenuCell to support UI patterns commonly seen in Apple's apps
7 | - May be make easier to customize DropDownMenuCell layout
8 | - Share code or reuse IconTextCell for DropDownMenuCell
9 | - Update example to showcase hiddenContentOffset
10 | - Better documentation (API and README)
11 | - Support grouping menu cells by section
12 |
--------------------------------------------------------------------------------
/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 | 0.9
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 9
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Example/Assets.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" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/DropDownMenuKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "DropDownMenuKit"
4 | s.version = "1.0"
5 | s.summary = "UIKit drop down menu, simple yet flexible and written in Swift"
6 | s.description = "DropDownMenuKit is a custom UIKit control to show a menu attached to the navigation bar or toolbar. The menu appears with a sliding animation and can be deeply customized. For example, with icons, embedded controls, or a checkmark to denote a selected row among multiple menu cells."
7 | s.homepage = "https://github.com/qmathe/DropDownMenuKit"
8 | s.screenshots = "http://www.quentinmathe.com/github/DropDownMenuKit/App%20History%20Menu%20-%20iPhone%205.png"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.author = { "Quentin Mathé" => "quentin.mathe@gmail.com" }
11 | s.social_media_url = "http://twitter.com/quentin_mathe"
12 |
13 | s.swift_version = "5.0"
14 | s.platform = :ios, "11.0"
15 |
16 | s.source = { :git => "https://github.com/qmathe/DropDownMenuKit.git", :tag => "1.0" }
17 | s.source_files = "*.swift", "*.{h,m}"
18 | s.public_header_files = "*.h"
19 | # Each xcassets directory must be in a distinct resource bundle (otherwise multiple assets.car end up in the same bundle)
20 | s.resource_bundles = { "DropDownMenuKitAssets" => ["DropDownMenuKit.xcassets"] }
21 | end
22 |
--------------------------------------------------------------------------------
/Example/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 | APPL
17 | CFBundleShortVersionString
18 | 0.9
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 9
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UIApplicationSceneManifest
40 |
41 | UIApplicationSupportsMultipleScenes
42 |
43 | UISceneConfigurations
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | DropDownMenuKit NEWS
2 | ====================
3 |
4 | 1.0
5 | ---
6 |
7 | - Fix crash on icon loading when subclassing DropDownMenuTitleView
8 | - Fix name conflicts between DropDownMenuKit and app assets when compiling as static library with CocoaPods
9 | - Update example to ensure overlays don't cover navigation bar and toolbar in iOS 11 and higher
10 |
11 | 0.9
12 | ---
13 |
14 | - Migrated to Swift 5
15 | - New custom transition API
16 | - Fixed menu cells not always visible during hiding animation on iOS 11 and higher
17 |
18 | 0.8.6
19 | -----
20 |
21 | - Migrated to Swift 4.2
22 |
23 | 0.8.5
24 | -----
25 |
26 | - Migrated to Swift 4
27 | - Improved example to showcase:
28 | - adding/removing menu entries
29 | - menu scrolling
30 | - long title support
31 | - Now usable in app extensions
32 | - DropDownMenuKit > Build Settings > Other Swift Flags must include -DAPP_EXTENSION
33 |
34 | 0.8.4
35 | -----
36 |
37 | - iOS 11 support
38 | - Improved menu to become scrollable menu when too many rows are presented
39 | - DropDownMenu.visibleContentOffset has been replaced with visibleContentInsets to support this
40 | - Fixed memory leak preventing menu to be released
41 |
42 | 0.8.3
43 | -----
44 |
45 | - New customization options
46 | - row height
47 | - icon size
48 | - up/down images
49 | - Fixed title to always appear correctly centered in navigation bar
50 | - long titles are now supported
51 | - Fixed height of menu view when setting its cells after it has been resized
52 |
53 | 0.8.2
54 | -----
55 |
56 | - Migrated to Swift 3
57 |
58 | 0.8.1
59 | -----
60 |
61 | - Added DropDownMenu.selectMenuCell(:)
62 | - Fixed incorrect arrow orientation after 3 quick taps on the title view
63 | - Don't let the user toggles the menu when a toggling animation is already underway
64 |
65 | 0.8
66 | ---
67 |
68 | - First release
69 |
70 |
--------------------------------------------------------------------------------
/Example/Base.lproj/LaunchScreen.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 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/swift
3 |
4 | ### Swift ###
5 | # Xcode
6 | #
7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
8 |
9 | PodTestInstall/
10 |
11 | ## Build generated
12 | build/
13 | DerivedData/
14 |
15 | ## Various settings
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata/
25 |
26 | ## Other
27 | *.moved-aside
28 | *.xccheckout
29 | *.xcscmblueprint
30 |
31 | ## Obj-C/Swift specific
32 | *.hmap
33 | *.ipa
34 | *.dSYM.zip
35 | *.dSYM
36 |
37 | ## Playgrounds
38 | timeline.xctimeline
39 | playground.xcworkspace
40 |
41 | # Swift Package Manager
42 | #
43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
44 | # Packages/
45 | # Package.pins
46 | # Package.resolved
47 | .build/
48 |
49 | # CocoaPods
50 | #
51 | # We recommend against adding the Pods directory to your .gitignore. However
52 | # you should judge for yourself, the pros and cons are mentioned at:
53 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
54 | #
55 | # Pods/
56 | #
57 | # Add this line if you want to avoid checking in source code from the Xcode workspace
58 | *.xcworkspace
59 |
60 | # Carthage
61 | #
62 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
63 | Carthage/Checkouts
64 |
65 | Carthage/Build
66 |
67 | # fastlane
68 | #
69 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
70 | # screenshots whenever they are needed.
71 | # For more information about the recommended setup visit:
72 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
73 |
74 | fastlane/report.xml
75 | fastlane/Preview.html
76 | fastlane/screenshots/**/*.png
77 | fastlane/test_output
78 |
79 | # Code Injection
80 | #
81 | # After new code Injection tools there's a generated folder /iOSInjectionProject
82 | # https://github.com/johnno1962/injectionforxcode
83 |
84 | iOSInjectionProject/
85 |
86 |
87 | # End of https://www.gitignore.io/api/swift
88 |
--------------------------------------------------------------------------------
/DropDownMenuCell.swift:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (C) 2015 Quentin Mathe
3 |
4 | Date: May 2015
5 | License: MIT
6 | */
7 |
8 | import UIKit
9 |
10 | open class DropDownMenuCell : UITableViewCell {
11 |
12 | open var customView: UIView? {
13 | didSet {
14 | guard let customView = customView else {
15 | return
16 | }
17 | contentView.addSubview(customView)
18 | }
19 | }
20 | /// For an app extension, the selector must take an argument.
21 | open var menuAction: Selector?
22 | /// For an app extension, the menu target must not be nil.
23 | open weak var menuTarget: AnyObject?
24 | open var showsCheckmark = true
25 | open var rowHeight: CGFloat = 44
26 |
27 | // MARK: - Initialization
28 |
29 | override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
30 | fatalError("init(style:reuseIdentifier:) is not supported")
31 | }
32 |
33 | public init() {
34 | super.init(style: .default, reuseIdentifier: NSStringFromClass(DropDownMenuCell.self))
35 | setUp()
36 | }
37 |
38 | required public init?(coder aDecoder: NSCoder) {
39 | super.init(coder: aDecoder)
40 | }
41 |
42 | open override func awakeFromNib() {
43 | setUp()
44 | }
45 |
46 | private func setUp() {
47 | textLabel?.text = "Untitled"
48 | }
49 |
50 | // MARK: - Layout
51 |
52 | open var iconSize = CGSize(width: 24, height: 24)
53 |
54 | override open func layoutSubviews() {
55 |
56 | if let textLabel = textLabel {
57 | if customView != nil && textLabel.text == nil {
58 | textLabel.text = "Custom View Origin Hint"
59 | }
60 | textLabel.isHidden = customView != nil
61 | }
62 |
63 | super.layoutSubviews()
64 |
65 | if let imageView = imageView, imageView.image != nil {
66 | imageView.frame.size = iconSize
67 | imageView.center = CGPoint(x: imageView.center.x, y: bounds.size.height / 2)
68 | }
69 |
70 | if let customView = customView {
71 | if let textLabel = textLabel, imageView?.image != nil {
72 | customView.frame.origin.x = textLabel.frame.origin.x
73 | } else {
74 | customView.center.x = bounds.width / 2
75 | }
76 | customView.center.y = bounds.height / 2
77 |
78 | let margin: CGFloat = 5 // imageView?.frame.origin.x ?? 15
79 |
80 | if customView.frame.maxX + margin > bounds.width {
81 | customView.frame.size.width = bounds.width - customView.frame.origin.x - margin
82 | }
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Quentin Mathé
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.
22 |
23 | --
24 |
25 | Assets.xcassets includes icons from Ionicons (http://ionicons.com) licensed under:
26 |
27 | The MIT License (MIT)
28 |
29 | Copyright (c) 2016 Drifty (http://drifty.com/)
30 |
31 | Permission is hereby granted, free of charge, to any person obtaining a copy
32 | of this software and associated documentation files (the "Software"), to deal
33 | in the Software without restriction, including without limitation the rights
34 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35 | copies of the Software, and to permit persons to whom the Software is
36 | furnished to do so, subject to the following conditions:
37 |
38 | The above copyright notice and this permission notice shall be included in
39 | all copies or substantial portions of the Software.
40 |
41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
47 | THE SOFTWARE.
48 |
--------------------------------------------------------------------------------
/DropDownMenuKit.xcodeproj/xcshareddata/xcschemes/DropDownMenuKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | DropDownMenuKit
2 | ===============
3 |
4 | [](https://travis-ci.org/qmathe/DropDownMenuKit)
5 | [](http://www.apple.com)
6 | [](https://swift.org)
7 | [](https://github.com/qmathe/DropDownMenuKit/LICENSE)
8 |
9 | DropDownMenuKit is a custom UIKit control to show a menu attached to the navigation bar or toolbar. The menu appears with a sliding animation and can be deeply customized. For example, with icons, embedded controls, or a checkmark to denote a selected row among multiple menu cells.
10 |
11 | The control is made up of three parts:
12 |
13 | - DropDownMenu: the menu itself, a UIView subclass that contains a UITableView presenting one or more DropDownMenuCell(s)
14 | - DropDownMenuCell: a menu entry, implemented as a UITableViewCell subclass
15 | - DropDownMenuTitleView: an optional title view to toggle the menu, which is usually put in the navigation bar and acts as a disclosure indicator
16 |
17 |
18 |
19 |
20 | To see in action, take a look at the very beginning of [Placeboard](http://www.placeboardapp.com) demo video.
21 |
22 | Compatibility
23 | -------------
24 |
25 | DropDownMenuKit requires at least Xcode 10.2 (introducing Swift 5) and supports iOS 11 and higher.
26 |
27 | For versions compatible with Swift 3 and 4, see branches named _swift-3/4.x_.
28 |
29 | For now, the code base remains compatible with older iOS versions (8 or higher), but these older
30 | versions are not supported anymore.
31 |
32 | Installation
33 | ------------
34 |
35 | ### Carthage
36 |
37 | Add the following line to your Cartfile, run `carthage update` to build the framework and drag the built DropDownMenuKit.framework into your Xcode project.
38 |
39 | github "qmathe/DropDownMenuKit"
40 |
41 | ### CocoaPods
42 |
43 | Add the following lines to your Podfile and run `pod install` with CocoaPods 1.9 or newer.
44 |
45 | pod "DropDownMenuKit"
46 |
47 | ### Manually
48 |
49 | If you don't use Carthage or CocoaPods, it's possible to drag the built framework or embed the source files into your project.
50 |
51 | #### Framework
52 |
53 | Build DropDownMenuKit framework and drop it into your Xcode project.
54 |
55 | #### Files
56 |
57 | Drop DropDownMenu.swift, DropDownMenuCell.swift, DropDownTitleView.swift and DropDownMenuKit.xcassets into your Xcode project.
58 |
59 |
60 | App Extension Usage
61 | -------------------------
62 |
63 | ### Build Settings
64 |
65 | Add **-DAPP_EXTENSION** to _DropDownMenuKit > Build Settings > Other Swift Flags_.
66 |
67 | ### Restrictions
68 |
69 | - `DropDownMenuCell.menuAction` must take a single argument
70 | - `DropDownMenuCell.menuTarget` must not be nil
71 |
--------------------------------------------------------------------------------
/DropDownMenuKit.xcodeproj/xcshareddata/xcschemes/DropDownMenuExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Example/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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/DropDownTitleView.swift:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (C) 2015 Quentin Mathe
3 |
4 | Date: June 2015
5 | License: MIT
6 | */
7 |
8 | import UIKit
9 |
10 | open class DropDownTitleView : UIControl {
11 |
12 | open var iconSize = CGSize(width: 12, height: 12) {
13 | didSet {
14 | setNeedsLayout()
15 | }
16 | }
17 | // When compiling as a static library with CocoaPods, image assets end ups in the main bundle
18 | // rather than the framework bundle:
19 | // AppName.app/Resources/DropDownMenuKit/Assets.car
20 | lazy var imageBundle: Bundle = {
21 | let bundle = Bundle(for: DropDownTitleView.self)
22 |
23 | if let podBundleURL = bundle.url(forResource: "DropDownMenuKitAssets", withExtension: "bundle") {
24 | guard let podBundle = Bundle(url: podBundleURL) else {
25 | fatalError("Missing image assets for DropDownMenUKit")
26 | }
27 | return podBundle
28 | } else {
29 | return bundle
30 | }
31 | }()
32 | open lazy var menuDownImageView: UIImageView = {
33 | let menuDownImageView = UIImageView(image: self.imageNamed("Ionicons-chevron-up"))
34 |
35 | menuDownImageView.tintColor = UIColor.black
36 | menuDownImageView.transform = CGAffineTransform(scaleX: 1, y: -1)
37 |
38 | return menuDownImageView
39 | }()
40 | open lazy var menuUpImageView: UIImageView = {
41 | let menuUpImageView = UIImageView(image: self.imageNamed("Ionicons-chevron-up"))
42 |
43 | menuUpImageView.tintColor = UIColor.black
44 |
45 | return menuUpImageView
46 | }()
47 | open lazy var imageView: UIView = {
48 | // For flip animation, we need a container view
49 | // See http://stackoverflow.com/questions/11847743/transitionfromview-and-strange-behavior-with-flip
50 | return UIView(frame: CGRect(origin: CGPoint.zero, size: self.iconSize))
51 | }()
52 | open lazy var titleLabel: UILabel = {
53 | let titleLabel = UILabel()
54 |
55 | titleLabel.font = UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize)
56 | titleLabel.textColor = UIColor.white
57 |
58 | return titleLabel
59 | }()
60 | open var title: String? {
61 | get {
62 | return titleLabel.text
63 | }
64 | set {
65 | titleLabel.text = newValue
66 | titleLabel.sizeToFit()
67 | titleWidth = titleLabel.frame.width
68 | layoutSubviews()
69 | }
70 | }
71 | private var titleWidth: CGFloat = 0
72 | open var isUp: Bool { return menuUpImageView.superview != nil }
73 | open var toggling = false
74 |
75 | // MARK: - Initialization
76 |
77 | override public init(frame: CGRect) {
78 | super.init(frame: frame)
79 | setUp()
80 | }
81 |
82 | required public init?(coder aDecoder: NSCoder) {
83 | super.init(coder: aDecoder)
84 | }
85 |
86 | // To support adding outlets/actions later and access them during initialization
87 | override open func awakeFromNib() {
88 | setUp()
89 | }
90 |
91 | func setUp() {
92 | imageView.addSubview(menuDownImageView)
93 |
94 | addSubview(titleLabel)
95 | addSubview(imageView)
96 |
97 | if #available(iOS 11, *) {
98 | translatesAutoresizingMaskIntoConstraints = false
99 | } else {
100 | autoresizingMask = [.flexibleWidth, .flexibleHeight]
101 | }
102 |
103 | let recognizer = UITapGestureRecognizer(target: self, action: #selector(DropDownTitleView.toggleMenu))
104 |
105 | isUserInteractionEnabled = true
106 | addGestureRecognizer(recognizer)
107 |
108 | title = "Untitled"
109 | }
110 |
111 | open func imageNamed(_ name: String) -> UIImage {
112 | return UIImage(named: name, in: imageBundle, compatibleWith: nil)!
113 | }
114 |
115 | // MARK: - Layout
116 |
117 | private let spacing: CGFloat = 4
118 | // For iOS 11 and above
119 | override open var intrinsicContentSize: CGSize {
120 | return UIView.layoutFittingExpandedSize
121 | }
122 |
123 | // For iOS 10 and below
124 | open override func willMove(toSuperview newSuperview: UIView?) {
125 | guard let superview = newSuperview else {
126 | return
127 | }
128 | // Will trigger layoutSubviews() without having resize the title view yet
129 | // e.g. (origin = (x = 0, y = 0), size = (width = 320, height = 44)
130 | frame = superview.bounds
131 | }
132 |
133 | // Centers the title when DropDownMenu.selectMenuCell() isn't called while creating the menu
134 | open override func didMoveToWindow() {
135 | // Will trigger layoutSubviews() with the title view resized according to autoresizing
136 | // e.g. (origin = (x = 58, y = 0), size = (width = 211.5, height = 44))
137 | layoutSubviews()
138 | }
139 |
140 | open override func layoutSubviews() {
141 | super.layoutSubviews()
142 |
143 | menuDownImageView.frame.size = iconSize
144 | menuUpImageView.frame.size = iconSize
145 | imageView.frame.size = iconSize
146 |
147 | let maxTitleWidth = frame.width - 2 * (spacing + imageView.frame.width)
148 |
149 | titleLabel.center = CGPoint(x: frame.width / 2, y: frame.height / 2)
150 | if titleWidth > maxTitleWidth {
151 | titleLabel.frame.origin.x = spacing + imageView.frame.width
152 | titleLabel.frame.size.width = maxTitleWidth
153 | } else {
154 | titleLabel.frame.size.width = titleWidth
155 | }
156 |
157 | imageView.frame.origin.x = titleLabel.frame.maxX + spacing
158 | imageView.center.y = frame.height / 2
159 | }
160 |
161 | // MARK: - Actions
162 |
163 | @IBAction open func toggleMenu() {
164 | if toggling {
165 | return
166 | }
167 | toggling = true
168 | let viewToReplace = isUp ? menuUpImageView : menuDownImageView
169 | let replacementView = isUp ? menuDownImageView : menuUpImageView
170 | let options = isUp ? UIView.AnimationOptions.transitionFlipFromTop : UIView.AnimationOptions.transitionFlipFromBottom
171 |
172 | sendActions(for: .touchUpInside)
173 |
174 | UIView.transition(from: viewToReplace,
175 | to: replacementView,
176 | duration: 0.4,
177 | options: options,
178 | completion: { (Bool) in
179 | self.sendActions(for: .valueChanged)
180 | self.toggling = false
181 | })
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Quentin Mathe
3 |
4 | Date: August 2016
5 | License: MIT
6 | */
7 |
8 | import UIKit
9 | import DropDownMenuKit
10 |
11 | class ViewController: UIViewController, DropDownMenuDelegate {
12 |
13 | // MARK: - UI Elements
14 |
15 | var titleView: DropDownTitleView!
16 | @IBOutlet var navigationBarMenu: DropDownMenu!
17 | @IBOutlet var toolbarMenu: DropDownMenu!
18 |
19 | // MARK: - Initialization
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | let title = prepareNavigationBarMenuTitleView()
24 |
25 | prepareNavigationBarMenu(title)
26 | prepareToolbarMenu()
27 |
28 | navigationBarMenu.container = view
29 | toolbarMenu.container = view
30 | }
31 |
32 | override func viewDidAppear(_ animated: Bool) {
33 | super.viewDidAppear(animated)
34 | updateMenuContentInsets()
35 | }
36 |
37 | func prepareNavigationBarMenuTitleView() -> String {
38 | titleView = DropDownTitleView()
39 | titleView.addTarget(self,
40 | action: #selector(ViewController.willToggleNavigationBarMenu(_:)),
41 | for: .touchUpInside)
42 | titleView.addTarget(self,
43 | action: #selector(ViewController.didToggleNavigationBarMenu(_:)),
44 | for: .valueChanged)
45 | titleView.titleLabel.textColor = UIColor.black
46 |
47 | navigationItem.titleView = titleView
48 |
49 | return titleView.title!
50 | }
51 |
52 | func prepareNavigationBarMenu(_ currentChoice: String) {
53 | navigationBarMenu = DropDownMenu(frame: view.bounds)
54 | navigationBarMenu.delegate = self
55 |
56 | let firstCell = DropDownMenuCell()
57 | let longTitle = "The quick brown fox jumps over the lazy dog"
58 |
59 | firstCell.textLabel!.text = longTitle
60 | firstCell.menuAction = #selector(ViewController.choose(_:))
61 | firstCell.menuTarget = self
62 | if currentChoice == longTitle {
63 | firstCell.accessoryType = .checkmark
64 | }
65 |
66 | let secondCell = DropDownMenuCell()
67 | let shortTitle = "Short"
68 |
69 | secondCell.textLabel!.text = shortTitle
70 | secondCell.rowHeight = 60
71 | secondCell.menuAction = #selector(ViewController.choose(_:))
72 | secondCell.menuTarget = self
73 | if currentChoice == shortTitle {
74 | firstCell.accessoryType = .checkmark
75 | }
76 |
77 | navigationBarMenu.menuCells = [firstCell, secondCell]
78 | navigationBarMenu.selectMenuCell(secondCell)
79 |
80 | // For a simple gray overlay in background
81 | navigationBarMenu.backgroundView = UIView(frame: navigationBarMenu.bounds)
82 | navigationBarMenu.backgroundView!.backgroundColor = UIColor.black
83 | navigationBarMenu.backgroundAlpha = 0.7
84 | }
85 |
86 | func prepareToolbarMenu() {
87 | toolbarMenu = DropDownMenu(frame: view.bounds)
88 | toolbarMenu.delegate = self
89 |
90 | let selectCell = DropDownMenuCell()
91 |
92 | selectCell.textLabel!.text = "Change Title Icons"
93 | selectCell.imageView!.image = UIImage(named: "Ionicons-ios-checkmark-outline")
94 | selectCell.showsCheckmark = false
95 | selectCell.menuAction = #selector(ViewController.changeTitleIcons as (ViewController) -> () -> ())
96 | selectCell.menuTarget = self
97 |
98 | let sortKeys = ["Name", "Date", "Size"]
99 | let sortCell = DropDownMenuCell()
100 | let sortSwitcher = UISegmentedControl(items: sortKeys)
101 |
102 | sortSwitcher.selectedSegmentIndex = sortKeys.firstIndex(of: "Name")!
103 | sortSwitcher.addTarget(self, action: #selector(ViewController.sort(_:)), for: .valueChanged)
104 |
105 | sortCell.customView = sortSwitcher
106 | sortCell.textLabel!.text = "Sort"
107 | sortCell.imageView!.image = UIImage(named: "Ionicons-ios-search")
108 | sortCell.showsCheckmark = false
109 |
110 | toolbarMenu.menuCells = [selectCell, sortCell]
111 | toolbarMenu.direction = .up
112 |
113 | // For a simple gray overlay in background
114 | toolbarMenu.backgroundView = UIView(frame: toolbarMenu.bounds)
115 | toolbarMenu.backgroundView!.backgroundColor = UIColor.black
116 | toolbarMenu.backgroundAlpha = 0.7
117 | }
118 |
119 | // MARK: - Layout
120 |
121 | // Resize overlays in order they doesn't cover navigation bar and toolbar (this can be changed
122 | // to cover more or less on screen depending on your needs).
123 | func updateOverlayInsets() {
124 | if #available(iOS 11, *) {
125 | updateOverlayInsets(for: navigationBarMenu)
126 | updateOverlayInsets(for: toolbarMenu)
127 | }
128 | }
129 |
130 | func updateOverlayInsets(for menu: DropDownMenu) {
131 | let overlayTop = menu.visibleContentInsets.top
132 | let overlayBottom = menu.visibleContentInsets.bottom
133 | let overlayHeight = menu.frame.height - overlayTop - overlayBottom
134 |
135 | menu.backgroundView?.frame.origin.y = overlayTop
136 | menu.backgroundView?.frame.size.height = overlayHeight
137 | }
138 |
139 | func updateMenuContentInsets() {
140 | var visibleContentInsets: UIEdgeInsets
141 |
142 | if #available(iOS 11, *) {
143 | visibleContentInsets = view.safeAreaInsets
144 | } else {
145 | visibleContentInsets =
146 | UIEdgeInsets(top: navigationController!.navigationBar.frame.size.height + statusBarHeight(),
147 | left: 0,
148 | bottom: navigationController!.toolbar.frame.size.height,
149 | right: 0)
150 | }
151 |
152 | navigationBarMenu.visibleContentInsets = visibleContentInsets
153 | toolbarMenu.visibleContentInsets = visibleContentInsets
154 |
155 | updateOverlayInsets()
156 | }
157 |
158 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
159 | super.viewWillTransition(to: size, with: coordinator)
160 |
161 | coordinator.animate(alongsideTransition: { _ in
162 | // If we put this only in -viewDidLayoutSubviews, menu animation is
163 | // messed up when selecting an item
164 | self.updateMenuContentInsets()
165 | }, completion: nil)
166 | }
167 |
168 | // MARK: - Updating UI
169 |
170 | func validateBarItems() {
171 | navigationItem.leftBarButtonItem?.isEnabled = titleView.isUp && !navigationBarMenu.menuCells.isEmpty
172 | navigationItem.rightBarButtonItem?.isEnabled = titleView.isUp
173 | }
174 |
175 | // MARK: - Actions
176 |
177 | @IBAction func choose(_ sender: AnyObject) {
178 | titleView.title = (sender as! DropDownMenuCell).textLabel!.text
179 | }
180 |
181 | @IBAction func removeNavigationBarMenuCell() {
182 | navigationBarMenu.menuCells = Array(navigationBarMenu.menuCells.dropLast())
183 | validateBarItems()
184 | }
185 |
186 | @IBAction func addNavigationBarMenuCell() {
187 | let cell = DropDownMenuCell()
188 |
189 | cell.textLabel!.text = String(navigationBarMenu.menuCells.count)
190 | cell.menuAction = #selector(ViewController.choose(_:))
191 | cell.menuTarget = self
192 |
193 | navigationBarMenu.menuCells += [cell]
194 | validateBarItems()
195 | }
196 |
197 | @IBAction func changeTitleIcons() {
198 | titleView.iconSize = CGSize(width: 24, height: 24)
199 | titleView.menuDownImageView.image = UIImage(named: "Ionicons-ios-checkmark-outline")
200 | titleView.menuDownImageView.transform = CGAffineTransform.identity
201 | titleView.menuDownImageView.tintColor = UIColor.green
202 | titleView.menuUpImageView.image = UIImage(named: "Ionicons-ios-search")
203 | }
204 |
205 | @IBAction func sort(_ sender: AnyObject) {
206 | print("Sent sort action")
207 | }
208 |
209 | @IBAction func showToolbarMenu() {
210 | if titleView.isUp {
211 | titleView.toggleMenu()
212 | }
213 | toolbarMenu.show()
214 | }
215 |
216 | @IBAction func willToggleNavigationBarMenu(_ sender: DropDownTitleView) {
217 | toolbarMenu.hide()
218 |
219 | if sender.isUp {
220 | navigationBarMenu.hide()
221 | } else {
222 | navigationBarMenu.show()
223 | }
224 | }
225 |
226 | @IBAction func didToggleNavigationBarMenu(_ sender: DropDownTitleView) {
227 | print("Sent did toggle navigation bar menu action")
228 | validateBarItems()
229 | }
230 |
231 | func didTapInDropDownMenuBackground(_ menu: DropDownMenu) {
232 | if menu == navigationBarMenu {
233 | titleView.toggleMenu()
234 | } else {
235 | menu.hide()
236 | }
237 | }
238 | }
239 |
240 |
241 | func statusBarHeight() -> CGFloat {
242 | let statusBarSize = UIApplication.shared.statusBarFrame.size
243 | return min(statusBarSize.width, statusBarSize.height)
244 | }
245 |
--------------------------------------------------------------------------------
/DropDownMenu.swift:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (C) 2015 Quentin Mathe
3 |
4 | Date: May 2015
5 | License: MIT
6 | */
7 |
8 | import UIKit
9 |
10 | @objc public protocol DropDownMenuDelegate {
11 | func didTapInDropDownMenuBackground(_ menu: DropDownMenu)
12 | }
13 |
14 | public enum DropDownMenuRevealDirection {
15 | case up
16 | case down
17 | }
18 |
19 |
20 | open class DropDownMenu : UIView, UITableViewDataSource, UITableViewDelegate, UIGestureRecognizerDelegate {
21 |
22 | open weak var delegate: DropDownMenuDelegate?
23 | open weak var container: UIView? {
24 | didSet {
25 | removeFromSuperview()
26 | container?.addSubview(self)
27 | }
28 | }
29 | // The content view fills the entire container, so we can use it to fade
30 | // the background view in and out.
31 | //
32 | // By default, it contains the menu view, but other subviews can be added to
33 | // it and laid out by overriding -layoutSubviews.
34 | public let contentView = UIView(frame: .zero)
35 | public let menuView = UITableView(frame: .zero)
36 | open var menuCells = [DropDownMenuCell]() {
37 | didSet {
38 | menuView.reloadData()
39 | setNeedsLayout()
40 | }
41 | }
42 | // The background view to be faded out with the background alpha, when the
43 | // menu slides over it
44 | open var backgroundView: UIView? {
45 | didSet {
46 | oldValue?.removeFromSuperview()
47 | backgroundView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
48 | backgroundView?.alpha = oldValue?.alpha ?? 0
49 | if let backgroundView = backgroundView {
50 | insertSubview(backgroundView, belowSubview: contentView)
51 | }
52 | }
53 | }
54 | open var backgroundAlpha = CGFloat(1)
55 |
56 | // MARK: - Initialization
57 |
58 | override public init(frame: CGRect) {
59 | super.init(frame: frame)
60 | setUp()
61 | }
62 |
63 | required public init?(coder aDecoder: NSCoder) {
64 | super.init(coder: aDecoder)
65 | }
66 |
67 | open override func awakeFromNib() {
68 | setUp()
69 | }
70 |
71 | private func setUp() {
72 | menuView.frame.size = frame.size
73 | menuView.autoresizingMask = .flexibleWidth
74 | if #available(iOS 11.0, *) {
75 | // Prevent table cells to be shifted downwards abruptly before the menu slides up under the navigation bar
76 | menuView.contentInsetAdjustmentBehavior = .never
77 | menuView.insetsContentViewsToSafeArea = false
78 | }
79 | menuView.isScrollEnabled = true
80 | menuView.bounces = false
81 | menuView.showsVerticalScrollIndicator = true
82 | menuView.showsHorizontalScrollIndicator = false
83 | menuView.dataSource = self
84 | menuView.delegate = self
85 |
86 | contentView.frame.size = frame.size
87 | contentView.autoresizingMask = [.flexibleWidth]
88 | contentView.addSubview(menuView)
89 |
90 | let gesture = UITapGestureRecognizer(target: self, action: #selector(DropDownMenu.tap(_:)))
91 | gesture.delegate = self
92 |
93 | addGestureRecognizer(gesture)
94 | autoresizingMask = [.flexibleWidth, .flexibleHeight]
95 | isHidden = true
96 | addSubview(contentView)
97 | }
98 |
99 | // MARK: - Layout
100 |
101 | // This hidden insets can be used to customize the position of the menu at
102 | // the end of the hiding animation.
103 | //
104 | // If the container doesn't extend under the toolbar and navigation bar,
105 | // this is useful to ensure the hiding animation continues until the menu is
106 | // positioned outside of the screen, rather than stopping the animation when
107 | // the menu is covered by the toolbar or navigation bar.
108 | //
109 | // Left and right insets are currently ignored.
110 | open var hiddenContentInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) {
111 | didSet {
112 | setNeedsLayout()
113 | }
114 | }
115 | // This visible insets can be used to customize the position of the menu
116 | // at the end of the showing animation.
117 | //
118 | // If the container extends under the toolbar and navigation bar, this is
119 | // useful to ensure the menu won't be covered by the toolbar or navigation
120 | // bar once the showing animation is done.
121 | //
122 | // Left and right insets are currently ignored.
123 | open var visibleContentInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) {
124 | didSet {
125 | // Menu height needs to be recomputed
126 | setNeedsLayout()
127 | }
128 | }
129 | open var direction = DropDownMenuRevealDirection.down {
130 | didSet {
131 | setNeedsLayout()
132 | }
133 | }
134 |
135 | public enum Operation {
136 | case show
137 | case hide
138 | }
139 |
140 | private func updateContentViewPosition(on operation: Operation) {
141 | guard let container = container else {
142 | fatalError("DropDownMenu.container must have been set to layout subviews")
143 | }
144 | switch (operation, direction) {
145 | case (.hide, .down):
146 | contentView.frame.origin.y = hiddenContentInsets.top - contentView.frame.height
147 | case (.hide, .up):
148 | contentView.frame.origin.y = container.frame.height - hiddenContentInsets.bottom
149 | case (.show, .down):
150 | contentView.frame.origin.y = visibleContentInsets.top
151 | case (.show, .up):
152 | contentView.frame.origin.y = container.frame.height - contentView.frame.height - visibleContentInsets.bottom
153 | }
154 | }
155 |
156 | open override func layoutSubviews() {
157 | super.layoutSubviews()
158 |
159 | let contentHeight = menuCells.reduce(0) { $0 + $1.rowHeight }
160 | let maxContentHeight = frame.height - visibleContentInsets.bottom - visibleContentInsets.top
161 | let scrollable = contentHeight > maxContentHeight
162 |
163 | menuView.frame.size.height = scrollable ? maxContentHeight : contentHeight
164 | contentView.frame.size.height = menuView.frame.height
165 | updateContentViewPosition(on: isHidden ? .hide : .show)
166 |
167 | // Reset scroll view content offset after rotation
168 | if menuView.visibleCells.isEmpty {
169 | return
170 | }
171 | menuView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)
172 | }
173 |
174 | // MARK: - Animations
175 |
176 | public struct Transition {
177 | public struct Animation {
178 | public typealias Block = () -> ()
179 |
180 | public let before: Block
181 | public let change: Block
182 | public let after: Block
183 |
184 | public init(before: @escaping Block = {}, change: @escaping Block, after: @escaping Block = {}) {
185 | self.before = before
186 | self.change = change
187 | self.after = after
188 | }
189 | }
190 |
191 | /// The duration taken by the animation when hiding/showing the menu and its background.
192 | public var duration: TimeInterval
193 | fileprivate let delay: TimeInterval = 0
194 | public let options: AnimationOptions
195 |
196 | public var show: [Animation]
197 | public var hide: [Animation]
198 | }
199 |
200 | public lazy var transition = Transition(
201 | duration: 0.4,
202 | options: AnimationOptions(),
203 | show: [showMenuAnimation, showBackgroundAnimation],
204 | hide: [hideMenuAnimation, hideBackgroundAnimation]
205 | )
206 |
207 | /// The animation part in charge of showing the menu (doesn't include the background animation).
208 | public private(set) lazy var showMenuAnimation = Transition.Animation(change: {
209 | self.updateContentViewPosition(on: .show)
210 | })
211 | /// The animation part in charge of showing the background (doesn't include the menu animation).
212 | public private(set) lazy var showBackgroundAnimation = Transition.Animation(change: {
213 | self.backgroundView?.alpha = self.backgroundAlpha
214 | })
215 | /// The animation part in charge of hiding the menu (doesn't include the background animation).
216 | public private(set) lazy var hideMenuAnimation = Transition.Animation(change: {
217 | self.updateContentViewPosition(on: .hide)
218 | })
219 | /// The animation part in charge of hiding the background (doesn't include the menu animation).
220 | ///
221 | /// Must set `self.isHidden = true` when the animation completes (this implies the background
222 | /// hiding animation is expected to have a duration equal or greater to the menu hiding animation).
223 | public private(set) lazy var hideBackgroundAnimation = Transition.Animation(change: {
224 | self.backgroundView?.alpha = 0
225 | })
226 |
227 | // MARK: - Selection
228 |
229 | /// Selects the cell briefly and sends the cell menu action.
230 | ///
231 | /// If DropDownMenuCell.showsCheckmark is true, then the cell is marked with
232 | /// a checkmark and all other cells are unchecked.
233 | open func selectMenuCell(_ cell: DropDownMenuCell) {
234 | guard let index = menuCells.firstIndex(of: cell) else {
235 | fatalError("The menu cell to select must belong to the menu")
236 | }
237 | let indexPath = IndexPath(row: index, section: 0)
238 |
239 | menuView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
240 | tableView(menuView, didSelectRowAt: indexPath)
241 | }
242 |
243 | // MARK: - Actions
244 |
245 | @IBAction open func tap(_ sender: AnyObject) {
246 | delegate?.didTapInDropDownMenuBackground(self)
247 | }
248 |
249 | // If we declare a protocol method private, it is not called anymore.
250 | open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
251 | precondition(gestureRecognizer.view == self)
252 |
253 | guard let touchedView = touch.view else {
254 | return true
255 | }
256 | return !touchedView.isDescendant(of: menuView)
257 | }
258 |
259 | @IBAction open func show() {
260 | if !isHidden {
261 | return
262 | }
263 | transition.show.map { $0.before }.joined()()
264 | isHidden = false
265 | UIView.animate(withDuration: transition.duration,
266 | delay: transition.delay,
267 | options: transition.options,
268 | animations: transition.show.map { $0.change }.joined(),
269 | completion: { _ in self.transition.show.map { $0.after }.joined()() })
270 | }
271 |
272 | @IBAction open func hide() {
273 | if isHidden {
274 | return
275 | }
276 | transition.hide.map { $0.before }.joined()()
277 | UIView.animate(withDuration: transition.duration,
278 | delay: transition.delay,
279 | options: transition.options,
280 | animations: transition.hide.map { $0.change }.joined(),
281 | completion: { _ in
282 | self.transition.hide.map { $0.after }.joined()()
283 | self.isHidden = true
284 | })
285 | }
286 |
287 | // MARK: - Table View
288 |
289 | open func numberOfSections(in tableView: UITableView) -> Int {
290 | return 1
291 | }
292 |
293 | open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
294 | return menuCells.count
295 | }
296 |
297 | open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
298 | return menuCells[indexPath.row].rowHeight
299 | }
300 |
301 | open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
302 | return menuCells[indexPath.row]
303 | }
304 |
305 | open func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
306 | return menuCells[indexPath.row].menuAction != nil
307 | }
308 |
309 | open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
310 | let cell = menuCells[indexPath.row]
311 |
312 | for cell in menuCells {
313 | cell.accessoryType = .none
314 | }
315 | cell.accessoryType = cell.showsCheckmark ? .checkmark : .none
316 |
317 | tableView.deselectRow(at: indexPath, animated: true)
318 |
319 | guard let menuAction = cell.menuAction else {
320 | return
321 | }
322 |
323 | #if APP_EXTENSION
324 | guard let menuTarget = cell.menuTarget, menuTarget.responds(to: menuAction) else {
325 | return
326 | }
327 | _ = menuTarget.perform(menuAction, with: cell)
328 | #else
329 | UIApplication.shared.sendAction(menuAction, to: cell.menuTarget, from: cell, for: nil)
330 | #endif
331 | }
332 | }
333 |
334 | private extension Array where Element == DropDownMenu.Transition.Animation.Block {
335 | func joined() -> DropDownMenu.Transition.Animation.Block {
336 | return { self.forEach { $0() } }
337 | }
338 | }
339 |
--------------------------------------------------------------------------------
/DropDownMenuKit.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6009E2461D5119BF006424BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6009E2441D5119BF006424BD /* AppDelegate.swift */; };
11 | 6009E2471D5119BF006424BD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6009E2451D5119BF006424BD /* ViewController.swift */; };
12 | 6009E24F1D5119DF006424BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6009E2491D5119DF006424BD /* LaunchScreen.storyboard */; };
13 | 6009E2501D5119DF006424BD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6009E24B1D5119DF006424BD /* Main.storyboard */; };
14 | 600E8C8A1D5B759300AFD10E /* DropDownMenuKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 600E8C831D5B759300AFD10E /* DropDownMenuKit.framework */; };
15 | 600E8C8B1D5B759300AFD10E /* DropDownMenuKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 600E8C831D5B759300AFD10E /* DropDownMenuKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16 | 600E8C901D5B76AA00AFD10E /* DropDownMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6009E2401D511955006424BD /* DropDownMenu.swift */; };
17 | 600E8C911D5B76AC00AFD10E /* DropDownTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6009E2411D511955006424BD /* DropDownTitleView.swift */; };
18 | 600E8C961D5B78B800AFD10E /* DropDownMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = 600E8C941D5B78B800AFD10E /* DropDownMenu.h */; settings = {ATTRIBUTES = (Public, ); }; };
19 | 600E8CA71D5C938900AFD10E /* DropDownMenuCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 600E8CA51D5C935E00AFD10E /* DropDownMenuCell.swift */; };
20 | 60495BFF1D6C60FE0016987F /* DropDownMenuKit.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 600E8C921D5B76DD00AFD10E /* DropDownMenuKit.xcassets */; };
21 | 60495C001D6C61A70016987F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6009E2481D5119DF006424BD /* Assets.xcassets */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 600E8C881D5B759300AFD10E /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 6009E2221D5117FC006424BD /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 600E8C821D5B759300AFD10E;
30 | remoteInfo = DropDownMenu;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXCopyFilesBuildPhase section */
35 | 600E8C8F1D5B759300AFD10E /* Embed Frameworks */ = {
36 | isa = PBXCopyFilesBuildPhase;
37 | buildActionMask = 2147483647;
38 | dstPath = "";
39 | dstSubfolderSpec = 10;
40 | files = (
41 | 600E8C8B1D5B759300AFD10E /* DropDownMenuKit.framework in Embed Frameworks */,
42 | );
43 | name = "Embed Frameworks";
44 | runOnlyForDeploymentPostprocessing = 0;
45 | };
46 | /* End PBXCopyFilesBuildPhase section */
47 |
48 | /* Begin PBXFileReference section */
49 | 6009E22A1D5117FC006424BD /* DropDownMenuExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DropDownMenuExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
50 | 6009E2401D511955006424BD /* DropDownMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownMenu.swift; sourceTree = SOURCE_ROOT; };
51 | 6009E2411D511955006424BD /* DropDownTitleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownTitleView.swift; sourceTree = SOURCE_ROOT; };
52 | 6009E2441D5119BF006424BD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Example/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
53 | 6009E2451D5119BF006424BD /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = Example/ViewController.swift; sourceTree = SOURCE_ROOT; };
54 | 6009E2481D5119DF006424BD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Example/Assets.xcassets; sourceTree = SOURCE_ROOT; };
55 | 6009E24A1D5119DF006424BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Example/Base.lproj/LaunchScreen.storyboard; sourceTree = SOURCE_ROOT; };
56 | 6009E24C1D5119DF006424BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Example/Base.lproj/Main.storyboard; sourceTree = SOURCE_ROOT; };
57 | 6009E2521D5119F6006424BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Example/Info.plist; sourceTree = SOURCE_ROOT; };
58 | 6009E27A1D522802006424BD /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
59 | 6009E27B1D522802006424BD /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
60 | 600E8C831D5B759300AFD10E /* DropDownMenuKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DropDownMenuKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 600E8C921D5B76DD00AFD10E /* DropDownMenuKit.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DropDownMenuKit.xcassets; sourceTree = ""; };
62 | 600E8C941D5B78B800AFD10E /* DropDownMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DropDownMenu.h; sourceTree = ""; };
63 | 600E8C951D5B78B800AFD10E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
64 | 600E8CA21D5C7D1800AFD10E /* TODO.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = ""; };
65 | 600E8CA51D5C935E00AFD10E /* DropDownMenuCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownMenuCell.swift; sourceTree = ""; };
66 | 606F9FD61DB24FCB0071C87A /* NEWS.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = NEWS.md; sourceTree = ""; };
67 | /* End PBXFileReference section */
68 |
69 | /* Begin PBXFrameworksBuildPhase section */
70 | 6009E2271D5117FC006424BD /* Frameworks */ = {
71 | isa = PBXFrameworksBuildPhase;
72 | buildActionMask = 2147483647;
73 | files = (
74 | 600E8C8A1D5B759300AFD10E /* DropDownMenuKit.framework in Frameworks */,
75 | );
76 | runOnlyForDeploymentPostprocessing = 0;
77 | };
78 | 600E8C7F1D5B759300AFD10E /* Frameworks */ = {
79 | isa = PBXFrameworksBuildPhase;
80 | buildActionMask = 2147483647;
81 | files = (
82 | );
83 | runOnlyForDeploymentPostprocessing = 0;
84 | };
85 | /* End PBXFrameworksBuildPhase section */
86 |
87 | /* Begin PBXGroup section */
88 | 6009E2211D5117FC006424BD = {
89 | isa = PBXGroup;
90 | children = (
91 | 6009E27B1D522802006424BD /* README.md */,
92 | 606F9FD61DB24FCB0071C87A /* NEWS.md */,
93 | 600E8CA21D5C7D1800AFD10E /* TODO.md */,
94 | 6009E27A1D522802006424BD /* LICENSE */,
95 | 6009E2401D511955006424BD /* DropDownMenu.swift */,
96 | 600E8CA51D5C935E00AFD10E /* DropDownMenuCell.swift */,
97 | 6009E2411D511955006424BD /* DropDownTitleView.swift */,
98 | 600E8C941D5B78B800AFD10E /* DropDownMenu.h */,
99 | 600E8C951D5B78B800AFD10E /* Info.plist */,
100 | 600E8C921D5B76DD00AFD10E /* DropDownMenuKit.xcassets */,
101 | 6009E23F1D511939006424BD /* Example */,
102 | 6009E22B1D5117FC006424BD /* Products */,
103 | );
104 | sourceTree = "";
105 | usesTabs = 1;
106 | };
107 | 6009E22B1D5117FC006424BD /* Products */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 6009E22A1D5117FC006424BD /* DropDownMenuExample.app */,
111 | 600E8C831D5B759300AFD10E /* DropDownMenuKit.framework */,
112 | );
113 | name = Products;
114 | sourceTree = "";
115 | };
116 | 6009E23F1D511939006424BD /* Example */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 6009E2441D5119BF006424BD /* AppDelegate.swift */,
120 | 6009E2451D5119BF006424BD /* ViewController.swift */,
121 | 6009E2481D5119DF006424BD /* Assets.xcassets */,
122 | 6009E2491D5119DF006424BD /* LaunchScreen.storyboard */,
123 | 6009E24B1D5119DF006424BD /* Main.storyboard */,
124 | 6009E2521D5119F6006424BD /* Info.plist */,
125 | );
126 | path = Example;
127 | sourceTree = "";
128 | };
129 | /* End PBXGroup section */
130 |
131 | /* Begin PBXHeadersBuildPhase section */
132 | 600E8C801D5B759300AFD10E /* Headers */ = {
133 | isa = PBXHeadersBuildPhase;
134 | buildActionMask = 2147483647;
135 | files = (
136 | 600E8C961D5B78B800AFD10E /* DropDownMenu.h in Headers */,
137 | );
138 | runOnlyForDeploymentPostprocessing = 0;
139 | };
140 | /* End PBXHeadersBuildPhase section */
141 |
142 | /* Begin PBXNativeTarget section */
143 | 6009E2291D5117FC006424BD /* DropDownMenuExample */ = {
144 | isa = PBXNativeTarget;
145 | buildConfigurationList = 6009E23C1D5117FC006424BD /* Build configuration list for PBXNativeTarget "DropDownMenuExample" */;
146 | buildPhases = (
147 | 6009E2261D5117FC006424BD /* Sources */,
148 | 6009E2271D5117FC006424BD /* Frameworks */,
149 | 6009E2281D5117FC006424BD /* Resources */,
150 | 600E8C8F1D5B759300AFD10E /* Embed Frameworks */,
151 | );
152 | buildRules = (
153 | );
154 | dependencies = (
155 | 600E8C891D5B759300AFD10E /* PBXTargetDependency */,
156 | );
157 | name = DropDownMenuExample;
158 | productName = DropDownMenu;
159 | productReference = 6009E22A1D5117FC006424BD /* DropDownMenuExample.app */;
160 | productType = "com.apple.product-type.application";
161 | };
162 | 600E8C821D5B759300AFD10E /* DropDownMenuKit */ = {
163 | isa = PBXNativeTarget;
164 | buildConfigurationList = 600E8C8C1D5B759300AFD10E /* Build configuration list for PBXNativeTarget "DropDownMenuKit" */;
165 | buildPhases = (
166 | 600E8C7E1D5B759300AFD10E /* Sources */,
167 | 600E8C7F1D5B759300AFD10E /* Frameworks */,
168 | 600E8C801D5B759300AFD10E /* Headers */,
169 | 600E8C811D5B759300AFD10E /* Resources */,
170 | );
171 | buildRules = (
172 | );
173 | dependencies = (
174 | );
175 | name = DropDownMenuKit;
176 | productName = DropDownMenu;
177 | productReference = 600E8C831D5B759300AFD10E /* DropDownMenuKit.framework */;
178 | productType = "com.apple.product-type.framework";
179 | };
180 | /* End PBXNativeTarget section */
181 |
182 | /* Begin PBXProject section */
183 | 6009E2221D5117FC006424BD /* Project object */ = {
184 | isa = PBXProject;
185 | attributes = {
186 | LastSwiftUpdateCheck = 0730;
187 | LastUpgradeCheck = 1420;
188 | ORGANIZATIONNAME = "Quentin Mathé";
189 | TargetAttributes = {
190 | 6009E2291D5117FC006424BD = {
191 | CreatedOnToolsVersion = 7.3;
192 | LastSwiftMigration = 1020;
193 | ProvisioningStyle = Manual;
194 | };
195 | 600E8C821D5B759300AFD10E = {
196 | CreatedOnToolsVersion = 7.3;
197 | LastSwiftMigration = 1020;
198 | };
199 | };
200 | };
201 | buildConfigurationList = 6009E2251D5117FC006424BD /* Build configuration list for PBXProject "DropDownMenuKit" */;
202 | compatibilityVersion = "Xcode 3.2";
203 | developmentRegion = en;
204 | hasScannedForEncodings = 0;
205 | knownRegions = (
206 | en,
207 | Base,
208 | );
209 | mainGroup = 6009E2211D5117FC006424BD;
210 | productRefGroup = 6009E22B1D5117FC006424BD /* Products */;
211 | projectDirPath = "";
212 | projectRoot = "";
213 | targets = (
214 | 6009E2291D5117FC006424BD /* DropDownMenuExample */,
215 | 600E8C821D5B759300AFD10E /* DropDownMenuKit */,
216 | );
217 | };
218 | /* End PBXProject section */
219 |
220 | /* Begin PBXResourcesBuildPhase section */
221 | 6009E2281D5117FC006424BD /* Resources */ = {
222 | isa = PBXResourcesBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | 60495C001D6C61A70016987F /* Assets.xcassets in Resources */,
226 | 6009E2501D5119DF006424BD /* Main.storyboard in Resources */,
227 | 6009E24F1D5119DF006424BD /* LaunchScreen.storyboard in Resources */,
228 | );
229 | runOnlyForDeploymentPostprocessing = 0;
230 | };
231 | 600E8C811D5B759300AFD10E /* Resources */ = {
232 | isa = PBXResourcesBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | 60495BFF1D6C60FE0016987F /* DropDownMenuKit.xcassets in Resources */,
236 | );
237 | runOnlyForDeploymentPostprocessing = 0;
238 | };
239 | /* End PBXResourcesBuildPhase section */
240 |
241 | /* Begin PBXSourcesBuildPhase section */
242 | 6009E2261D5117FC006424BD /* Sources */ = {
243 | isa = PBXSourcesBuildPhase;
244 | buildActionMask = 2147483647;
245 | files = (
246 | 6009E2461D5119BF006424BD /* AppDelegate.swift in Sources */,
247 | 6009E2471D5119BF006424BD /* ViewController.swift in Sources */,
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | 600E8C7E1D5B759300AFD10E /* Sources */ = {
252 | isa = PBXSourcesBuildPhase;
253 | buildActionMask = 2147483647;
254 | files = (
255 | 600E8CA71D5C938900AFD10E /* DropDownMenuCell.swift in Sources */,
256 | 600E8C911D5B76AC00AFD10E /* DropDownTitleView.swift in Sources */,
257 | 600E8C901D5B76AA00AFD10E /* DropDownMenu.swift in Sources */,
258 | );
259 | runOnlyForDeploymentPostprocessing = 0;
260 | };
261 | /* End PBXSourcesBuildPhase section */
262 |
263 | /* Begin PBXTargetDependency section */
264 | 600E8C891D5B759300AFD10E /* PBXTargetDependency */ = {
265 | isa = PBXTargetDependency;
266 | target = 600E8C821D5B759300AFD10E /* DropDownMenuKit */;
267 | targetProxy = 600E8C881D5B759300AFD10E /* PBXContainerItemProxy */;
268 | };
269 | /* End PBXTargetDependency section */
270 |
271 | /* Begin PBXVariantGroup section */
272 | 6009E2491D5119DF006424BD /* LaunchScreen.storyboard */ = {
273 | isa = PBXVariantGroup;
274 | children = (
275 | 6009E24A1D5119DF006424BD /* Base */,
276 | );
277 | name = LaunchScreen.storyboard;
278 | sourceTree = "";
279 | };
280 | 6009E24B1D5119DF006424BD /* Main.storyboard */ = {
281 | isa = PBXVariantGroup;
282 | children = (
283 | 6009E24C1D5119DF006424BD /* Base */,
284 | );
285 | name = Main.storyboard;
286 | sourceTree = "";
287 | };
288 | /* End PBXVariantGroup section */
289 |
290 | /* Begin XCBuildConfiguration section */
291 | 6009E23A1D5117FC006424BD /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ALWAYS_SEARCH_USER_PATHS = NO;
295 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
296 | CLANG_ANALYZER_NONNULL = YES;
297 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
298 | CLANG_CXX_LIBRARY = "libc++";
299 | CLANG_ENABLE_MODULES = YES;
300 | CLANG_ENABLE_OBJC_ARC = YES;
301 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
302 | CLANG_WARN_BOOL_CONVERSION = YES;
303 | CLANG_WARN_COMMA = YES;
304 | CLANG_WARN_CONSTANT_CONVERSION = YES;
305 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
307 | CLANG_WARN_EMPTY_BODY = YES;
308 | CLANG_WARN_ENUM_CONVERSION = YES;
309 | CLANG_WARN_INFINITE_RECURSION = YES;
310 | CLANG_WARN_INT_CONVERSION = YES;
311 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
312 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
313 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
315 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
316 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
317 | CLANG_WARN_STRICT_PROTOTYPES = YES;
318 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
319 | CLANG_WARN_UNREACHABLE_CODE = YES;
320 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
321 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
322 | COPY_PHASE_STRIP = NO;
323 | DEBUG_INFORMATION_FORMAT = dwarf;
324 | ENABLE_STRICT_OBJC_MSGSEND = YES;
325 | ENABLE_TESTABILITY = YES;
326 | GCC_C_LANGUAGE_STANDARD = gnu99;
327 | GCC_DYNAMIC_NO_PIC = NO;
328 | GCC_NO_COMMON_BLOCKS = YES;
329 | GCC_OPTIMIZATION_LEVEL = 0;
330 | GCC_PREPROCESSOR_DEFINITIONS = (
331 | "DEBUG=1",
332 | "$(inherited)",
333 | );
334 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
335 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
336 | GCC_WARN_UNDECLARED_SELECTOR = YES;
337 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
338 | GCC_WARN_UNUSED_FUNCTION = YES;
339 | GCC_WARN_UNUSED_VARIABLE = YES;
340 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
341 | MTL_ENABLE_DEBUG_INFO = YES;
342 | ONLY_ACTIVE_ARCH = YES;
343 | SDKROOT = iphoneos;
344 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
345 | SWIFT_VERSION = 5.0;
346 | };
347 | name = Debug;
348 | };
349 | 6009E23B1D5117FC006424BD /* Release */ = {
350 | isa = XCBuildConfiguration;
351 | buildSettings = {
352 | ALWAYS_SEARCH_USER_PATHS = NO;
353 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
354 | CLANG_ANALYZER_NONNULL = YES;
355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
356 | CLANG_CXX_LIBRARY = "libc++";
357 | CLANG_ENABLE_MODULES = YES;
358 | CLANG_ENABLE_OBJC_ARC = YES;
359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
360 | CLANG_WARN_BOOL_CONVERSION = YES;
361 | CLANG_WARN_COMMA = YES;
362 | CLANG_WARN_CONSTANT_CONVERSION = YES;
363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
365 | CLANG_WARN_EMPTY_BODY = YES;
366 | CLANG_WARN_ENUM_CONVERSION = YES;
367 | CLANG_WARN_INFINITE_RECURSION = YES;
368 | CLANG_WARN_INT_CONVERSION = YES;
369 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
371 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
373 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
374 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
375 | CLANG_WARN_STRICT_PROTOTYPES = YES;
376 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
377 | CLANG_WARN_UNREACHABLE_CODE = YES;
378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
380 | COPY_PHASE_STRIP = NO;
381 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
382 | ENABLE_NS_ASSERTIONS = NO;
383 | ENABLE_STRICT_OBJC_MSGSEND = YES;
384 | GCC_C_LANGUAGE_STANDARD = gnu99;
385 | GCC_NO_COMMON_BLOCKS = YES;
386 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
387 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
388 | GCC_WARN_UNDECLARED_SELECTOR = YES;
389 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
390 | GCC_WARN_UNUSED_FUNCTION = YES;
391 | GCC_WARN_UNUSED_VARIABLE = YES;
392 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
393 | MTL_ENABLE_DEBUG_INFO = NO;
394 | SDKROOT = iphoneos;
395 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
396 | SWIFT_VERSION = 5.0;
397 | VALIDATE_PRODUCT = YES;
398 | };
399 | name = Release;
400 | };
401 | 6009E23D1D5117FC006424BD /* Debug */ = {
402 | isa = XCBuildConfiguration;
403 | buildSettings = {
404 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
405 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
406 | CODE_SIGN_STYLE = Manual;
407 | DEVELOPMENT_TEAM = "";
408 | INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist";
409 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
410 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
411 | PRODUCT_BUNDLE_IDENTIFIER = com.quentinmathe.DropDownMenuExample;
412 | PRODUCT_NAME = "$(TARGET_NAME)";
413 | PROVISIONING_PROFILE_SPECIFIER = "";
414 | };
415 | name = Debug;
416 | };
417 | 6009E23E1D5117FC006424BD /* Release */ = {
418 | isa = XCBuildConfiguration;
419 | buildSettings = {
420 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
422 | CODE_SIGN_STYLE = Manual;
423 | DEVELOPMENT_TEAM = "";
424 | INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist";
425 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
427 | PRODUCT_BUNDLE_IDENTIFIER = com.quentinmathe.DropDownMenuExample;
428 | PRODUCT_NAME = "$(TARGET_NAME)";
429 | PROVISIONING_PROFILE_SPECIFIER = "";
430 | };
431 | name = Release;
432 | };
433 | 600E8C8D1D5B759300AFD10E /* Debug */ = {
434 | isa = XCBuildConfiguration;
435 | buildSettings = {
436 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
437 | CURRENT_PROJECT_VERSION = 1;
438 | DEFINES_MODULE = YES;
439 | DYLIB_COMPATIBILITY_VERSION = 1;
440 | DYLIB_CURRENT_VERSION = 1;
441 | DYLIB_INSTALL_NAME_BASE = "@rpath";
442 | INFOPLIST_FILE = Info.plist;
443 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
444 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
445 | PRODUCT_BUNDLE_IDENTIFIER = com.quentinmathe.DropDownMenuKit;
446 | PRODUCT_NAME = "$(TARGET_NAME)";
447 | SKIP_INSTALL = YES;
448 | TARGETED_DEVICE_FAMILY = "1,2";
449 | VERSIONING_SYSTEM = "apple-generic";
450 | VERSION_INFO_PREFIX = "";
451 | };
452 | name = Debug;
453 | };
454 | 600E8C8E1D5B759300AFD10E /* Release */ = {
455 | isa = XCBuildConfiguration;
456 | buildSettings = {
457 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
458 | CURRENT_PROJECT_VERSION = 1;
459 | DEFINES_MODULE = YES;
460 | DYLIB_COMPATIBILITY_VERSION = 1;
461 | DYLIB_CURRENT_VERSION = 1;
462 | DYLIB_INSTALL_NAME_BASE = "@rpath";
463 | INFOPLIST_FILE = Info.plist;
464 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
466 | PRODUCT_BUNDLE_IDENTIFIER = com.quentinmathe.DropDownMenuKit;
467 | PRODUCT_NAME = "$(TARGET_NAME)";
468 | SKIP_INSTALL = YES;
469 | TARGETED_DEVICE_FAMILY = "1,2";
470 | VERSIONING_SYSTEM = "apple-generic";
471 | VERSION_INFO_PREFIX = "";
472 | };
473 | name = Release;
474 | };
475 | /* End XCBuildConfiguration section */
476 |
477 | /* Begin XCConfigurationList section */
478 | 6009E2251D5117FC006424BD /* Build configuration list for PBXProject "DropDownMenuKit" */ = {
479 | isa = XCConfigurationList;
480 | buildConfigurations = (
481 | 6009E23A1D5117FC006424BD /* Debug */,
482 | 6009E23B1D5117FC006424BD /* Release */,
483 | );
484 | defaultConfigurationIsVisible = 0;
485 | defaultConfigurationName = Release;
486 | };
487 | 6009E23C1D5117FC006424BD /* Build configuration list for PBXNativeTarget "DropDownMenuExample" */ = {
488 | isa = XCConfigurationList;
489 | buildConfigurations = (
490 | 6009E23D1D5117FC006424BD /* Debug */,
491 | 6009E23E1D5117FC006424BD /* Release */,
492 | );
493 | defaultConfigurationIsVisible = 0;
494 | defaultConfigurationName = Release;
495 | };
496 | 600E8C8C1D5B759300AFD10E /* Build configuration list for PBXNativeTarget "DropDownMenuKit" */ = {
497 | isa = XCConfigurationList;
498 | buildConfigurations = (
499 | 600E8C8D1D5B759300AFD10E /* Debug */,
500 | 600E8C8E1D5B759300AFD10E /* Release */,
501 | );
502 | defaultConfigurationIsVisible = 0;
503 | defaultConfigurationName = Release;
504 | };
505 | /* End XCConfigurationList section */
506 | };
507 | rootObject = 6009E2221D5117FC006424BD /* Project object */;
508 | }
509 |
--------------------------------------------------------------------------------