├── .DS_Store ├── TestMenu ├── .DS_Store ├── Assets.xcassets │ ├── Contents.json │ ├── dropdown_menu_triangle.imageset │ │ ├── triangle.png │ │ └── Contents.json │ ├── discovery_icon_investor.imageset │ │ ├── discovery_icon_investor@2x.png │ │ ├── discovery_icon_investor@3x.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Supporting Files │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Info.plist │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard ├── DropdownMenu │ ├── Resource │ │ └── DropdownMenu.xcassets │ │ │ ├── Contents.json │ │ │ ├── .DS_Store │ │ │ └── dropdown_menu_triangle.imageset │ │ │ ├── common_filter_arrow_down@2x.png │ │ │ ├── common_filter_arrow_down@3x.png │ │ │ └── Contents.json │ ├── Views │ │ ├── DropdownSubmenu.swift │ │ ├── DorpdownMainMenu.swift │ │ ├── DropdownSubmenuTableView.swift │ │ └── DropdownSubmenuCollectionView.swift │ ├── Utils │ │ ├── FontManager.swift │ │ ├── ColorManager.swift │ │ ├── AppColor+Utils.swift │ │ ├── UIView+Utils.swift │ │ └── UIView+AutoLayout.swift │ ├── DropdownMenu+Types.swift │ ├── DropdownMenuConfiguration.swift │ ├── DropdownMenuViewController+TableView.swift │ ├── DropdownMenuViewController+CollectionView.swift │ └── DropdownMenuViewController.swift ├── ViewController.swift └── DemoDropdownMenuManager.swift ├── screenshots └── screenshoot.gif ├── TestMenu.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── inatu.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcuserdata │ └── inatu.xcuserdatad │ │ ├── xcschemes │ │ ├── xcschememanagement.plist │ │ └── TestMenu.xcscheme │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── project.pbxproj ├── TestMenuTests ├── Info.plist └── TestMenuTests.swift └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/.DS_Store -------------------------------------------------------------------------------- /TestMenu/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/.DS_Store -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /screenshots/screenshoot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/screenshots/screenshoot.gif -------------------------------------------------------------------------------- /TestMenu/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/.DS_Store -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/dropdown_menu_triangle.imageset/triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/Assets.xcassets/dropdown_menu_triangle.imageset/triangle.png -------------------------------------------------------------------------------- /TestMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/discovery_icon_investor.imageset/discovery_icon_investor@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/Assets.xcassets/discovery_icon_investor.imageset/discovery_icon_investor@2x.png -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/discovery_icon_investor.imageset/discovery_icon_investor@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/Assets.xcassets/discovery_icon_investor.imageset/discovery_icon_investor@3x.png -------------------------------------------------------------------------------- /TestMenu.xcodeproj/project.xcworkspace/xcuserdata/inatu.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu.xcodeproj/project.xcworkspace/xcuserdata/inatu.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/dropdown_menu_triangle.imageset/common_filter_arrow_down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/dropdown_menu_triangle.imageset/common_filter_arrow_down@2x.png -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/dropdown_menu_triangle.imageset/common_filter_arrow_down@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shannon-s-Dreamland/DropdownMenu/HEAD/TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/dropdown_menu_triangle.imageset/common_filter_arrow_down@3x.png -------------------------------------------------------------------------------- /TestMenu/Supporting Files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/18/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/dropdown_menu_triangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "triangle.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 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Views/DropdownSubmenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownSubmenu.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 12/22/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Submenu 12 | protocol DropdownSubmenu: class { 13 | var title: String? { get } 14 | 15 | func reload(includeMainMenu: Bool) 16 | func removeFromSuperview() 17 | func selectItemAtIndex(index: DropdownSubmenuIndex) 18 | } 19 | -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/discovery_icon_investor.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "discovery_icon_investor@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "discovery_icon_investor@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Resource/DropdownMenu.xcassets/dropdown_menu_triangle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "common_filter_arrow_down@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "common_filter_arrow_down@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /TestMenu/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/18/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let menuViewController = DropdownMenuViewController() 14 | let menuManager = DemoDropdownMenuManager() 15 | 16 | override func viewDidLoad() { 17 | menuViewController.menuManager = menuManager 18 | menuManager.menuVC = menuViewController 19 | 20 | view.addSubview(menuViewController.view) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /TestMenu.xcodeproj/xcuserdata/inatu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TestMenu.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6162CD1F1C2407F90094A00B 16 | 17 | primary 18 | 19 | 20 | 6162CD331C2407F90094A00B 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TestMenuTests/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 | -------------------------------------------------------------------------------- /TestMenu.xcodeproj/xcuserdata/inatu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /TestMenuTests/TestMenuTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestMenuTests.swift 3 | // TestMenuTests 4 | // 5 | // Created by Shannon Wu on 12/18/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import TestMenu 11 | 12 | class TestMenuTests: 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 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Utils/FontManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontManager.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 10/26/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(watchOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import Cocoa 13 | #endif 14 | 15 | #if os(iOS) || os(watchOS) 16 | typealias AppFont = UIFont 17 | #elseif os(OSX) 18 | typealias AppFont = NSFont 19 | #endif 20 | 21 | struct FontManager { 22 | 23 | static var headline: AppFont { 24 | return AppFont.systemFontOfSize(18) 25 | } 26 | 27 | static var subheadline: AppFont { 28 | return AppFont.systemFontOfSize(17) 29 | } 30 | 31 | static var boldSubheadline: AppFont { 32 | return AppFont.boldSystemFontOfSize(17) 33 | } 34 | 35 | static var caption1: AppFont { 36 | return AppFont.systemFontOfSize(15) 37 | } 38 | 39 | static var body: AppFont { 40 | return AppFont.systemFontOfSize(14) 41 | } 42 | 43 | static var caption2: AppFont { 44 | return AppFont.systemFontOfSize(13) 45 | } 46 | 47 | static var footnote: AppFont { 48 | return AppFont.systemFontOfSize(11) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/DropdownMenu+Types.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownMenuManager.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 12/23/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Menu Manager 12 | 13 | protocol DropdownMenuManager: DropdownMenuDelegate { 14 | 15 | // Main Menu 16 | func intialTitlesForMainMenu() -> [String] 17 | 18 | // Submenu 19 | func numberOfSectionsInSubmenu(isMainSubmenu: Bool) -> Int 20 | func titleForSectionInSubmenu(section: Int) -> String? 21 | 22 | func numberOfRowsInSection(section: Int, isMainSubmenu: Bool) -> Int 23 | func titleForDropdownSubmenuIndex(index: DropdownSubmenuIndex) -> String? 24 | 25 | func didSelectSubmenuAtIndex(index: DropdownSubmenuIndex) 26 | 27 | var shrinkStateItemCount: Int { get } 28 | 29 | // Reload Data 30 | func reloadData() 31 | 32 | // Toggle Open 33 | func toggleOpenState(state: Bool) 34 | 35 | // Reset state 36 | func resetSelectionState() 37 | } 38 | 39 | // MARK: - Submenu Index 40 | 41 | struct DropdownSubmenuIndex { 42 | var main: Int? 43 | var secondary: Int? 44 | 45 | init(main: Int?, secondary: Int?) { 46 | self.main = main 47 | self.secondary = secondary 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 说明 3 | 4 | ![screenshot](screenshots/screenshoot.gif) 5 | 6 | 这是一个用面向对象写的二级下拉菜单的示例. 上面的图使我们即将发布的4.0版本的截图, 它只是用来演示这个 demo 能达到什么效果, 代码中不包含这一块业务的实现! 7 | 8 | 我现在正计划写 iOS 开发的两个专题, 分别是关于36氪4.0版本的程序架构和用面向对象的方法做UI开发, 这个示例属于后者, 你可以在[这个网址](http://www.jianshu.com/users/61cd67a3e447/latest_articles)找到所有的文章, 很遗憾, 因为我们4.0的开发任务比较繁重,尽管我最近经常晚上写代码到凌晨, 但是还是 一直没有时间总结这些东西, 我是计划春节后将这两个专题的文章都完成, 算是在36氪工作半年来的一个总结. 希望回家后我的小侄子不会缠着我吧! 9 | 10 | 你可以随时关注上面的博客地址来获取最新的更新. 11 | 12 | ## 关于这个示例 13 | 14 | 示例的很多代码, 除了`DemoDropdownMenuManager`这个是自己编造的之外, 其它都是用在了我们的程序开发中的生产代码, 虽然都是自己的劳动成果, 但是所有权属于 36氪. 15 | 16 | ## 示例的局限性 17 | 18 | 1. 里面的`DropdownMenuConfiguration`文件比较杂乱, 没有办法, 按照我们 UI 的说法, 他们追求1像素的精度, 尽管我觉得 title 的字体应该一样, 但是说服不了他们, 为了方便调节面, 所以分的很细. 他们应该不会看到这句话! 19 | 20 | 2. `UIView+AutoLayout`的代码会需要重构, 其实它的作用和[SnapKit](https://github.com/SnapKit/SnapKit)挺类似, 但是我不喜欢它的前缀语法, 觉得 Swift 的 Extension 特性已经能写出更自然的语法, 但是我对自动布局的认识还不全面, 打算研究苹果的文档和 [SnapKit](https://github.com/SnapKit/SnapKit) 后自己写一个布局的 Library. 21 | 22 | 3. 这个界面只是整个工程很小的一部分, 其实上面的数据交互, 包括具体的 MenuManager的实现, 网络请求的管理, 缓存数据等是更复杂的交互逻辑, 这些东西我是打算放在以后关于架构的文章中介绍.这个 Menu 的作用只是提供一个可扩展的 Menu 界面, 也就是它并没有任何的逻辑在里面. 23 | 24 | 4. Menu 做的比较基础, 但是已经留出了很充足的接口进行扩展,我觉得你可以轻松的扩展, 比如想做出现的动画, 就可以在 `DropdownMenuViewController`的`animateHideSubmenu()`和`animateShowSubmenu()`中自己实现动画, 或者使用第三方的[pop](https://github.com/facebook/pop)或者其它动画库. 25 | -------------------------------------------------------------------------------- /TestMenu/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /TestMenu/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Utils/ColorManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorManager.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 10/22/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(watchOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import Cocoa 13 | #endif 14 | 15 | struct ColorManager { 16 | 17 | static var title: AppColor { 18 | return AppColor(hex: 0x222c38) 19 | } 20 | 21 | static var content: AppColor { 22 | return AppColor(hex: 0x5a626d) 23 | } 24 | 25 | static var prompt: AppColor { 26 | return AppColor(hex: 0x969fa9) 27 | } 28 | 29 | static var divider: AppColor { 30 | return AppColor(hex: 0xe7e7e7) 31 | } 32 | 33 | static var background: AppColor { 34 | return AppColor(hex: 0xf9f9f9) 35 | } 36 | 37 | static var theme: AppColor { 38 | return AppColor(hex: 0x4285f4) 39 | } 40 | 41 | static var auxiliary: AppColor { 42 | return AppColor(hex: 0xff6e0e) 43 | } 44 | 45 | static var success: AppColor { 46 | return AppColor(hex: 0x31c27c) 47 | } 48 | 49 | static var failure: AppColor { 50 | return AppColor(hex: 0xf0633d) 51 | } 52 | 53 | static var empty: AppColor { 54 | return AppColor(hex: 0xcccccc) 55 | } 56 | 57 | static var lightBackground: AppColor { 58 | return AppColor(hex: 0xfafafa) 59 | } 60 | 61 | static var greenHighlight: AppColor { 62 | return AppColor(hex: 0x3da300) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /TestMenu/Supporting Files/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 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 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 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TestMenu/Supporting Files/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 | -------------------------------------------------------------------------------- /TestMenu/Supporting Files/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 | -------------------------------------------------------------------------------- /TestMenu/DemoDropdownMenuManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoDropdownMenuManager.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/19/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DemoDropdownMenuManager: DropdownMenuManager { 12 | 13 | weak var menuVC: DropdownMenuViewController? 14 | 15 | private var currentlySelectedIndex: Int? 16 | 17 | var shrinkStateItemCount: Int { 18 | return 12 19 | } 20 | 21 | func intialTitlesForMainMenu() -> [String] { 22 | return ["你好", "我不好", "你好不好", "好!"] 23 | } 24 | 25 | func mainMenuItemAtIndexSelected(index: Int) { 26 | if index == currentlySelectedIndex { 27 | menuVC?.animateHideSubmenu() 28 | resetSelectionState() 29 | return 30 | } 31 | currentlySelectedIndex = index 32 | if index == 0 { 33 | menuVC?.submenuType = .TableViewSingleView 34 | } 35 | else if index == 1 { 36 | menuVC?.submenuType = .TableViewDoubleView 37 | } else if index == 2{ 38 | menuVC?.submenuType = .CollectionView 39 | } else { 40 | menuVC?.submenuType = .TableViewSingleView 41 | } 42 | } 43 | 44 | func resetSelectionState() { 45 | currentlySelectedIndex = nil 46 | } 47 | 48 | func didSelectSubmenuAtIndex(index: DropdownSubmenuIndex) { 49 | print("\(index.main) \(index.secondary)") 50 | } 51 | 52 | func numberOfSectionsInSubmenu(isMainSubmenu: Bool) -> Int { 53 | return 2 54 | } 55 | 56 | func numberOfRowsInSection(section: Int, isMainSubmenu: Bool) -> Int { 57 | return 5 58 | } 59 | 60 | func titleForDropdownSubmenuIndex(index: DropdownSubmenuIndex) -> String? { 61 | return "现在好点了" 62 | } 63 | 64 | func titleForSectionInSubmenu(section: Int) -> String? { 65 | return "好多了@" 66 | } 67 | 68 | func toggleOpenState(state: Bool) {} 69 | 70 | func reloadData() {} 71 | } 72 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Utils/AppColor+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppColor+Utils.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 10/21/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(watchOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import Cocoa 13 | #endif 14 | 15 | #if os(iOS) || os(watchOS) 16 | /// AppColor 是 UIColor 的别名. 17 | typealias AppColor = UIColor 18 | #elseif os(OSX) 19 | /// AppColor 是 NSColor 的别名. 20 | typealias AppColor = NSColor 21 | #endif 22 | 23 | // MARK: - 扩展 UIColor 或者 NSColor, 包含一些常量和初始化方法. 24 | extension AppColor { 25 | 26 | /// 返回一个随机的 UIColor 或者 NSColor. 27 | static var randomColor: AppColor { 28 | let r = CGFloat(drand48()) 29 | let g = CGFloat(drand48()) 30 | let b = CGFloat(drand48()) 31 | 32 | return AppColor(red: r, green: g, blue: b, alpha: 1.0) 33 | } 34 | 35 | /** 36 | 通过颜色值的 Hex 字符串和 默认为1.0的 Alpha 值来初始化一个 UIColor 或者 NSColor. 37 | 38 | - parameter hexString: 颜色值的 Hex 字符串, 传入的字符串可以为 `FFFFFF` 39 | 或者 `#FFFFFF` 的形式. 40 | - parameter alpha: 颜色值的 alpha 值, 默认为 1.0 41 | 42 | - returns: 如果传入的 Hex 字符串有效, 则返回一个初始化成功的UIColor 或者 NSColor; 43 | 如果传入的 Hex 字符串无效, 则返回 nil. 44 | */ 45 | convenience init?(hexString: String, alpha: CGFloat = 1.0){ 46 | var result: UInt32 = 0 47 | 48 | let scanner = NSScanner(string: hexString) 49 | if hexString.characters.first == "#" { 50 | scanner.scanLocation = 1 51 | } else { 52 | scanner.scanLocation = 0 53 | } 54 | 55 | if !scanner.scanHexInt(&result) { return nil } 56 | 57 | self.init(hex: result, alpha: alpha) 58 | } 59 | 60 | /** 61 | 通过颜色值的 Hex 值和默认为1.0的 Alpha 值来初始化一个 UIColor 或者 NSColor. 62 | 63 | - parameter hex: 颜色值的 Hex值, 传入 UInt32类型的值. 64 | - parameter alpha: 颜色值的 alpha 值, 默认为 1.0 65 | 66 | - returns: 返回一个初始化成功的UIColor 或者 NSColor 67 | */ 68 | convenience init(hex: UInt32, alpha: CGFloat = 1.0) { 69 | let r = CGFloat(((hex & 0xFF0000) >> 16)) / 255.0 70 | let g = CGFloat(((hex & 0xFF00) >> 8)) / 255.0 71 | let b = CGFloat(((hex & 0xFF))) / 255.0 72 | 73 | self.init(red: r, green: g, blue: b, alpha: alpha) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Utils/UIView+Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Utils.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/24/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIRectEdge: Hashable { 12 | public var hashValue: Int { 13 | return Int(self.rawValue) 14 | } 15 | } 16 | 17 | extension UIView { 18 | func addBorder(edges edges: UIRectEdge, colour: UIColor = UIColor.whiteColor(), thickness: CGFloat = 1) -> [UIRectEdge: UIView] { 19 | var borders = [UIRectEdge: UIView]() 20 | 21 | func border() -> UIView { 22 | let border = UIView(frame: CGRectZero) 23 | border.backgroundColor = colour 24 | border.translatesAutoresizingMaskIntoConstraints = false 25 | return border 26 | } 27 | 28 | if edges.contains(.Top) || edges.contains(.All) { 29 | let top = border() 30 | addSubview(top) 31 | borders[.Top] = top 32 | top.addDimentionConstraints(.Height, constant: thickness) 33 | top.pinToSuperView(.Top, inset: 0, relation: .Equal) 34 | } 35 | 36 | if edges.contains(.Left) || edges.contains(.All) { 37 | let left = border() 38 | addSubview(left) 39 | borders[.Left] = left 40 | left.addDimentionConstraints(.Width, constant: thickness) 41 | left.pinToSuperView(.Leading, inset: 0, relation: .Equal) 42 | } 43 | 44 | if edges.contains(.Right) || edges.contains(.All) { 45 | let right = border() 46 | addSubview(right) 47 | borders[.Right] = right 48 | right.addDimentionConstraints(.Width, constant: thickness) 49 | right.pinToSuperView(.Trailing, inset: 0, relation: .Equal) 50 | } 51 | 52 | if edges.contains(.Bottom) || edges.contains(.All) { 53 | let bottom = border() 54 | addSubview(bottom) 55 | borders[.Bottom] = bottom 56 | bottom.addDimentionConstraints(.Height, constant: thickness) 57 | bottom.pinToSuperView(.Bottom, inset: 0, relation: .Equal) 58 | } 59 | 60 | return borders 61 | } 62 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/DropdownMenuConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownMenuConfiguration.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Menu Item 12 | 13 | struct DropdownMenuConfiguration { 14 | // MARK: Main Menu 15 | static let mainMenuFrame = CGRect(x: 0, y: 64.0, width: UIScreen.mainScreen().bounds.width, height: 50.0) 16 | static let mainMenuHeight: CGFloat = 50.0 17 | static let mainMenuBackgoundColor = UIColor.whiteColor() 18 | static let mainMenuBorderWidth: CGFloat = 1.0 19 | static let mainMenuBorderColor = ColorManager.divider 20 | static let mainMenuFont = FontManager.caption2 21 | static let mainMenuTopOffset: CGFloat = 64.0 22 | 23 | // MARK: Submenu 24 | static let mainSubmenuBackgroundColor = ColorManager.lightBackground 25 | 26 | // MARK: TableViewSubmenu 27 | static let tableViewMainSubMenuLeftOffSet: CGFloat = 44.0 28 | static let tableViewSecondarySubMenuLeftOffSet: CGFloat = 30.0 29 | 30 | // MARK: CollectionViewSubmenu 31 | static let collectionViewSubmenuBodyFont = FontManager.body 32 | static let collectionViewSubmenuHeaderFont = FontManager.caption1 33 | static let collectionViewSubmenuSideMargin: CGFloat = 15.0 34 | static let collectionViewSubmenuTopMargin: CGFloat = 13.0 35 | static let collectionViewSubmenuFinishButtonFont = FontManager.caption1 36 | static let collectionViewSubmenuHeaderReferenceSize = CGSize(width: 0, height: 45.0) 37 | static let collectionViewSubmenuEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 0.0, right: 15.0) 38 | static let collectionViewSubmenuItemSize = CGSize(width: 80.0, height: 33.0) 39 | static let collectionViewSubmenuMinimumInteritemSpacing: CGFloat = 12.0 40 | static let collectionViewSubmenuMinimumLineSpacing: CGFloat = 15.0 41 | static let collectionViewSubmenuFirstSectionSize = CGSize(width: 0, height: 15.0) 42 | static let collectionViewSubmenuSecondSectionSize = CGSize(width: 0, height: 108.0) 43 | 44 | // MARK: Identifier 45 | static let cellIdentifier = "Cell" 46 | static let headerIdentifier = "Header" 47 | static let decorationFooterIdentifier = "DecorationFooter" 48 | static let doneFooterIdentifier = "DoneIdentifier" 49 | 50 | // MARK: Overall 51 | static let normalColor = ColorManager.content 52 | static let highlightColor = ColorManager.theme 53 | static let separatorColor = ColorManager.divider 54 | static let headerFont = FontManager.caption1 55 | static let triangleImage = UIImage(named: "dropdown_menu_triangle") 56 | 57 | static let secondarySubmenuBackgroundColor = UIColor.whiteColor() 58 | static let space: CGFloat = 3.0 59 | static let tableViewCellHeight: CGFloat = 50.0 60 | 61 | // MARK: Background 62 | static let backgroundViewColor = UIColor.blackColor() 63 | static let backgroundViewAlpha: CGFloat = 0.3 64 | static let backgroundViewMinimumHeight: CGFloat = 120.0 65 | } 66 | -------------------------------------------------------------------------------- /TestMenu.xcodeproj/xcuserdata/inatu.xcuserdatad/xcschemes/TestMenu.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Views/DorpdownMainMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DorpdownMenuMainMenu.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Main Menu Item 12 | 13 | class DropdownMenuItem: UIView { 14 | // MARK: Properties 15 | 16 | private var triangleImageView: UIImageView = { 17 | let image = UIImage(named: "dropdown_menu_triangle")?.imageWithRenderingMode(.AlwaysTemplate) 18 | let imageView = UIImageView(image: image) 19 | imageView.tintColor = DropdownMenuConfiguration.normalColor 20 | imageView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.5) 21 | imageView.translatesAutoresizingMaskIntoConstraints = false 22 | return imageView 23 | }() 24 | 25 | private var titleLabel: UILabel = { 26 | let label = UILabel() 27 | label.font = DropdownMenuConfiguration.mainMenuFont 28 | label.translatesAutoresizingMaskIntoConstraints = false 29 | return label 30 | }() 31 | 32 | private var selected: Bool = false { 33 | didSet { 34 | if selected { 35 | titleLabel.textColor = DropdownMenuConfiguration.highlightColor 36 | triangleImageView.tintColor = DropdownMenuConfiguration.highlightColor 37 | triangleImageView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI)) 38 | } 39 | else { 40 | titleLabel.textColor = DropdownMenuConfiguration.normalColor 41 | triangleImageView.tintColor = DropdownMenuConfiguration.normalColor 42 | triangleImageView.transform = CGAffineTransformMakeRotation(0) 43 | } 44 | } 45 | } 46 | 47 | // MARK: Initializers 48 | 49 | init(title: String) { 50 | super.init(frame: CGRect.zero) 51 | 52 | titleLabel.text = title 53 | commonInit() 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | func commonInit() { 61 | 62 | addSubview(triangleImageView) 63 | addSubview(titleLabel) 64 | 65 | titleLabel.positionToSuperView(horizontal: .Center, hOffset: 0, vertical: .Center, vOffset: 0) 66 | triangleImageView.positionRelativeToView(titleLabel, direction: .Trailing, space: DropdownMenuConfiguration.space) 67 | } 68 | 69 | } 70 | 71 | // MARK: - Main Menu Delegate 72 | 73 | protocol DropdownMenuDelegate: class { 74 | func mainMenuItemAtIndexSelected(index: Int) 75 | } 76 | 77 | // MARK: - Main Menu 78 | 79 | class DropdownMenu: UIView { 80 | // MARK: Properties 81 | 82 | private let menuItems: [DropdownMenuItem] 83 | private var selectedItem: DropdownMenuItem? 84 | 85 | weak var delegate: DropdownMenuDelegate? 86 | 87 | // MARK: Initializers 88 | 89 | init(items: [String], delegate: DropdownMenuDelegate) { 90 | self.delegate = delegate 91 | 92 | var menuItems = [DropdownMenuItem]() 93 | for (index, title) in items.enumerate() { 94 | let item = DropdownMenuItem(title: title) 95 | item.tag = index 96 | item.translatesAutoresizingMaskIntoConstraints = false 97 | menuItems.append(item) 98 | } 99 | self.menuItems = menuItems 100 | 101 | super.init(frame: CGRect.zero) 102 | 103 | commonInit() 104 | } 105 | 106 | required init?(coder aDecoder: NSCoder) { 107 | fatalError("init(coder:) has not been implemented") 108 | } 109 | 110 | func commonInit() { 111 | 112 | backgroundColor = DropdownMenuConfiguration.mainMenuBackgoundColor 113 | layer.borderWidth = DropdownMenuConfiguration.mainMenuBorderWidth 114 | layer.borderColor = DropdownMenuConfiguration.mainMenuBorderColor.CGColor 115 | 116 | menuItems.forEach { item in 117 | addSubview(item) 118 | } 119 | 120 | if menuItems.count != 0 { 121 | let menuItemsTuple: [ViewTuple] = menuItems.map { item in 122 | return (item, 1.0) 123 | } 124 | LayoutKit.arrangeViews(.Horizontal, viewsTuple: menuItemsTuple) 125 | } 126 | } 127 | 128 | // MARK: Methods 129 | 130 | override func touchesBegan(touches: Set, withEvent event: UIEvent?) { 131 | guard let touch = touches.first else { return } 132 | 133 | let position = touch.locationInView(self) 134 | 135 | guard let touchedView = hitTest(position, withEvent: event) as? DropdownMenuItem else { return } 136 | 137 | touchedView.selected = true 138 | if selectedItem != touchedView { 139 | selectedItem?.selected = false 140 | } 141 | selectedItem = touchedView 142 | 143 | delegate?.mainMenuItemAtIndexSelected(touchedView.tag) 144 | } 145 | 146 | func setTitleForSelectedItem(title: String?) { 147 | if let title = title { 148 | selectedItem?.titleLabel.text = title 149 | } 150 | selectedItem?.selected = false 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/DropdownMenuViewController+TableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownMenuViewController+TableView.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: DropdownMenu + UITableViewDelegate 12 | 13 | extension DropdownMenuViewController: UITableViewDelegate { 14 | func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 15 | return DropdownMenuConfiguration.tableViewCellHeight 16 | } 17 | 18 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 19 | guard let submenuType = submenuType else { 20 | assertionFailure("在没有设置 SubMenu 的时候, 不应该调到这个方法") 21 | return 22 | } 23 | 24 | switch submenuType { 25 | case .TableViewSingleView: 26 | menuManager?.didSelectSubmenuAtIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 27 | animateHideSubmenu() 28 | case .TableViewDoubleView: 29 | if let type = DropdownTableViewSubmenuDoubleViewType(rawValue: tableView.tag) { 30 | switch type { 31 | case .Main: 32 | menuManager?.didSelectSubmenuAtIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 33 | case .Secondary: 34 | menuManager?.didSelectSubmenuAtIndex(DropdownSubmenuIndex(main: nil, secondary: indexPath.row)) 35 | animateHideSubmenu() 36 | } 37 | } 38 | case .CollectionView: 39 | assertionFailure("不应该请求到 CollectionView") 40 | } 41 | } 42 | } 43 | 44 | // MARK: DropdownMenu + UITableViewDataSource 45 | 46 | extension DropdownMenuViewController: UITableViewDataSource { 47 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 48 | guard let submenuType = submenuType else { 49 | assertionFailure("在没有设置 SubMenu 的时候, 不应该调到这个方法") 50 | return 0 51 | } 52 | 53 | switch submenuType { 54 | case .TableViewSingleView: 55 | return menuManager?.numberOfSectionsInSubmenu(true) ?? 0 56 | case .TableViewDoubleView: 57 | if let type = DropdownTableViewSubmenuDoubleViewType(rawValue: tableView.tag) { 58 | switch type { 59 | case .Main: 60 | return menuManager?.numberOfSectionsInSubmenu(true) ?? 0 61 | case .Secondary: 62 | return menuManager?.numberOfSectionsInSubmenu(false) ?? 0 63 | } 64 | } 65 | assertionFailure("必须要获取到 Type") 66 | return 0 67 | case .CollectionView: 68 | assertionFailure("不应该请求到 CollectionView") 69 | return 0 70 | } 71 | } 72 | 73 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 74 | guard let submenuType = submenuType else { 75 | assertionFailure("在没有设置 SubMenu 的时候, 不应该调到这个方法") 76 | return 0 77 | } 78 | switch submenuType { 79 | case .TableViewSingleView: 80 | return menuManager?.numberOfRowsInSection(section, isMainSubmenu: true) ?? 0 81 | case .TableViewDoubleView: 82 | if let type = DropdownTableViewSubmenuDoubleViewType(rawValue: tableView.tag) { 83 | switch type { 84 | case .Main: 85 | return menuManager?.numberOfRowsInSection(section, isMainSubmenu: true) ?? 0 86 | case .Secondary: 87 | return menuManager?.numberOfRowsInSection(section, isMainSubmenu: false) ?? 0 88 | } 89 | } 90 | assertionFailure("必须要获取到 Type") 91 | return 0 92 | default: 93 | assertionFailure("不应该请求到 CollectionView") 94 | return 0 95 | } 96 | } 97 | 98 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 99 | guard let submenuType = submenuType else { 100 | assertionFailure("在没有设置 SubMenu 的时候, 不应该调到这个方法") 101 | return UITableViewCell() 102 | } 103 | switch submenuType { 104 | case .TableViewSingleView: 105 | if let cell = tableView.dequeueReusableCellWithIdentifier(DropdownMenuConfiguration.cellIdentifier) as? DropdownTableViewSubmenuSinlgeViewCell { 106 | cell.titleLabel.text = menuManager?.titleForDropdownSubmenuIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 107 | return cell 108 | } 109 | case .TableViewDoubleView: 110 | if let type = DropdownTableViewSubmenuDoubleViewType(rawValue: tableView.tag) { 111 | switch type { 112 | case .Main: 113 | if let cell = tableView.dequeueReusableCellWithIdentifier(DropdownMenuConfiguration.cellIdentifier) as? DropdownSubmenuMainViewCell { 114 | cell.titleLabel.text = menuManager?.titleForDropdownSubmenuIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 115 | return cell 116 | } 117 | case .Secondary: 118 | if let cell = tableView.dequeueReusableCellWithIdentifier(DropdownMenuConfiguration.cellIdentifier) as? DropdownSubmenuSecondaryViewCell { 119 | cell.titleLabel.text = menuManager?.titleForDropdownSubmenuIndex(DropdownSubmenuIndex(main: nil, secondary: indexPath.row)) 120 | return cell 121 | } 122 | } 123 | } else { 124 | assertionFailure("必须要获取到 Type") 125 | } 126 | case .CollectionView: 127 | assertionFailure("不应该请求到 CollectionView") 128 | } 129 | assertionFailure("没有获取到正确的 Cell") 130 | return UITableViewCell() 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/DropdownMenuViewController+CollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownMenuViewController+CollectionView.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: DropdownMenu + CollectionViewSectionHeaderDelegate 12 | 13 | extension DropdownMenuViewController: DropdownCollectionViewSectionHeaderDelegate { 14 | func toggleSectionContentHiddenState(state: Bool) { 15 | menuManager?.toggleOpenState(state) 16 | } 17 | } 18 | 19 | // MARK: DropdownMenu + UICollectionViewDelegate 20 | 21 | extension DropdownMenuViewController: UICollectionViewDelegate { 22 | func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { 23 | if let indexPaths = collectionView.indexPathsForSelectedItems() { 24 | for oldIndexPath in indexPaths { 25 | if oldIndexPath.section == indexPath.section && oldIndexPath.row != indexPath.row { 26 | collectionView.deselectItemAtIndexPath(oldIndexPath, animated: true) 27 | } 28 | } 29 | } 30 | 31 | if indexPath.section == 0 { 32 | menuManager?.didSelectSubmenuAtIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 33 | } 34 | else if indexPath.section == 1 { 35 | menuManager?.didSelectSubmenuAtIndex(DropdownSubmenuIndex(main: nil, secondary: indexPath.row)) 36 | } 37 | } 38 | } 39 | 40 | // MARK: DropdownMenu + UICollectionViewDataSource 41 | 42 | extension DropdownMenuViewController: UICollectionViewDataSource { 43 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 44 | return menuManager?.numberOfSectionsInSubmenu(true) ?? 0 45 | } 46 | 47 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 48 | return menuManager?.numberOfRowsInSection(section, isMainSubmenu: true) ?? 0 49 | } 50 | 51 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 52 | if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(DropdownMenuConfiguration.cellIdentifier, forIndexPath: indexPath) as? DropdownCollectionViewCell { 53 | if indexPath.section == 0 { 54 | cell.titleLabel.text = menuManager?.titleForDropdownSubmenuIndex(DropdownSubmenuIndex(main: indexPath.row, secondary: nil)) 55 | } else { 56 | cell.titleLabel.text = menuManager?.titleForDropdownSubmenuIndex(DropdownSubmenuIndex(main: nil, secondary: indexPath.row)) 57 | } 58 | return cell 59 | } else { 60 | assertionFailure("初始化 Collection View Cell 失败") 61 | return UICollectionViewCell() 62 | } 63 | } 64 | 65 | func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { 66 | if kind == UICollectionElementKindSectionHeader { 67 | if let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: DropdownMenuConfiguration.headerIdentifier, forIndexPath: indexPath) as? DropdownCollectionViewSectionHeader { 68 | header.titleLabel.text = menuManager?.titleForSectionInSubmenu(indexPath.section) 69 | 70 | if indexPath.section == 0 { 71 | collectionViewHeaders = [] 72 | } 73 | collectionViewHeaders.append(header) 74 | 75 | return header 76 | } 77 | } 78 | else if kind == UICollectionElementKindSectionFooter { 79 | if indexPath.section == 0 { 80 | if let footer = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionFooter, withReuseIdentifier: DropdownMenuConfiguration.decorationFooterIdentifier, forIndexPath: indexPath) as? DropdownCollectionViewSectionDecorationFooter { 81 | return footer 82 | } 83 | } 84 | else if indexPath.section == 1 { 85 | if let footer = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionFooter, withReuseIdentifier: DropdownMenuConfiguration.doneFooterIdentifier, forIndexPath: indexPath) as? DropdownCollectionViewSectionDoneFooter { 86 | footer.delegate = self 87 | return footer 88 | } 89 | } 90 | } 91 | assertionFailure("初始化 Collection View Footer 或者 Header 失败") 92 | return UICollectionReusableView() 93 | } 94 | 95 | func collectionView(collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, atIndexPath indexPath: NSIndexPath) { 96 | if elementKind == UICollectionElementKindSectionHeader { 97 | let itemCount = collectionView.numberOfItemsInSection(indexPath.section) 98 | if let shrinkItemCount = menuManager?.shrinkStateItemCount { 99 | toggleExpendButtonState(itemCount < shrinkItemCount ? true : false, atSection: indexPath.section) 100 | } 101 | } 102 | } 103 | } 104 | 105 | // MARK: DropdownMenu + DropdownCollectionViewSectionDoneFooterDelegate 106 | 107 | extension DropdownMenuViewController: DropdownCollectionViewSectionDoneFooterDelegate { 108 | func submenuSelectionFinished() { 109 | animateHideSubmenu() 110 | } 111 | } 112 | 113 | // MARK: DropdownMenu + UICollectionViewDelegateFlowLayout 114 | 115 | extension DropdownMenuViewController: UICollectionViewDelegateFlowLayout { 116 | func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { 117 | if section == 0 { 118 | return DropdownMenuConfiguration.collectionViewSubmenuFirstSectionSize 119 | } 120 | else if section == 1 { 121 | return DropdownMenuConfiguration.collectionViewSubmenuSecondSectionSize 122 | } 123 | assertionFailure("不应该请求到这个地方") 124 | return CGSize.zero 125 | } 126 | } 127 | 128 | // MARK: DropdownMenu + Header 129 | extension DropdownMenuViewController { 130 | func toggleExpendButtonState(state: Bool, atSection section: Int) { 131 | guard section < collectionViewHeaders.count || section > 0 else { 132 | assertionFailure("请求不太合法") 133 | return 134 | } 135 | 136 | let header = collectionViewHeaders[section] 137 | header.expandButton.hidden = state 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/DropdownMenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DiscoveryMenuViewController.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/18/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Menu View Controller 12 | 13 | class DropdownMenuViewController: UIViewController { 14 | // MARK: Types 15 | 16 | enum SubmenuType { 17 | case TableViewSingleView 18 | case TableViewDoubleView 19 | case CollectionView 20 | } 21 | 22 | // MARK: Properties 23 | 24 | weak var menuManager: DropdownMenuManager? 25 | var currentlySelectedSubmenu: DropdownSubmenu? 26 | var mainMenu: DropdownMenu! 27 | var backgroundView: UIView? 28 | var collectionViewHeaders = [DropdownCollectionViewSectionHeader]() 29 | 30 | var submenuType: SubmenuType? { 31 | willSet { 32 | if let submenuType = submenuType { 33 | switch submenuType { 34 | case .TableViewSingleView: 35 | tableViewSubmenuSinlgeView.removeFromSuperview() 36 | case .TableViewDoubleView: 37 | tableViewSubmenuDoubleView.removeFromSuperview() 38 | case .CollectionView: 39 | collectionViewSubmenuView.removeFromSuperview() 40 | } 41 | } 42 | } 43 | 44 | didSet { 45 | if let submenuType = submenuType { 46 | switch submenuType { 47 | case .TableViewSingleView: 48 | view.addSubview(tableViewSubmenuSinlgeView) 49 | currentlySelectedSubmenu = tableViewSubmenuSinlgeView 50 | tableViewSubmenuSinlgeView.reload() 51 | case .TableViewDoubleView: 52 | view.addSubview(tableViewSubmenuDoubleView) 53 | currentlySelectedSubmenu = tableViewSubmenuDoubleView 54 | tableViewSubmenuDoubleView.reload() 55 | case .CollectionView: 56 | view.addSubview(collectionViewSubmenuView) 57 | currentlySelectedSubmenu = collectionViewSubmenuView 58 | collectionViewSubmenuView.reload() 59 | } 60 | } 61 | animateShowSubmenu() 62 | } 63 | } 64 | 65 | lazy var tableViewSubmenuSinlgeView: DropdownTableViewSubmenuSinlgeView = { 66 | let submenu = DropdownTableViewSubmenuSinlgeView() 67 | submenu.delegate = self 68 | submenu.dataSource = self 69 | return submenu 70 | }() 71 | 72 | lazy var tableViewSubmenuDoubleView: DropdownTableViewSubmenuDoubleView = { 73 | let submenu = DropdownTableViewSubmenuDoubleView() 74 | submenu.delegate = self 75 | submenu.dataSource = self 76 | return submenu 77 | }() 78 | 79 | lazy var collectionViewSubmenuView: DropdownCollectionViewSubmenu = { 80 | let layout = UICollectionViewFlowLayout() 81 | layout.scrollDirection = .Vertical 82 | layout.minimumLineSpacing = DropdownMenuConfiguration.collectionViewSubmenuMinimumLineSpacing 83 | layout.minimumInteritemSpacing = DropdownMenuConfiguration.collectionViewSubmenuMinimumInteritemSpacing 84 | layout.itemSize = DropdownMenuConfiguration.collectionViewSubmenuItemSize 85 | layout.sectionInset = DropdownMenuConfiguration.collectionViewSubmenuEdgeInsets 86 | layout.headerReferenceSize = DropdownMenuConfiguration.collectionViewSubmenuHeaderReferenceSize 87 | let submenu = DropdownCollectionViewSubmenu(frame: CGRect.zero, collectionViewLayout: layout) 88 | submenu.allowsMultipleSelection = true 89 | submenu.delegate = self 90 | submenu.dataSource = self 91 | return submenu 92 | }() 93 | 94 | 95 | //MARK: View Life Cycle 96 | override func viewDidLoad() { 97 | super.viewDidLoad() 98 | 99 | // Configure background view 100 | backgroundView = UIView() 101 | backgroundView!.translatesAutoresizingMaskIntoConstraints = false 102 | backgroundView!.backgroundColor = DropdownMenuConfiguration.backgroundViewColor 103 | backgroundView!.alpha = DropdownMenuConfiguration.backgroundViewAlpha 104 | view.addSubview(backgroundView!) 105 | 106 | let tapGesture = UITapGestureRecognizer(target: self, action: "animateHideSubmenu") 107 | backgroundView?.addGestureRecognizer(tapGesture) 108 | 109 | backgroundView!.fillSuperView(.Both) 110 | 111 | // Configure menu manager 112 | guard let menuManager = menuManager else { return } 113 | mainMenu = DropdownMenu(items: menuManager.intialTitlesForMainMenu(), delegate: menuManager) 114 | mainMenu.translatesAutoresizingMaskIntoConstraints = false 115 | view.addSubview(mainMenu) 116 | 117 | mainMenu.addDimentionConstraints(.Height, constant: DropdownMenuConfiguration.mainMenuHeight) 118 | mainMenu.pinToSuperView(.Top, inset: 0, relation: .Equal) 119 | 120 | // Configure view 121 | view.frame = DropdownMenuConfiguration.mainMenuFrame 122 | } 123 | 124 | //MARK: Convenience + Menu Frame 125 | 126 | func animateShowSubmenu() { 127 | guard let submenuType = submenuType else { 128 | assertionFailure("在没有设置 Submenu 的时候调用到这里") 129 | return 130 | } 131 | 132 | let submenu: UIView 133 | switch submenuType { 134 | case .TableViewSingleView: 135 | submenu = tableViewSubmenuSinlgeView 136 | case .TableViewDoubleView: 137 | submenu = tableViewSubmenuDoubleView 138 | case .CollectionView: 139 | submenu = collectionViewSubmenuView 140 | } 141 | 142 | if let superView = view.superview { 143 | view.frame.size = CGSize(width: superView.bounds.width, height: superView.bounds.height - DropdownMenuConfiguration.mainMenuTopOffset) 144 | } 145 | 146 | submenu.positionRelativeToView(mainMenu, direction: .Bottom, space: 0.0) 147 | submenu.pinToSuperView(.Bottom, inset: DropdownMenuConfiguration.backgroundViewMinimumHeight, relation: .GreaterThanOrEqual) 148 | } 149 | 150 | func animateHideSubmenu() { 151 | currentlySelectedSubmenu?.removeFromSuperview() 152 | view.frame = DropdownMenuConfiguration.mainMenuFrame 153 | 154 | mainMenu.setTitleForSelectedItem(currentlySelectedSubmenu?.title) 155 | menuManager?.resetSelectionState() 156 | menuManager?.reloadData() 157 | } 158 | 159 | //MARK: Convenience + Menu State 160 | 161 | func reloadSubmenu(includeMainMenu: Bool = false) { 162 | if let submenuType = submenuType { 163 | switch submenuType { 164 | case .TableViewSingleView: 165 | tableViewSubmenuSinlgeView.reload(includeMainMenu) 166 | case .TableViewDoubleView: 167 | tableViewSubmenuDoubleView.reload(includeMainMenu) 168 | case .CollectionView: 169 | collectionViewSubmenuView.reload(includeMainMenu) 170 | } 171 | } 172 | } 173 | 174 | func setTitleForMainMenu(title: String) { 175 | mainMenu.setTitleForSelectedItem(title) 176 | } 177 | 178 | func selectItemAtIndex(index: DropdownSubmenuIndex) { 179 | currentlySelectedSubmenu?.selectItemAtIndex(index) 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Views/DropdownSubmenuTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownSubmenuTableView.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Base Cell 12 | class DropdownSubmenuBaseCell: UITableViewCell { 13 | // MARK: Properties 14 | 15 | let titleLabel: UILabel = { 16 | let label = UILabel(frame: CGRect.zero) 17 | label.font = DropdownMenuConfiguration.headerFont 18 | label.translatesAutoresizingMaskIntoConstraints = false 19 | return label 20 | }() 21 | 22 | // MARK: Initializers 23 | 24 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 25 | super.init(style: style, reuseIdentifier: reuseIdentifier) 26 | 27 | commonInit() 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | func commonInit() { 35 | selectionStyle = .None 36 | } 37 | } 38 | 39 | // MARK: - Main View Cell 40 | 41 | class DropdownSubmenuMainViewCell: DropdownSubmenuBaseCell { 42 | // MARK: Initializers 43 | 44 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 45 | super.init(style: style, reuseIdentifier: reuseIdentifier) 46 | 47 | commonInit() 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | override func commonInit() { 55 | super.commonInit() 56 | 57 | addSubview(titleLabel) 58 | titleLabel.positionToSuperView(horizontal: .Left, hOffset: DropdownMenuConfiguration.tableViewMainSubMenuLeftOffSet, vertical: .Center, vOffset: 0.0) 59 | 60 | borders = addBorder(edges: [.Right, .Bottom], colour: DropdownMenuConfiguration.separatorColor, thickness: 1.0) 61 | backgroundColor = DropdownMenuConfiguration.mainSubmenuBackgroundColor 62 | 63 | } 64 | 65 | // MARK: Properties 66 | var borders: [UIRectEdge: UIView]? 67 | 68 | // MARK: Conviences 69 | 70 | override func setSelected(selected: Bool, animated: Bool) { 71 | if selected { 72 | if let border = borders?[.Right] { 73 | border.backgroundColor = UIColor.whiteColor() 74 | border.setNeedsDisplay() 75 | } 76 | backgroundColor = UIColor.whiteColor() 77 | titleLabel.textColor = DropdownMenuConfiguration.highlightColor 78 | } else { 79 | if let border = borders?[.Right] { 80 | border.backgroundColor = DropdownMenuConfiguration.separatorColor 81 | border.setNeedsDisplay() 82 | } 83 | backgroundColor = DropdownMenuConfiguration.mainSubmenuBackgroundColor 84 | titleLabel.textColor = DropdownMenuConfiguration.normalColor 85 | } 86 | } 87 | 88 | } 89 | 90 | 91 | // MARK: - Secondary View Cell + Single View Cell 92 | 93 | typealias DropdownTableViewSubmenuSinlgeViewCell = DropdownSubmenuSecondaryViewCell 94 | 95 | class DropdownSubmenuSecondaryViewCell: DropdownSubmenuBaseCell { 96 | // MARK: Initializers 97 | 98 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 99 | super.init(style: style, reuseIdentifier: reuseIdentifier) 100 | 101 | commonInit() 102 | } 103 | 104 | required init?(coder aDecoder: NSCoder) { 105 | fatalError("init(coder:) has not been implemented") 106 | } 107 | 108 | override func commonInit() { 109 | super.commonInit() 110 | 111 | addSubview(titleLabel) 112 | 113 | titleLabel.positionToSuperView(horizontal: .Left, hOffset: DropdownMenuConfiguration.tableViewSecondarySubMenuLeftOffSet, vertical: .Center, vOffset: 0.0) 114 | 115 | backgroundColor = UIColor.whiteColor() 116 | } 117 | 118 | // MARK: Properties 119 | 120 | override func setSelected(selected: Bool, animated: Bool) { 121 | if selected { 122 | titleLabel.textColor = DropdownMenuConfiguration.highlightColor 123 | } else { 124 | titleLabel.textColor = DropdownMenuConfiguration.normalColor 125 | } 126 | } 127 | } 128 | 129 | // MARK: - Submenu Base View 130 | 131 | class DropdownTableSubmenuBaseView: UITableView { 132 | // MARK: Initializers 133 | 134 | init() { 135 | super.init(frame: CGRect.zero, style: .Plain) 136 | translatesAutoresizingMaskIntoConstraints = false 137 | 138 | let footer = UIView() 139 | tableFooterView = footer 140 | } 141 | 142 | required init?(coder aDecoder: NSCoder) { 143 | fatalError("init(coder:) has not been implemented") 144 | } 145 | } 146 | 147 | // MARK: - Submenu Main View 148 | 149 | class DropdownTableSubmenuMainView: DropdownTableSubmenuBaseView { 150 | // MARK: Initializers 151 | 152 | override init() { 153 | super.init() 154 | 155 | registerClass(DropdownSubmenuMainViewCell.self, forCellReuseIdentifier: DropdownMenuConfiguration.cellIdentifier) 156 | } 157 | 158 | required init?(coder aDecoder: NSCoder) { 159 | fatalError("init(coder:) has not been implemented") 160 | } 161 | } 162 | 163 | class DropdownTableSubmenuSecondaryView: DropdownTableSubmenuBaseView { 164 | // MARK: Initializers 165 | 166 | override init() { 167 | super.init() 168 | 169 | separatorStyle = .None 170 | registerClass(DropdownSubmenuSecondaryViewCell.self, forCellReuseIdentifier: DropdownMenuConfiguration.cellIdentifier) 171 | } 172 | 173 | required init?(coder aDecoder: NSCoder) { 174 | fatalError("init(coder:) has not been implemented") 175 | } 176 | } 177 | 178 | // MARK: - Submenu Single View + Single View 179 | 180 | typealias DropdownTableViewSubmenuSinlgeView = DropdownTableSubmenuSecondaryView 181 | 182 | // MARK: SubmenuSinlgeView+DropdownSubmenu 183 | 184 | extension DropdownTableViewSubmenuSinlgeView: DropdownSubmenu { 185 | var title: String? { 186 | if let indexPath = indexPathForSelectedRow, 187 | cell = cellForRowAtIndexPath(indexPath) as? DropdownSubmenuBaseCell { 188 | return cell.titleLabel.text 189 | } 190 | return nil 191 | } 192 | 193 | override func intrinsicContentSize() -> CGSize { 194 | return contentSize 195 | } 196 | 197 | func reload(includeMainMenu: Bool = true) { 198 | reloadData() 199 | } 200 | 201 | func selectItemAtIndex(index: DropdownSubmenuIndex) { 202 | if let mainIndex = index.main { 203 | selectRowAtIndexPath(NSIndexPath(forRow: mainIndex, inSection: 0), animated: false, scrollPosition: .Top) 204 | } 205 | } 206 | } 207 | 208 | // MARK: - SubmenuDoubleView 209 | 210 | enum DropdownTableViewSubmenuDoubleViewType: Int { 211 | case Main = 0 212 | case Secondary = 1 213 | } 214 | 215 | class DropdownTableViewSubmenuDoubleView: UIView{ 216 | // MARK: Properties 217 | 218 | let mainView: DropdownTableSubmenuMainView = { 219 | let mainView = DropdownTableSubmenuMainView() 220 | return mainView 221 | }() 222 | 223 | let secondaryView: DropdownTableSubmenuSecondaryView = { 224 | let secondaryView = DropdownTableSubmenuSecondaryView() 225 | return secondaryView 226 | }() 227 | 228 | weak var delegate: UITableViewDelegate? { 229 | didSet { 230 | mainView.delegate = delegate 231 | secondaryView.delegate = delegate 232 | } 233 | } 234 | 235 | weak var dataSource: UITableViewDataSource? { 236 | didSet { 237 | mainView.dataSource = dataSource 238 | secondaryView.dataSource = dataSource 239 | } 240 | } 241 | 242 | // MARK: Initializers 243 | 244 | init() { 245 | super.init(frame: CGRect.zero) 246 | 247 | translatesAutoresizingMaskIntoConstraints = false 248 | 249 | commonInit() 250 | } 251 | 252 | required init?(coder aDecoder: NSCoder) { 253 | fatalError("init(coder:) has not been implemented") 254 | } 255 | 256 | func commonInit() { 257 | addSubview(mainView) 258 | addSubview(secondaryView) 259 | 260 | mainView.tag = DropdownTableViewSubmenuDoubleViewType.Main.rawValue 261 | secondaryView.tag = DropdownTableViewSubmenuDoubleViewType.Secondary.rawValue 262 | 263 | mainView.fillSuperView(.Vertical) 264 | secondaryView.fillSuperView(.Vertical) 265 | LayoutKit.arrangeViews(.Horizontal, viewsTuple: [(mainView, 1.0), (secondaryView, 2)]) 266 | } 267 | } 268 | 269 | // MARK: DropdownTableViewSubmenuDoubleView + DropdownSubmenu 270 | 271 | extension DropdownTableViewSubmenuDoubleView: DropdownSubmenu { 272 | var title: String? { 273 | if let indexPath = secondaryView.indexPathForSelectedRow, 274 | cell = secondaryView.cellForRowAtIndexPath(indexPath) as? DropdownSubmenuBaseCell { 275 | return cell.titleLabel.text 276 | } 277 | return nil 278 | } 279 | 280 | override func intrinsicContentSize() -> CGSize { 281 | return mainView.contentSize.height > secondaryView.contentSize.height ? mainView.contentSize : secondaryView.contentSize 282 | } 283 | 284 | func selectItemAtIndex(index: DropdownSubmenuIndex) { 285 | if let mainIndex = index.main { 286 | mainView.selectRowAtIndexPath(NSIndexPath(forRow: mainIndex, inSection: 0), animated: false, scrollPosition: .None) 287 | } 288 | if let secondaryIndex = index.secondary { 289 | secondaryView.selectRowAtIndexPath(NSIndexPath(forRow: secondaryIndex, inSection: 0), animated: false, scrollPosition: .None) 290 | } 291 | } 292 | 293 | func reload(includeMainMenu: Bool = false) { 294 | if includeMainMenu { 295 | mainView.reloadData() 296 | } 297 | secondaryView.reloadData() 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Views/DropdownSubmenuCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropdownCollectionView.swift 3 | // TestMenu 4 | // 5 | // Created by Shannon Wu on 12/20/15. 6 | // Copyright © 2015 Shannon's Dreamland. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Collection View Cell 12 | 13 | class DropdownCollectionViewCell: UICollectionViewCell { 14 | // MARK: Properties 15 | 16 | let titleLabel: UILabel = { 17 | let label = UILabel() 18 | label.textAlignment = .Center 19 | label.font = DropdownMenuConfiguration.collectionViewSubmenuBodyFont 20 | label.textColor = DropdownMenuConfiguration.normalColor 21 | label.translatesAutoresizingMaskIntoConstraints = false 22 | return label 23 | }() 24 | 25 | // MARK: Initializers 26 | 27 | override init(frame: CGRect) { 28 | super.init(frame: frame) 29 | 30 | commonInit() 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | func commonInit() { 38 | translatesAutoresizingMaskIntoConstraints = false 39 | 40 | addSubview(titleLabel) 41 | titleLabel.fillSuperView(.Both) 42 | 43 | backgroundColor = UIColor.whiteColor() 44 | layer.cornerRadius = 4.0 45 | layer.borderWidth = 1.0 46 | layer.borderColor = DropdownMenuConfiguration.separatorColor.CGColor 47 | } 48 | 49 | // MARK: Properties 50 | 51 | override var selected: Bool { 52 | didSet { 53 | if selected { 54 | layer.borderColor = DropdownMenuConfiguration.highlightColor.CGColor 55 | titleLabel.textColor = DropdownMenuConfiguration.highlightColor 56 | } else { 57 | layer.borderColor = DropdownMenuConfiguration.separatorColor.CGColor 58 | titleLabel.textColor = DropdownMenuConfiguration.normalColor 59 | } 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Section Header 65 | 66 | protocol DropdownCollectionViewSectionHeaderDelegate: class { 67 | func toggleSectionContentHiddenState(state: Bool) 68 | } 69 | 70 | class DropdownCollectionViewSectionHeader: UICollectionReusableView { 71 | // MARK: Properties 72 | private var expandState = false 73 | weak var delegate: DropdownCollectionViewSectionHeaderDelegate? 74 | 75 | let titleLabel: UILabel = { 76 | let label = UILabel() 77 | label.textColor = DropdownMenuConfiguration.normalColor 78 | label.font = DropdownMenuConfiguration.collectionViewSubmenuHeaderFont 79 | label.translatesAutoresizingMaskIntoConstraints = false 80 | return label 81 | }() 82 | 83 | let expandButton: UIButton = { 84 | let button = UIButton(type: .Custom) 85 | button.setImage(DropdownMenuConfiguration.triangleImage, forState: .Normal) 86 | button.translatesAutoresizingMaskIntoConstraints = false 87 | return button 88 | }() 89 | 90 | override var reuseIdentifier: String { 91 | return DropdownMenuConfiguration.headerIdentifier 92 | } 93 | 94 | // MARK: Initializers 95 | 96 | override init(frame: CGRect) { 97 | super.init(frame: frame) 98 | 99 | commonInit() 100 | } 101 | 102 | func commonInit() { 103 | translatesAutoresizingMaskIntoConstraints = false 104 | 105 | expandButton.addTarget(self, action: "toggleExpandState", forControlEvents: .TouchUpInside) 106 | 107 | addSubview(titleLabel) 108 | addSubview(expandButton) 109 | 110 | titleLabel.positionToSuperView(horizontal: .Left, hOffset: 15.0, vertical: .Center, vOffset: 0) 111 | expandButton.positionToSuperView(horizontal: .Right, hOffset: -15.0, vertical: .Center, vOffset: 0) 112 | } 113 | 114 | required init?(coder aDecoder: NSCoder) { 115 | fatalError("init(coder:) has not been implemented") 116 | } 117 | 118 | // MARK: Convenience 119 | 120 | func toggleExpandState() { 121 | expandState = !expandState 122 | if expandState { 123 | expandButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI)) 124 | expandButton.tintColor = DropdownMenuConfiguration.highlightColor 125 | } else { 126 | expandButton.transform = CGAffineTransformMakeRotation(0) 127 | expandButton.tintColor = DropdownMenuConfiguration.normalColor 128 | } 129 | delegate?.toggleSectionContentHiddenState(expandState) 130 | } 131 | } 132 | 133 | // MARK: - Footer 134 | 135 | // MARK: DecorationFooter 136 | 137 | class DropdownCollectionViewSectionDecorationFooter: UICollectionReusableView { 138 | // MARK: Properties 139 | 140 | let decorationView: UIView = { 141 | let view = UIView() 142 | view.backgroundColor = DropdownMenuConfiguration.separatorColor 143 | view.translatesAutoresizingMaskIntoConstraints = false 144 | return view 145 | }() 146 | 147 | override var reuseIdentifier: String { 148 | return DropdownMenuConfiguration.decorationFooterIdentifier 149 | } 150 | 151 | // MARK: Initializers 152 | 153 | override init(frame: CGRect) { 154 | super.init(frame: frame) 155 | commonInit() 156 | } 157 | 158 | required init?(coder aDecoder: NSCoder) { 159 | fatalError("init(coder:) has not been implemented") 160 | } 161 | 162 | func commonInit() { 163 | translatesAutoresizingMaskIntoConstraints = false 164 | 165 | addSubview(decorationView) 166 | decorationView.addDimentionConstraints(.Height, constant: 1.0) 167 | decorationView.positionToSuperView(horizontal: .Left, hOffset: 15.0, vertical: .Bottom, vOffset: -1) 168 | decorationView.positionToSuperView(horizontal: .Right, hOffset: -15.0, vertical: .Bottom, vOffset: -1) 169 | } 170 | } 171 | 172 | // MARK: DoneFooter 173 | 174 | protocol DropdownCollectionViewSectionDoneFooterDelegate: class { 175 | func submenuSelectionFinished() 176 | } 177 | 178 | class DropdownCollectionViewSectionDoneFooter: UICollectionReusableView { 179 | // MARK: Properties 180 | 181 | weak var delegate: DropdownCollectionViewSectionDoneFooterDelegate? 182 | 183 | let doneButton: UIButton = { 184 | let button = UIButton(type: .Custom) 185 | button.setTitle(NSLocalizedString("完成", comment: ""), forState: .Normal) 186 | button.setTitleColor(DropdownMenuConfiguration.highlightColor, forState: .Normal) 187 | button.titleLabel?.font = DropdownMenuConfiguration.collectionViewSubmenuFinishButtonFont 188 | 189 | button.layer.borderColor = DropdownMenuConfiguration.highlightColor.CGColor 190 | button.layer.borderWidth = 1.0 191 | button.layer.cornerRadius = 4.0 192 | button.translatesAutoresizingMaskIntoConstraints = false 193 | return button 194 | }() 195 | 196 | override var reuseIdentifier: String { 197 | return DropdownMenuConfiguration.doneFooterIdentifier 198 | } 199 | 200 | // MARK: Initializers 201 | 202 | override init(frame: CGRect) { 203 | super.init(frame: frame) 204 | commonInit() 205 | } 206 | 207 | required init?(coder aDecoder: NSCoder) { 208 | fatalError("init(coder:) has not been implemented") 209 | } 210 | 211 | func commonInit() { 212 | translatesAutoresizingMaskIntoConstraints = false 213 | 214 | doneButton.addTarget(self, action: "select", forControlEvents: .TouchUpInside) 215 | addSubview(doneButton) 216 | 217 | doneButton.addDimentionConstraints(.Height, constant: 40.0) 218 | doneButton.positionToSuperView(horizontal: .Left, hOffset: 15.0, vertical: .Top, vOffset: 40.0) 219 | doneButton.positionToSuperView(horizontal: .Right, hOffset: -15.0, vertical: .Top, vOffset: 40.0) 220 | } 221 | 222 | // MARK: Convience 223 | 224 | func select() { 225 | delegate?.submenuSelectionFinished() 226 | } 227 | } 228 | 229 | // MARK: - CollectionViewSubmenuSubmenu 230 | 231 | class DropdownCollectionViewSubmenu: UICollectionView { 232 | // MARK: Initializers 233 | 234 | override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { 235 | super.init(frame: frame, collectionViewLayout: layout) 236 | 237 | commonInit() 238 | } 239 | 240 | required init?(coder aDecoder: NSCoder) { 241 | fatalError("init(coder:) has not been implemented") 242 | } 243 | 244 | override var contentSize: CGSize { 245 | didSet { 246 | invalidateIntrinsicContentSize() 247 | } 248 | } 249 | 250 | func commonInit() { 251 | translatesAutoresizingMaskIntoConstraints = false 252 | 253 | backgroundColor = UIColor.whiteColor() 254 | 255 | registerClass(DropdownCollectionViewCell.self, forCellWithReuseIdentifier: DropdownMenuConfiguration.cellIdentifier) 256 | registerClass(DropdownCollectionViewSectionHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: DropdownMenuConfiguration.headerIdentifier) 257 | registerClass(DropdownCollectionViewSectionDecorationFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: DropdownMenuConfiguration.decorationFooterIdentifier) 258 | registerClass(DropdownCollectionViewSectionDoneFooter.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: DropdownMenuConfiguration.doneFooterIdentifier) 259 | } 260 | } 261 | 262 | // MARK: DropdownSubmenu 263 | 264 | extension DropdownCollectionViewSubmenu: DropdownSubmenu { 265 | 266 | func reload(includeMainMenu: Bool = false) { 267 | reloadData() 268 | } 269 | 270 | var title: String? { 271 | return nil 272 | } 273 | 274 | override func intrinsicContentSize() -> CGSize { 275 | return contentSize 276 | } 277 | 278 | func selectItemAtIndex(index: DropdownSubmenuIndex) { 279 | if let mainIndex = index.main { 280 | selectItemAtIndexPath(NSIndexPath(forRow: mainIndex, inSection: 0), animated: false, scrollPosition: .Top) 281 | } 282 | if let secondaryIndex = index.secondary { 283 | selectItemAtIndexPath(NSIndexPath(forRow: secondaryIndex, inSection: 1), animated: false, scrollPosition: .Top) 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /TestMenu/DropdownMenu/Utils/UIView+AutoLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+AutoLayout.swift 3 | // Client 4 | // 5 | // Created by Shannon Wu on 12/22/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Types 12 | 13 | enum LayoutDirection { 14 | case Horizontal 15 | case Vertical 16 | case Both 17 | } 18 | 19 | extension UIView { 20 | typealias Relation = NSLayoutRelation 21 | 22 | enum Horizontal { 23 | case Left 24 | case Center 25 | case Right 26 | 27 | var attribute: NSLayoutAttribute { 28 | switch self { 29 | case .Left: 30 | return .Left 31 | case .Right: 32 | return .Right 33 | case .Center: 34 | return .CenterX 35 | } 36 | } 37 | } 38 | 39 | enum Vertical { 40 | case Top 41 | case Center 42 | case Bottom 43 | 44 | var attribute: NSLayoutAttribute { 45 | switch self { 46 | case .Top: 47 | return .Top 48 | case .Center: 49 | return .CenterY 50 | case .Bottom: 51 | return .Bottom 52 | } 53 | } 54 | } 55 | 56 | enum Dimension { 57 | case Width 58 | case Height 59 | 60 | var attribute: NSLayoutAttribute { 61 | switch self { 62 | case .Width: 63 | return .Width 64 | case .Height: 65 | return .Height 66 | } 67 | } 68 | } 69 | 70 | enum Direction { 71 | case Leading 72 | case Trailing 73 | case Bottom 74 | case Top 75 | } 76 | 77 | // MARK: - Layout Methods 78 | 79 | func addDimentionConstraints(dimention: Dimension, constant: CGFloat, relation: Relation = .Equal) { 80 | var constraints = [NSLayoutConstraint]() 81 | switch dimention { 82 | case .Width: 83 | constraints.append(NSLayoutConstraint(item: self, attribute: .Width, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: 0.0, constant: constant)) 84 | case .Height: 85 | constraints.append(NSLayoutConstraint(item: self, attribute: .Height, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: 0.0, constant: constant)) 86 | } 87 | NSLayoutConstraint.activateConstraints(constraints) 88 | } 89 | 90 | func positionToSuperView(horizontal horizontal: Horizontal, hOffset: CGFloat, vertical: Vertical, vOffset: CGFloat) { 91 | guard let superView = self.superview else { 92 | assertionFailure("在调用这个方法前要将它加入到父 View 上面") 93 | return 94 | } 95 | 96 | var constraints = [NSLayoutConstraint]() 97 | constraints.append(NSLayoutConstraint(item: self, attribute: horizontal.attribute, relatedBy: .Equal, toItem: superView, attribute: horizontal.attribute, multiplier: 1.0, constant: hOffset)) 98 | constraints.append(NSLayoutConstraint(item: self, attribute: vertical.attribute, relatedBy: .Equal, toItem: superView, attribute: vertical.attribute, multiplier: 1.0, constant: vOffset)) 99 | NSLayoutConstraint.activateConstraints(constraints) 100 | } 101 | 102 | func pinToSuperView(direction: Direction, inset: CGFloat, relation: Relation) { 103 | guard self.superview != nil else { 104 | assertionFailure("在调用这个方法前要将它加入到父 View 上面") 105 | return 106 | } 107 | 108 | var constraints = [NSLayoutConstraint]() 109 | let views = ["self": self] 110 | var formats = [String]() 111 | 112 | let relationSubFormat: String 113 | switch relation { 114 | case .GreaterThanOrEqual: 115 | relationSubFormat = ">=\(inset)" 116 | case .Equal: 117 | relationSubFormat = "==\(inset)" 118 | case .LessThanOrEqual: 119 | relationSubFormat = "<=\(inset)" 120 | } 121 | 122 | switch direction { 123 | case .Top: 124 | formats.append("|-(\(relationSubFormat))-[self]|") 125 | formats.append("V:|[self]") 126 | case .Trailing: 127 | formats.append("V:|[self]|") 128 | formats.append("[self]-(\(relationSubFormat))-|") 129 | case .Bottom: 130 | formats.append("V:[self]-(\(relationSubFormat))-|") 131 | formats.append("|[self]|") 132 | case .Leading: 133 | formats.append("V:|[self]|") 134 | formats.append("|-(\(relationSubFormat))-[self]") 135 | } 136 | 137 | formats.forEach { format in 138 | constraints += NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: views) 139 | } 140 | NSLayoutConstraint.activateConstraints(constraints) 141 | } 142 | 143 | func positionRelativeToView(view: UIView, direction: Direction, space: CGFloat) { 144 | guard view.superview == self.superview else { 145 | assertionFailure("在调用这个方法前要将它它们加入到同一个 View 的下面") 146 | return 147 | } 148 | 149 | var constraints = [NSLayoutConstraint]() 150 | 151 | let selfSideAttribute: NSLayoutAttribute 152 | let viewSideAttribute: NSLayoutAttribute 153 | let centerAttribute: NSLayoutAttribute 154 | switch direction { 155 | case .Top: 156 | selfSideAttribute = .Bottom 157 | viewSideAttribute = .Top 158 | centerAttribute = .CenterX 159 | case .Trailing: 160 | selfSideAttribute = .Leading 161 | viewSideAttribute = .Trailing 162 | centerAttribute = .CenterY 163 | case .Bottom: 164 | selfSideAttribute = .Top 165 | viewSideAttribute = .Bottom 166 | centerAttribute = .CenterX 167 | case .Leading: 168 | selfSideAttribute = .Trailing 169 | viewSideAttribute = .Leading 170 | centerAttribute = .CenterY 171 | } 172 | 173 | constraints.append(NSLayoutConstraint(item: self, attribute: selfSideAttribute, relatedBy: .Equal, toItem: view, attribute: viewSideAttribute, multiplier: 1.0, constant: space)) 174 | constraints.append(NSLayoutConstraint(item: self, attribute: centerAttribute, relatedBy: .Equal, toItem: view, attribute: centerAttribute, multiplier: 1.0, constant: 0)) 175 | NSLayoutConstraint.activateConstraints(constraints) 176 | } 177 | 178 | func fillSuperView(direction: LayoutDirection, edgeInsets: UIEdgeInsets = UIEdgeInsetsZero) { 179 | guard self.superview != nil else { 180 | assertionFailure("在调用这个方法前要将它加入到父 View 上面") 181 | return 182 | } 183 | 184 | let view = ["self": self] 185 | var constraints = [NSLayoutConstraint]() 186 | 187 | let hConstraints = NSLayoutConstraint.constraintsWithVisualFormat("|-\(edgeInsets.left)-[self]-\(edgeInsets.right)-|", options: [], metrics: nil, views: view) 188 | let vConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|-\(edgeInsets.top)-[self]-\(edgeInsets.bottom)-|", options: [], metrics: nil, views: view) 189 | 190 | switch direction { 191 | case .Horizontal: 192 | constraints += hConstraints 193 | case .Vertical: 194 | constraints += vConstraints 195 | case .Both: 196 | constraints += hConstraints 197 | constraints += vConstraints 198 | } 199 | NSLayoutConstraint.activateConstraints(constraints) 200 | } 201 | } 202 | 203 | typealias ViewTuple = (view: UIView, multiplierRelativeToFirstView: CGFloat) 204 | struct LayoutKit { 205 | 206 | static func arrangeViews(direction: LayoutDirection, viewsTuple: [ViewTuple]) { 207 | 208 | var viewsDict = [String: AnyObject]() 209 | guard let viewFlag = viewsTuple.first?.view.superview else { 210 | assertionFailure("在调用这个方法前要将它加入到父 View 上面") 211 | return 212 | } 213 | for (index, viewTuple) in viewsTuple.enumerate() { 214 | viewsDict["Item\(index)"] = viewTuple.view 215 | if viewFlag != viewTuple.view.superview { 216 | assertionFailure("这个方法的所有视图必须放在同一个父视图中") 217 | return 218 | } 219 | } 220 | 221 | var constraints = [NSLayoutConstraint]() 222 | switch direction { 223 | case .Horizontal: 224 | for (index, viewTuple) in viewsTuple.enumerate() { 225 | let format: String 226 | if index == 0 { 227 | format = "|[Item\(index)]" 228 | } 229 | else if index == viewsTuple.count - 1 { 230 | format = "[Item\(index-1)][Item\(index)]|" 231 | } 232 | else { 233 | format = "[Item\(index-1)][Item\(index)]" 234 | } 235 | constraints += NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: viewsDict) 236 | constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[Item\(index)]|", options: [], metrics: nil, views: viewsDict) 237 | constraints.append(NSLayoutConstraint(item: viewsDict["Item\(index)"]!, attribute: .Width, relatedBy: .Equal, toItem: viewsDict["Item0"], attribute: .Width, multiplier: viewTuple.multiplierRelativeToFirstView, constant: 0)) 238 | } 239 | case .Vertical: 240 | for (index, viewTuple) in viewsTuple.enumerate() { 241 | let format: String 242 | if index == 0 { 243 | format = "V:|[Item\(index)]" 244 | } 245 | else if index == viewsTuple.count - 1 { 246 | format = "V:[Item\(index-1)][Item\(index)]|" 247 | } 248 | else { 249 | format = "V:[Item\(index-1)][Item\(index)]" 250 | } 251 | constraints += NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: viewsDict) 252 | constraints += NSLayoutConstraint.constraintsWithVisualFormat("|[Item\(index)]|", options: [], metrics: nil, views: viewsDict) 253 | constraints.append(NSLayoutConstraint(item: viewsDict["Item\(index)"]!, attribute: .Height, relatedBy: .Equal, toItem: viewsDict["Item0"], attribute: .Height, multiplier: viewTuple.multiplierRelativeToFirstView, constant: 0)) 254 | } 255 | case .Both: () 256 | } 257 | NSLayoutConstraint.activateConstraints(constraints) 258 | } 259 | } -------------------------------------------------------------------------------- /TestMenu.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 610C3B8D1C252B9E007DB909 /* DemoDropdownMenuManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 610C3B8C1C252B9E007DB909 /* DemoDropdownMenuManager.swift */; }; 11 | 614DAC8A1C2AFEAF0063CAD4 /* DropdownMenu+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC7E1C2AFEAF0063CAD4 /* DropdownMenu+Types.swift */; }; 12 | 614DAC8B1C2AFEAF0063CAD4 /* DropdownMenu.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 614DAC7F1C2AFEAF0063CAD4 /* DropdownMenu.xcassets */; }; 13 | 614DAC8C1C2AFEAF0063CAD4 /* DropdownMenuConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC801C2AFEAF0063CAD4 /* DropdownMenuConfiguration.swift */; }; 14 | 614DAC8D1C2AFEAF0063CAD4 /* DropdownMenuViewController+CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC811C2AFEAF0063CAD4 /* DropdownMenuViewController+CollectionView.swift */; }; 15 | 614DAC8E1C2AFEAF0063CAD4 /* DropdownMenuViewController+TableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC821C2AFEAF0063CAD4 /* DropdownMenuViewController+TableView.swift */; }; 16 | 614DAC8F1C2AFEAF0063CAD4 /* DropdownMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC831C2AFEAF0063CAD4 /* DropdownMenuViewController.swift */; }; 17 | 614DAC901C2AFEAF0063CAD4 /* UIView+AutoLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC841C2AFEAF0063CAD4 /* UIView+AutoLayout.swift */; }; 18 | 614DAC911C2AFEAF0063CAD4 /* DorpdownMainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC861C2AFEAF0063CAD4 /* DorpdownMainMenu.swift */; }; 19 | 614DAC921C2AFEAF0063CAD4 /* DropdownSubmenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC871C2AFEAF0063CAD4 /* DropdownSubmenu.swift */; }; 20 | 614DAC931C2AFEAF0063CAD4 /* DropdownSubmenuCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC881C2AFEAF0063CAD4 /* DropdownSubmenuCollectionView.swift */; }; 21 | 614DAC941C2AFEAF0063CAD4 /* DropdownSubmenuTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC891C2AFEAF0063CAD4 /* DropdownSubmenuTableView.swift */; }; 22 | 614DAC981C2B01050063CAD4 /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC971C2B01050063CAD4 /* FontManager.swift */; }; 23 | 614DAC9A1C2B01290063CAD4 /* ColorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC991C2B01290063CAD4 /* ColorManager.swift */; }; 24 | 614DAC9C1C2B01550063CAD4 /* AppColor+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 614DAC9B1C2B01550063CAD4 /* AppColor+Utils.swift */; }; 25 | 614DAC9E1C2B017D0063CAD4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 614DAC9D1C2B017D0063CAD4 /* Assets.xcassets */; }; 26 | 6162CD241C2407F90094A00B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6162CD231C2407F90094A00B /* AppDelegate.swift */; }; 27 | 6162CD261C2407F90094A00B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6162CD251C2407F90094A00B /* ViewController.swift */; }; 28 | 6162CD291C2407F90094A00B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6162CD271C2407F90094A00B /* Main.storyboard */; }; 29 | 6162CD2E1C2407F90094A00B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6162CD2C1C2407F90094A00B /* LaunchScreen.storyboard */; }; 30 | 6162CD391C2407F90094A00B /* TestMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6162CD381C2407F90094A00B /* TestMenuTests.swift */; }; 31 | 619A2A371C2B875900011B6E /* UIView+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619A2A361C2B875900011B6E /* UIView+Utils.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 6162CD351C2407F90094A00B /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 6162CD181C2407F90094A00B /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 6162CD1F1C2407F90094A00B; 40 | remoteInfo = TestMenu; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 610C3B8C1C252B9E007DB909 /* DemoDropdownMenuManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DemoDropdownMenuManager.swift; path = TestMenu/DemoDropdownMenuManager.swift; sourceTree = ""; }; 46 | 614DAC7E1C2AFEAF0063CAD4 /* DropdownMenu+Types.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DropdownMenu+Types.swift"; sourceTree = ""; }; 47 | 614DAC7F1C2AFEAF0063CAD4 /* DropdownMenu.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DropdownMenu.xcassets; sourceTree = ""; }; 48 | 614DAC801C2AFEAF0063CAD4 /* DropdownMenuConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropdownMenuConfiguration.swift; sourceTree = ""; }; 49 | 614DAC811C2AFEAF0063CAD4 /* DropdownMenuViewController+CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DropdownMenuViewController+CollectionView.swift"; sourceTree = ""; }; 50 | 614DAC821C2AFEAF0063CAD4 /* DropdownMenuViewController+TableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DropdownMenuViewController+TableView.swift"; sourceTree = ""; }; 51 | 614DAC831C2AFEAF0063CAD4 /* DropdownMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropdownMenuViewController.swift; sourceTree = ""; }; 52 | 614DAC841C2AFEAF0063CAD4 /* UIView+AutoLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayout.swift"; sourceTree = ""; }; 53 | 614DAC861C2AFEAF0063CAD4 /* DorpdownMainMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DorpdownMainMenu.swift; sourceTree = ""; }; 54 | 614DAC871C2AFEAF0063CAD4 /* DropdownSubmenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropdownSubmenu.swift; sourceTree = ""; }; 55 | 614DAC881C2AFEAF0063CAD4 /* DropdownSubmenuCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropdownSubmenuCollectionView.swift; sourceTree = ""; }; 56 | 614DAC891C2AFEAF0063CAD4 /* DropdownSubmenuTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropdownSubmenuTableView.swift; sourceTree = ""; }; 57 | 614DAC971C2B01050063CAD4 /* FontManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontManager.swift; sourceTree = ""; }; 58 | 614DAC991C2B01290063CAD4 /* ColorManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorManager.swift; sourceTree = ""; }; 59 | 614DAC9B1C2B01550063CAD4 /* AppColor+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppColor+Utils.swift"; sourceTree = ""; }; 60 | 614DAC9D1C2B017D0063CAD4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 6162CD201C2407F90094A00B /* TestMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestMenu.app; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 6162CD231C2407F90094A00B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 63 | 6162CD251C2407F90094A00B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = TestMenu/ViewController.swift; sourceTree = ""; }; 64 | 6162CD281C2407F90094A00B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 65 | 6162CD2D1C2407F90094A00B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 66 | 6162CD2F1C2407F90094A00B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 67 | 6162CD341C2407F90094A00B /* TestMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TestMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 6162CD381C2407F90094A00B /* TestMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMenuTests.swift; sourceTree = ""; }; 69 | 6162CD3A1C2407F90094A00B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | 619A2A361C2B875900011B6E /* UIView+Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Utils.swift"; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | 6162CD1D1C2407F90094A00B /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 6162CD311C2407F90094A00B /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 614DAC7C1C2AFBFC0063CAD4 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 6162CD231C2407F90094A00B /* AppDelegate.swift */, 95 | 614DAC9D1C2B017D0063CAD4 /* Assets.xcassets */, 96 | 6162CD2F1C2407F90094A00B /* Info.plist */, 97 | 6162CD2C1C2407F90094A00B /* LaunchScreen.storyboard */, 98 | 6162CD271C2407F90094A00B /* Main.storyboard */, 99 | ); 100 | path = "Supporting Files"; 101 | sourceTree = ""; 102 | }; 103 | 614DAC7D1C2AFEAF0063CAD4 /* DropdownMenu */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 614DAC7E1C2AFEAF0063CAD4 /* DropdownMenu+Types.swift */, 107 | 614DAC801C2AFEAF0063CAD4 /* DropdownMenuConfiguration.swift */, 108 | 614DAC831C2AFEAF0063CAD4 /* DropdownMenuViewController.swift */, 109 | 614DAC821C2AFEAF0063CAD4 /* DropdownMenuViewController+TableView.swift */, 110 | 614DAC811C2AFEAF0063CAD4 /* DropdownMenuViewController+CollectionView.swift */, 111 | 614DAC961C2B00780063CAD4 /* Utils */, 112 | 614DAC851C2AFEAF0063CAD4 /* Views */, 113 | 614DAC951C2B006E0063CAD4 /* Resource */, 114 | ); 115 | path = DropdownMenu; 116 | sourceTree = ""; 117 | }; 118 | 614DAC851C2AFEAF0063CAD4 /* Views */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 614DAC861C2AFEAF0063CAD4 /* DorpdownMainMenu.swift */, 122 | 614DAC871C2AFEAF0063CAD4 /* DropdownSubmenu.swift */, 123 | 614DAC891C2AFEAF0063CAD4 /* DropdownSubmenuTableView.swift */, 124 | 614DAC881C2AFEAF0063CAD4 /* DropdownSubmenuCollectionView.swift */, 125 | ); 126 | path = Views; 127 | sourceTree = ""; 128 | }; 129 | 614DAC951C2B006E0063CAD4 /* Resource */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 614DAC7F1C2AFEAF0063CAD4 /* DropdownMenu.xcassets */, 133 | ); 134 | path = Resource; 135 | sourceTree = ""; 136 | }; 137 | 614DAC961C2B00780063CAD4 /* Utils */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 619A2A361C2B875900011B6E /* UIView+Utils.swift */, 141 | 614DAC991C2B01290063CAD4 /* ColorManager.swift */, 142 | 614DAC971C2B01050063CAD4 /* FontManager.swift */, 143 | 614DAC9B1C2B01550063CAD4 /* AppColor+Utils.swift */, 144 | 614DAC841C2AFEAF0063CAD4 /* UIView+AutoLayout.swift */, 145 | ); 146 | path = Utils; 147 | sourceTree = ""; 148 | }; 149 | 6162CD171C2407F90094A00B = { 150 | isa = PBXGroup; 151 | children = ( 152 | 610C3B8C1C252B9E007DB909 /* DemoDropdownMenuManager.swift */, 153 | 6162CD251C2407F90094A00B /* ViewController.swift */, 154 | 6162CD221C2407F90094A00B /* TestMenu */, 155 | 6162CD211C2407F90094A00B /* Products */, 156 | 6162CD371C2407F90094A00B /* TestMenuTests */, 157 | ); 158 | sourceTree = ""; 159 | }; 160 | 6162CD211C2407F90094A00B /* Products */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | 6162CD201C2407F90094A00B /* TestMenu.app */, 164 | 6162CD341C2407F90094A00B /* TestMenuTests.xctest */, 165 | ); 166 | name = Products; 167 | sourceTree = ""; 168 | }; 169 | 6162CD221C2407F90094A00B /* TestMenu */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 614DAC7D1C2AFEAF0063CAD4 /* DropdownMenu */, 173 | 614DAC7C1C2AFBFC0063CAD4 /* Supporting Files */, 174 | ); 175 | path = TestMenu; 176 | sourceTree = ""; 177 | }; 178 | 6162CD371C2407F90094A00B /* TestMenuTests */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 6162CD3A1C2407F90094A00B /* Info.plist */, 182 | 6162CD381C2407F90094A00B /* TestMenuTests.swift */, 183 | ); 184 | path = TestMenuTests; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | 6162CD1F1C2407F90094A00B /* TestMenu */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 6162CD3D1C2407F90094A00B /* Build configuration list for PBXNativeTarget "TestMenu" */; 193 | buildPhases = ( 194 | 6162CD1C1C2407F90094A00B /* Sources */, 195 | 6162CD1D1C2407F90094A00B /* Frameworks */, 196 | 6162CD1E1C2407F90094A00B /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | ); 202 | name = TestMenu; 203 | productName = TestMenu; 204 | productReference = 6162CD201C2407F90094A00B /* TestMenu.app */; 205 | productType = "com.apple.product-type.application"; 206 | }; 207 | 6162CD331C2407F90094A00B /* TestMenuTests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 6162CD401C2407F90094A00B /* Build configuration list for PBXNativeTarget "TestMenuTests" */; 210 | buildPhases = ( 211 | 6162CD301C2407F90094A00B /* Sources */, 212 | 6162CD311C2407F90094A00B /* Frameworks */, 213 | 6162CD321C2407F90094A00B /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | 6162CD361C2407F90094A00B /* PBXTargetDependency */, 219 | ); 220 | name = TestMenuTests; 221 | productName = TestMenuTests; 222 | productReference = 6162CD341C2407F90094A00B /* TestMenuTests.xctest */; 223 | productType = "com.apple.product-type.bundle.unit-test"; 224 | }; 225 | /* End PBXNativeTarget section */ 226 | 227 | /* Begin PBXProject section */ 228 | 6162CD181C2407F90094A00B /* Project object */ = { 229 | isa = PBXProject; 230 | attributes = { 231 | LastSwiftUpdateCheck = 0720; 232 | LastUpgradeCheck = 0720; 233 | ORGANIZATIONNAME = "Shannon's Dreamland"; 234 | TargetAttributes = { 235 | 6162CD1F1C2407F90094A00B = { 236 | CreatedOnToolsVersion = 7.2; 237 | }; 238 | 6162CD331C2407F90094A00B = { 239 | CreatedOnToolsVersion = 7.2; 240 | TestTargetID = 6162CD1F1C2407F90094A00B; 241 | }; 242 | }; 243 | }; 244 | buildConfigurationList = 6162CD1B1C2407F90094A00B /* Build configuration list for PBXProject "TestMenu" */; 245 | compatibilityVersion = "Xcode 3.2"; 246 | developmentRegion = English; 247 | hasScannedForEncodings = 0; 248 | knownRegions = ( 249 | en, 250 | Base, 251 | ); 252 | mainGroup = 6162CD171C2407F90094A00B; 253 | productRefGroup = 6162CD211C2407F90094A00B /* Products */; 254 | projectDirPath = ""; 255 | projectRoot = ""; 256 | targets = ( 257 | 6162CD1F1C2407F90094A00B /* TestMenu */, 258 | 6162CD331C2407F90094A00B /* TestMenuTests */, 259 | ); 260 | }; 261 | /* End PBXProject section */ 262 | 263 | /* Begin PBXResourcesBuildPhase section */ 264 | 6162CD1E1C2407F90094A00B /* Resources */ = { 265 | isa = PBXResourcesBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | 6162CD2E1C2407F90094A00B /* LaunchScreen.storyboard in Resources */, 269 | 6162CD291C2407F90094A00B /* Main.storyboard in Resources */, 270 | 614DAC9E1C2B017D0063CAD4 /* Assets.xcassets in Resources */, 271 | 614DAC8B1C2AFEAF0063CAD4 /* DropdownMenu.xcassets in Resources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | 6162CD321C2407F90094A00B /* Resources */ = { 276 | isa = PBXResourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | ); 280 | runOnlyForDeploymentPostprocessing = 0; 281 | }; 282 | /* End PBXResourcesBuildPhase section */ 283 | 284 | /* Begin PBXSourcesBuildPhase section */ 285 | 6162CD1C1C2407F90094A00B /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 614DAC8F1C2AFEAF0063CAD4 /* DropdownMenuViewController.swift in Sources */, 290 | 614DAC931C2AFEAF0063CAD4 /* DropdownSubmenuCollectionView.swift in Sources */, 291 | 614DAC981C2B01050063CAD4 /* FontManager.swift in Sources */, 292 | 614DAC941C2AFEAF0063CAD4 /* DropdownSubmenuTableView.swift in Sources */, 293 | 614DAC901C2AFEAF0063CAD4 /* UIView+AutoLayout.swift in Sources */, 294 | 614DAC921C2AFEAF0063CAD4 /* DropdownSubmenu.swift in Sources */, 295 | 614DAC9A1C2B01290063CAD4 /* ColorManager.swift in Sources */, 296 | 6162CD261C2407F90094A00B /* ViewController.swift in Sources */, 297 | 614DAC8E1C2AFEAF0063CAD4 /* DropdownMenuViewController+TableView.swift in Sources */, 298 | 614DAC8A1C2AFEAF0063CAD4 /* DropdownMenu+Types.swift in Sources */, 299 | 614DAC8C1C2AFEAF0063CAD4 /* DropdownMenuConfiguration.swift in Sources */, 300 | 614DAC9C1C2B01550063CAD4 /* AppColor+Utils.swift in Sources */, 301 | 614DAC8D1C2AFEAF0063CAD4 /* DropdownMenuViewController+CollectionView.swift in Sources */, 302 | 619A2A371C2B875900011B6E /* UIView+Utils.swift in Sources */, 303 | 6162CD241C2407F90094A00B /* AppDelegate.swift in Sources */, 304 | 614DAC911C2AFEAF0063CAD4 /* DorpdownMainMenu.swift in Sources */, 305 | 610C3B8D1C252B9E007DB909 /* DemoDropdownMenuManager.swift in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | 6162CD301C2407F90094A00B /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 6162CD391C2407F90094A00B /* TestMenuTests.swift in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | /* End PBXSourcesBuildPhase section */ 318 | 319 | /* Begin PBXTargetDependency section */ 320 | 6162CD361C2407F90094A00B /* PBXTargetDependency */ = { 321 | isa = PBXTargetDependency; 322 | target = 6162CD1F1C2407F90094A00B /* TestMenu */; 323 | targetProxy = 6162CD351C2407F90094A00B /* PBXContainerItemProxy */; 324 | }; 325 | /* End PBXTargetDependency section */ 326 | 327 | /* Begin PBXVariantGroup section */ 328 | 6162CD271C2407F90094A00B /* Main.storyboard */ = { 329 | isa = PBXVariantGroup; 330 | children = ( 331 | 6162CD281C2407F90094A00B /* Base */, 332 | ); 333 | name = Main.storyboard; 334 | path = .; 335 | sourceTree = ""; 336 | }; 337 | 6162CD2C1C2407F90094A00B /* LaunchScreen.storyboard */ = { 338 | isa = PBXVariantGroup; 339 | children = ( 340 | 6162CD2D1C2407F90094A00B /* Base */, 341 | ); 342 | name = LaunchScreen.storyboard; 343 | path = .; 344 | sourceTree = ""; 345 | }; 346 | /* End PBXVariantGroup section */ 347 | 348 | /* Begin XCBuildConfiguration section */ 349 | 6162CD3B1C2407F90094A00B /* Debug */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ALWAYS_SEARCH_USER_PATHS = NO; 353 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 354 | CLANG_CXX_LIBRARY = "libc++"; 355 | CLANG_ENABLE_MODULES = YES; 356 | CLANG_ENABLE_OBJC_ARC = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_CONSTANT_CONVERSION = YES; 359 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INT_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_UNREACHABLE_CODE = YES; 365 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 366 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu99; 372 | GCC_DYNAMIC_NO_PIC = NO; 373 | GCC_NO_COMMON_BLOCKS = YES; 374 | GCC_OPTIMIZATION_LEVEL = 0; 375 | GCC_PREPROCESSOR_DEFINITIONS = ( 376 | "DEBUG=1", 377 | "$(inherited)", 378 | ); 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 386 | MTL_ENABLE_DEBUG_INFO = YES; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = iphoneos; 389 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 390 | TARGETED_DEVICE_FAMILY = "1,2"; 391 | }; 392 | name = Debug; 393 | }; 394 | 6162CD3C1C2407F90094A00B /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 399 | CLANG_CXX_LIBRARY = "libc++"; 400 | CLANG_ENABLE_MODULES = YES; 401 | CLANG_ENABLE_OBJC_ARC = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_CONSTANT_CONVERSION = YES; 404 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 405 | CLANG_WARN_EMPTY_BODY = YES; 406 | CLANG_WARN_ENUM_CONVERSION = YES; 407 | CLANG_WARN_INT_CONVERSION = YES; 408 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 409 | CLANG_WARN_UNREACHABLE_CODE = YES; 410 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 411 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 412 | COPY_PHASE_STRIP = NO; 413 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 414 | ENABLE_NS_ASSERTIONS = NO; 415 | ENABLE_STRICT_OBJC_MSGSEND = YES; 416 | GCC_C_LANGUAGE_STANDARD = gnu99; 417 | GCC_NO_COMMON_BLOCKS = YES; 418 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 419 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 420 | GCC_WARN_UNDECLARED_SELECTOR = YES; 421 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 422 | GCC_WARN_UNUSED_FUNCTION = YES; 423 | GCC_WARN_UNUSED_VARIABLE = YES; 424 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 425 | MTL_ENABLE_DEBUG_INFO = NO; 426 | SDKROOT = iphoneos; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | 6162CD3E1C2407F90094A00B /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | buildSettings = { 435 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 436 | INFOPLIST_FILE = "TestMenu/Supporting Files/Info.plist"; 437 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.TestMenu; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | }; 442 | name = Debug; 443 | }; 444 | 6162CD3F1C2407F90094A00B /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | INFOPLIST_FILE = "TestMenu/Supporting Files/Info.plist"; 449 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.TestMenu; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | }; 454 | name = Release; 455 | }; 456 | 6162CD411C2407F90094A00B /* Debug */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | BUNDLE_LOADER = "$(TEST_HOST)"; 460 | INFOPLIST_FILE = TestMenuTests/Info.plist; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.TestMenuTests; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestMenu.app/TestMenu"; 465 | }; 466 | name = Debug; 467 | }; 468 | 6162CD421C2407F90094A00B /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | BUNDLE_LOADER = "$(TEST_HOST)"; 472 | INFOPLIST_FILE = TestMenuTests/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 474 | PRODUCT_BUNDLE_IDENTIFIER = com.redesignapp.ios.demo.TestMenuTests; 475 | PRODUCT_NAME = "$(TARGET_NAME)"; 476 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestMenu.app/TestMenu"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 6162CD1B1C2407F90094A00B /* Build configuration list for PBXProject "TestMenu" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 6162CD3B1C2407F90094A00B /* Debug */, 487 | 6162CD3C1C2407F90094A00B /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | 6162CD3D1C2407F90094A00B /* Build configuration list for PBXNativeTarget "TestMenu" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | 6162CD3E1C2407F90094A00B /* Debug */, 496 | 6162CD3F1C2407F90094A00B /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | 6162CD401C2407F90094A00B /* Build configuration list for PBXNativeTarget "TestMenuTests" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 6162CD411C2407F90094A00B /* Debug */, 505 | 6162CD421C2407F90094A00B /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | /* End XCConfigurationList section */ 511 | }; 512 | rootObject = 6162CD181C2407F90094A00B /* Project object */; 513 | } 514 | --------------------------------------------------------------------------------