├── GitHubImages
├── freeform.png
├── leftMenuVid.gif
├── rightConstraints.png
├── runtimeAttributes.png
├── scrollingEnabled.png
├── containerStoryboard.png
├── leftMenuConstraints.png
├── tabbedApplicationIcon.png
└── setConstraintsFromContainers.gif
├── LeftSlideoutMenu
├── Images.xcassets
│ ├── first.imageset
│ │ ├── first.pdf
│ │ └── Contents.json
│ ├── second.imageset
│ │ ├── second.pdf
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── ModalWindow.swift
├── FirstViewController.swift
├── SecondViewController.swift
├── TabVCTemplate.swift
├── LeftMenu.swift
├── Info.plist
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
└── ContainerVC.swift
├── LeftSlideoutMenu.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── LeftSlideoutMenu.xccheckout
├── xcuserdata
│ └── robertchen.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── LeftSlideoutMenu.xcscheme
└── project.pbxproj
├── LeftSlideoutMenuTests
├── Info.plist
└── LeftSlideoutMenuTests.swift
└── README.md
/GitHubImages/freeform.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/freeform.png
--------------------------------------------------------------------------------
/GitHubImages/leftMenuVid.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/leftMenuVid.gif
--------------------------------------------------------------------------------
/GitHubImages/rightConstraints.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/rightConstraints.png
--------------------------------------------------------------------------------
/GitHubImages/runtimeAttributes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/runtimeAttributes.png
--------------------------------------------------------------------------------
/GitHubImages/scrollingEnabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/scrollingEnabled.png
--------------------------------------------------------------------------------
/GitHubImages/containerStoryboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/containerStoryboard.png
--------------------------------------------------------------------------------
/GitHubImages/leftMenuConstraints.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/leftMenuConstraints.png
--------------------------------------------------------------------------------
/GitHubImages/tabbedApplicationIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/tabbedApplicationIcon.png
--------------------------------------------------------------------------------
/GitHubImages/setConstraintsFromContainers.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/GitHubImages/setConstraintsFromContainers.gif
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Images.xcassets/first.imageset/first.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/LeftSlideoutMenu/Images.xcassets/first.imageset/first.pdf
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Images.xcassets/second.imageset/second.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ThornTechPublic/LeftSlideoutMenu/HEAD/LeftSlideoutMenu/Images.xcassets/second.imageset/second.pdf
--------------------------------------------------------------------------------
/LeftSlideoutMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Images.xcassets/first.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "first.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Images.xcassets/second.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "second.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/LeftSlideoutMenu/ModalWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModalWindow.swift
3 | // LeftSlideoutMenu
4 | //
5 | // Created by Robert Chen on 8/5/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ModalWindow : UIViewController {
12 |
13 | @IBAction func close(_ sender: Any) {
14 | dismiss(animated: true, completion: nil)
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/FirstViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class FirstViewController: TabVCTemplate {
4 |
5 | override func viewDidLoad() {
6 | super.viewDidLoad()
7 | selectedTab = 0
8 | // do stuff here
9 | }
10 |
11 | @IBAction func toggleMenu(_ sender: Any) {
12 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)
13 | }
14 |
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/SecondViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class SecondViewController: TabVCTemplate {
4 |
5 | override func viewDidLoad() {
6 | super.viewDidLoad()
7 | selectedTab = 1
8 | // do stuff here
9 | }
10 |
11 | @IBAction func toggleMenu(_ sender: Any) {
12 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)
13 | }
14 |
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu.xcodeproj/xcuserdata/robertchen.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | LeftSlideoutMenu.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | FCDFC07E1B2F7F110015115E
16 |
17 | primary
18 |
19 |
20 | FCDFC0951B2F7F120015115E
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LeftSlideoutMenuTests/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 |
--------------------------------------------------------------------------------
/LeftSlideoutMenuTests/LeftSlideoutMenuTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeftSlideoutMenuTests.swift
3 | // LeftSlideoutMenuTests
4 | //
5 | // Created by Robert Chen on 6/15/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 |
12 | class LeftSlideoutMenuTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | XCTAssert(true, "Pass")
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measureBlock() {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/TabVCTemplate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabVCTemplate.swift
3 | // LeftSlideoutMenu
4 | //
5 | // Created by Robert Chen on 8/5/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TabVCTemplate : UIViewController {
12 |
13 | // placeholder for the tab's index
14 | var selectedTab = 0
15 |
16 | override func viewDidLoad() {
17 |
18 | // Sent from LeftMenu
19 | NotificationCenter.default.addObserver(self, selector: #selector(TabVCTemplate.openPushWindow), name: NSNotification.Name(rawValue: "openPushWindow"), object: nil)
20 |
21 | }
22 |
23 | deinit {
24 | NotificationCenter.default.removeObserver(self)
25 | }
26 |
27 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
28 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)
29 | view.endEditing(true)
30 | }
31 |
32 | @objc func openPushWindow(){
33 | if tabBarController?.selectedIndex == selectedTab {
34 | performSegue(withIdentifier: "openPushWindow", sender: nil)
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Images.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 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/LeftSlideoutMenu.xcodeproj/project.xcworkspace/xcshareddata/LeftSlideoutMenu.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | FA89CF3F-5556-43FB-8DC2-286EEDCBEC50
9 | IDESourceControlProjectName
10 | LeftSlideoutMenu
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 34690A32E65B0D821D562FB56D89D8FC20C244CF
14 | https://github.com/ThornTechPublic/LeftSlideoutMenu.git
15 |
16 | IDESourceControlProjectPath
17 | LeftSlideoutMenu.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 34690A32E65B0D821D562FB56D89D8FC20C244CF
21 | ../..
22 |
23 | IDESourceControlProjectURL
24 | https://github.com/ThornTechPublic/LeftSlideoutMenu.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 34690A32E65B0D821D562FB56D89D8FC20C244CF
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 34690A32E65B0D821D562FB56D89D8FC20C244CF
36 | IDESourceControlWCCName
37 | LeftSlideoutMenu
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/LeftMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LeftMenu.swift
3 | // LeftSlideoutMenu
4 | //
5 | // Created by Robert Chen on 8/5/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LeftMenu : UITableViewController {
12 |
13 | let menuOptions = ["Open Modal", "Open Push"]
14 |
15 | }
16 |
17 | // MARK: - UITableViewDelegate methods
18 |
19 | extension LeftMenu {
20 |
21 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
22 | tableView.deselectRow(at: indexPath as IndexPath, animated: true)
23 |
24 | switch indexPath.row {
25 | case 0:
26 | // ContainerVC.swift listens for this
27 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "openModalWindow"), object: nil)
28 | case 1:
29 | // Both FirstViewController and SecondViewController listen for this
30 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "openPushWindow"), object: nil)
31 | default:
32 | print("indexPath.row:: \(indexPath.row)")
33 | }
34 |
35 | // also close the menu
36 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)
37 |
38 | }
39 |
40 | }
41 |
42 | // MARK: - UITableViewDataSource methods
43 |
44 | extension LeftMenu {
45 |
46 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
47 | return 2
48 | }
49 |
50 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
51 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath as IndexPath)
52 | cell.textLabel?.text = menuOptions[indexPath.row]
53 | return cell
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/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 | UIStatusBarTintParameters
34 |
35 | UINavigationBar
36 |
37 | Style
38 | UIBarStyleDefault
39 | Translucent
40 |
41 |
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UISupportedInterfaceOrientations~ipad
50 |
51 | UIInterfaceOrientationPortrait
52 | UIInterfaceOrientationPortraitUpsideDown
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LeftSlideoutMenu
4 | //
5 | // Created by Robert Chen on 6/15/15.
6 | // Copyright (c) 2015 Thorn Technologies. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/ContainerVC.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ContainerVC : UIViewController {
4 |
5 | // This value matches the left menu's width in the Storyboard
6 | let leftMenuWidth:CGFloat = 260
7 |
8 | // Need a handle to the scrollView to open and close the menu
9 | @IBOutlet weak var scrollView: UIScrollView!
10 |
11 | override func viewDidLoad() {
12 |
13 | // Initially close menu programmatically. This needs to be done on the main thread initially in order to work.
14 | DispatchQueue.main.async() {
15 | self.closeMenu(animated: false)
16 | }
17 |
18 | // Tab bar controller's child pages have a top-left button toggles the menu
19 | NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.toggleMenu), name: NSNotification.Name(rawValue: "toggleMenu"), object: nil)
20 |
21 | NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.closeMenuViaNotification), name: NSNotification.Name(rawValue: "closeMenuViaNotification"), object: nil)
22 |
23 | // Close the menu when the device rotates
24 | NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.rotated), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
25 |
26 | // LeftMenu sends openModalWindow
27 | NotificationCenter.default.addObserver(self, selector: #selector(ContainerVC.openModalWindow), name: NSNotification.Name(rawValue: "openModalWindow"), object: nil)
28 |
29 | }
30 |
31 | // Cleanup notifications added in viewDidLoad
32 | deinit {
33 | NotificationCenter.default.removeObserver(self)
34 | }
35 |
36 | @objc func openModalWindow() {
37 | performSegue(withIdentifier: "openModalWindow", sender: nil)
38 | }
39 |
40 | @objc func toggleMenu() {
41 | scrollView.contentOffset.x == 0 ? closeMenu() : openMenu()
42 | }
43 |
44 | // This wrapper function is necessary because
45 | // closeMenu params do not match up with Notification
46 | @objc func closeMenuViaNotification(){
47 | closeMenu()
48 | }
49 |
50 | // Use scrollview content offset-x to slide the menu.
51 | func closeMenu(animated:Bool = true){
52 | scrollView.setContentOffset(CGPoint(x: leftMenuWidth, y: 0), animated: animated)
53 | }
54 |
55 | // Open is the natural state of the menu because of how the storyboard is setup.
56 | func openMenu(){
57 | print("opening menu")
58 | scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
59 | }
60 |
61 | // see http://stackoverflow.com/questions/25666269/ios8-swift-how-to-detect-orientation-change
62 | // close the menu when rotating to landscape.
63 | // Note: you have to put this on the main queue in order for it to work
64 | @objc func rotated(){
65 | if UIDeviceOrientationIsLandscape(UIDevice.current.orientation) {
66 | DispatchQueue.main.async() {
67 | print("closing menu on rotate")
68 | self.closeMenu()
69 | }
70 | }
71 | }
72 |
73 | }
74 |
75 | extension ContainerVC : UIScrollViewDelegate {
76 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
77 | print("scrollView.contentOffset.x:: \(scrollView.contentOffset.x)")
78 | }
79 |
80 | // http://www.4byte.cn/question/49110/uiscrollview-change-contentoffset-when-change-frame.html
81 | // When paging is enabled on a Scroll View,
82 | // a private method _adjustContentOffsetIfNecessary gets called,
83 | // presumably when present whatever controller is called.
84 | // The idea is to disable paging.
85 | // But we rely on paging to snap the slideout menu in place
86 | // (if you're relying on the built-in pan gesture).
87 | // So the approach is to keep paging disabled.
88 | // But enable it at the last minute during scrollViewWillBeginDragging.
89 | // And then turn it off once the scroll view stops moving.
90 | //
91 | // Approaches that don't work:
92 | // 1. automaticallyAdjustsScrollViewInsets -- don't bother
93 | // 2. overriding _adjustContentOffsetIfNecessary -- messing with private methods is a bad idea
94 | // 3. disable paging altogether. works, but at the loss of a feature
95 | // 4. nest the scrollview inside UIView, so UIKit doesn't mess with it. may have worked before,
96 | // but not anymore.
97 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
98 | scrollView.isPagingEnabled = true
99 | }
100 |
101 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
102 | scrollView.isPagingEnabled = false
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu.xcodeproj/xcuserdata/robertchen.xcuserdatad/xcschemes/LeftSlideoutMenu.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
75 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
94 |
96 |
102 |
103 |
104 |
105 |
107 |
108 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Create a Left Slideout Menu with Swift
2 |
3 | ----
4 |
5 | **Update**: Check out this [updated blog post](http://www.thorntech.com/2016/03/ios-tutorial-make-interactive-slide-menu-swift/) which uses a better approach to creating slideout menus.
6 |
7 | ----
8 |
9 | This is a sample project with a left slideout menu, and a two-tabbed application for the main body. The majority of the setup is done in the Storyboard.
10 |
11 | This repo is tied to [this blog post](http://www.thorntech.com/2015/06/want-to-implement-a-slideout-menu-in-your-swift-app-heres-how/).
12 |
13 | 
14 |
15 | ## Use Case
16 |
17 | * You lean heavily on Storyboard use, and value separation of UI code over portability.
18 | * You're using Swift, and want to avoid bridging Objective-C modules when possible
19 |
20 | ## Theory
21 |
22 | Apps tend to have a top level container like a `Tab Bar Controller` that cannot be embedded inside a View. The approach here is to wrap the main body and the left menu each inside a `Container View`. Now both of these elements can be arranged inside a wrapper `View Controller`.
23 |
24 | 
25 |
26 | A `ScrollView` is used to simulate opening and closing the menu by shifting the left menu on/off-screen.
27 |
28 | Since Storyboard changes aren't really portable across projects, here is a recipe on how to recreate this.
29 |
30 | ## Instructions
31 |
32 | ### Create project
33 |
34 | Start with a `Tabbed Application`. This is just to demonstrate that even a `Tab Bar Controller` can be used with slideout menus.
35 |
36 | Uncheck the box `Use Size Classes` for now, simply for the sake of screen real estate.
37 |
38 | 
39 |
40 | ### Containing View Controller
41 |
42 | Drop a `View Controller` onto the Storyboard. This is your entry point into the app, and acts as a big wrapper. Think of a [Laundry Sorting Cart](http://www.amazon.com/DecoBros-Heavy-Duty-3-Bag-Laundry-Sorter/dp/B00HLSTYNS/) that can hold baskets for lights and darks. Or in this case, a Menu and Tab Bar Controller.
43 |
44 | Check the box for `Is Initial View Controller`.
45 |
46 | Change the `Simulated Size` from `Fixed` to `Freeform`. Set the `Width` to 568 so we can fit the menu and Tab Bar side-by-side.
47 |
48 | 
49 |
50 | Create a new Swift file and set this View Controller's class to ContainerVC.
51 |
52 | ```
53 | import UIKit
54 | class ContainerVC : UIViewController {
55 | }
56 | ```
57 |
58 | ### ScrollView
59 |
60 | Inside Container View Controller, drop in a `ScrollView` and add constraints in all directions.
61 |
62 | Check the box for `Scrolling Enabled`. This allows you to pan the screen to slide the menu. You might need to disable this if your app uses horizontally scrolling elements.
63 |
64 | Check the box for `Paging Enabled`. This snaps the menu to either the open or closed state.
65 |
66 | Uncheck the box for `Bounces`. You don't really want to scroll past the right edge of the Tab Bar Controller.
67 |
68 | 
69 |
70 | Wire the IBOutlet to ContainerVC:
71 |
72 | ```
73 | @IBOutlet weak var scrollView: UIScrollView!
74 | ```
75 |
76 | ### Left Container
77 |
78 | The left container holds the menu, and is not quite the full width of the screen.
79 |
80 | Drag a `Container View` into the left side of the `ScrollView`.
81 |
82 | Add constraints for top, left, and right to the containing `ScrollView`.
83 |
84 | Hard-code the width to 260.
85 |
86 | Add a constraint for `Equal height` with the ContainerVC's embedded `View`. Note: don't constrain the height to the ScrollView.
87 |
88 | 
89 |
90 | Delete the embedded `View Controller` that came with the `Container View`.
91 |
92 | Drop a new `Table View Controller` onto the Storyboard, and connect using an `embed` segue.
93 |
94 | ### Right Container
95 |
96 | The right container holds the main body of your app, namely the `Tab Bar Controller`.
97 |
98 | Drag a second `Container View` into the right side of the `ScrollView`.
99 |
100 | Add constraints to the containing `ScrollView` for top, right, and bottom. Connect horizontally to the left `Container View` you created earlier.
101 |
102 | Constraint both `Equal height` and `Equal width` to the ContainerVC's embedded `View`. Note: do not constrain these to the ScrollView.
103 |
104 | 
105 |
106 | Here's a short demo of setting the `Equal height` and `width` constraints to the `View`:
107 |
108 | 
109 |
110 | Again, delete the embedded View Controller that came free with the `Container View`. Instead, create an `embed` segue to the `Tab Bar Controller`.
111 |
112 | To create a little visual separation between the two containers, add a `Runtime Attribute` to the `Right Container`. Add `layer.shadowOpacity` with a `number` of `0.8`.
113 |
114 | 
115 |
116 | ### First and Second Tabs
117 |
118 | Embed each tab inside a `Navigation Controller`. Doing so gives you a free `Navigation Bar`.
119 |
120 | Drag a `Bar Button Item` into the top left corner of each `Navigation Bar`.
121 |
122 | Wire an `IBAction` to each controller. These will fire off a `NSNotification` to the great-grandfather `ContainerVC` to toggle the menu.
123 |
124 | ```
125 | @IBAction func toggleMenu(sender: AnyObject) {
126 | NSNotificationCenter.defaultCenter().postNotificationName("toggleMenu", object: nil)
127 | }
128 | ```
129 |
130 | ### ContainerVC
131 |
132 | Most of the Storyboard configuration is done. All that's left is to copy the code from [ContainerVC.swift](https://github.com/ThornTechPublic/LeftSlideoutMenu/blob/master/LeftSlideoutMenu/ContainerVC.swift).
133 |
134 | The code accomplishes the following:
135 |
136 | * Listen for the "toggleMenu" notification
137 | * Implement the toggleMenu method by opening or closing based on the current contentOffset.x
138 | * Open and close the menu by changing the contentOffset-x
139 |
140 | ### That's it
141 |
142 | Build and run, and hopefully you have a simple slideout left menu on which to build the rest of your app.
143 |
144 | ### Still confused?
145 |
146 | The `ScrollView` is pretty confusing to begin with. One common trick is to have a rigidly defined view on the inside. In this case, the left and right container views are constrained to the device (View Controller's embedded `View`) width and height. The `ScrollView` acts like a flexible wrapper.
147 |
148 | Here's an excellent video tutorial on [Ray Wenderlich](http://www.raywenderlich.com/video-tutorials#swiftscrollview) that covers the `ScrollView`. Note parts 13 and 14 cover the Slide-out-sidebar upon which the idea of using the ScrollView for a slideout menu is based upon.
149 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | FCDFC0851B2F7F110015115E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDFC0841B2F7F110015115E /* AppDelegate.swift */; };
11 | FCDFC0871B2F7F110015115E /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDFC0861B2F7F110015115E /* FirstViewController.swift */; };
12 | FCDFC0891B2F7F110015115E /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDFC0881B2F7F110015115E /* SecondViewController.swift */; };
13 | FCDFC08C1B2F7F110015115E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FCDFC08A1B2F7F110015115E /* Main.storyboard */; };
14 | FCDFC08E1B2F7F120015115E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FCDFC08D1B2F7F120015115E /* Images.xcassets */; };
15 | FCDFC0911B2F7F120015115E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = FCDFC08F1B2F7F120015115E /* LaunchScreen.xib */; };
16 | FCDFC09D1B2F7F120015115E /* LeftSlideoutMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDFC09C1B2F7F120015115E /* LeftSlideoutMenuTests.swift */; };
17 | FCDFC0A71B2F87B00015115E /* ContainerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCDFC0A61B2F87B00015115E /* ContainerVC.swift */; };
18 | FCE2F37B1B7248BC00773729 /* LeftMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE2F37A1B7248BC00773729 /* LeftMenu.swift */; };
19 | FCE2F37D1B724B6B00773729 /* ModalWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE2F37C1B724B6B00773729 /* ModalWindow.swift */; };
20 | FCE2F37F1B724C4D00773729 /* TabVCTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE2F37E1B724C4D00773729 /* TabVCTemplate.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXContainerItemProxy section */
24 | FCDFC0971B2F7F120015115E /* PBXContainerItemProxy */ = {
25 | isa = PBXContainerItemProxy;
26 | containerPortal = FCDFC0771B2F7F110015115E /* Project object */;
27 | proxyType = 1;
28 | remoteGlobalIDString = FCDFC07E1B2F7F110015115E;
29 | remoteInfo = LeftSlideoutMenu;
30 | };
31 | /* End PBXContainerItemProxy section */
32 |
33 | /* Begin PBXFileReference section */
34 | FCDFC07F1B2F7F110015115E /* LeftSlideoutMenu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LeftSlideoutMenu.app; sourceTree = BUILT_PRODUCTS_DIR; };
35 | FCDFC0831B2F7F110015115E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
36 | FCDFC0841B2F7F110015115E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | FCDFC0861B2F7F110015115E /* FirstViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; };
38 | FCDFC0881B2F7F110015115E /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; };
39 | FCDFC08B1B2F7F110015115E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
40 | FCDFC08D1B2F7F120015115E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
41 | FCDFC0901B2F7F120015115E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
42 | FCDFC0961B2F7F120015115E /* LeftSlideoutMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LeftSlideoutMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
43 | FCDFC09B1B2F7F120015115E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
44 | FCDFC09C1B2F7F120015115E /* LeftSlideoutMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftSlideoutMenuTests.swift; sourceTree = ""; };
45 | FCDFC0A61B2F87B00015115E /* ContainerVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerVC.swift; sourceTree = ""; };
46 | FCE2F37A1B7248BC00773729 /* LeftMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftMenu.swift; sourceTree = ""; };
47 | FCE2F37C1B724B6B00773729 /* ModalWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalWindow.swift; sourceTree = ""; };
48 | FCE2F37E1B724C4D00773729 /* TabVCTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabVCTemplate.swift; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | FCDFC07C1B2F7F110015115E /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | FCDFC0931B2F7F120015115E /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | );
64 | runOnlyForDeploymentPostprocessing = 0;
65 | };
66 | /* End PBXFrameworksBuildPhase section */
67 |
68 | /* Begin PBXGroup section */
69 | FCDFC0761B2F7F110015115E = {
70 | isa = PBXGroup;
71 | children = (
72 | FCDFC0811B2F7F110015115E /* LeftSlideoutMenu */,
73 | FCDFC0991B2F7F120015115E /* LeftSlideoutMenuTests */,
74 | FCDFC0801B2F7F110015115E /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | FCDFC0801B2F7F110015115E /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | FCDFC07F1B2F7F110015115E /* LeftSlideoutMenu.app */,
82 | FCDFC0961B2F7F120015115E /* LeftSlideoutMenuTests.xctest */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | FCDFC0811B2F7F110015115E /* LeftSlideoutMenu */ = {
88 | isa = PBXGroup;
89 | children = (
90 | FCDFC0841B2F7F110015115E /* AppDelegate.swift */,
91 | FCDFC0A61B2F87B00015115E /* ContainerVC.swift */,
92 | FCDFC0861B2F7F110015115E /* FirstViewController.swift */,
93 | FCDFC0881B2F7F110015115E /* SecondViewController.swift */,
94 | FCE2F37E1B724C4D00773729 /* TabVCTemplate.swift */,
95 | FCE2F37A1B7248BC00773729 /* LeftMenu.swift */,
96 | FCE2F37C1B724B6B00773729 /* ModalWindow.swift */,
97 | FCDFC08A1B2F7F110015115E /* Main.storyboard */,
98 | FCDFC08D1B2F7F120015115E /* Images.xcassets */,
99 | FCDFC08F1B2F7F120015115E /* LaunchScreen.xib */,
100 | FCDFC0821B2F7F110015115E /* Supporting Files */,
101 | );
102 | path = LeftSlideoutMenu;
103 | sourceTree = "";
104 | };
105 | FCDFC0821B2F7F110015115E /* Supporting Files */ = {
106 | isa = PBXGroup;
107 | children = (
108 | FCDFC0831B2F7F110015115E /* Info.plist */,
109 | );
110 | name = "Supporting Files";
111 | sourceTree = "";
112 | };
113 | FCDFC0991B2F7F120015115E /* LeftSlideoutMenuTests */ = {
114 | isa = PBXGroup;
115 | children = (
116 | FCDFC09C1B2F7F120015115E /* LeftSlideoutMenuTests.swift */,
117 | FCDFC09A1B2F7F120015115E /* Supporting Files */,
118 | );
119 | path = LeftSlideoutMenuTests;
120 | sourceTree = "";
121 | };
122 | FCDFC09A1B2F7F120015115E /* Supporting Files */ = {
123 | isa = PBXGroup;
124 | children = (
125 | FCDFC09B1B2F7F120015115E /* Info.plist */,
126 | );
127 | name = "Supporting Files";
128 | sourceTree = "";
129 | };
130 | /* End PBXGroup section */
131 |
132 | /* Begin PBXNativeTarget section */
133 | FCDFC07E1B2F7F110015115E /* LeftSlideoutMenu */ = {
134 | isa = PBXNativeTarget;
135 | buildConfigurationList = FCDFC0A01B2F7F120015115E /* Build configuration list for PBXNativeTarget "LeftSlideoutMenu" */;
136 | buildPhases = (
137 | FCDFC07B1B2F7F110015115E /* Sources */,
138 | FCDFC07C1B2F7F110015115E /* Frameworks */,
139 | FCDFC07D1B2F7F110015115E /* Resources */,
140 | );
141 | buildRules = (
142 | );
143 | dependencies = (
144 | );
145 | name = LeftSlideoutMenu;
146 | productName = LeftSlideoutMenu;
147 | productReference = FCDFC07F1B2F7F110015115E /* LeftSlideoutMenu.app */;
148 | productType = "com.apple.product-type.application";
149 | };
150 | FCDFC0951B2F7F120015115E /* LeftSlideoutMenuTests */ = {
151 | isa = PBXNativeTarget;
152 | buildConfigurationList = FCDFC0A31B2F7F120015115E /* Build configuration list for PBXNativeTarget "LeftSlideoutMenuTests" */;
153 | buildPhases = (
154 | FCDFC0921B2F7F120015115E /* Sources */,
155 | FCDFC0931B2F7F120015115E /* Frameworks */,
156 | FCDFC0941B2F7F120015115E /* Resources */,
157 | );
158 | buildRules = (
159 | );
160 | dependencies = (
161 | FCDFC0981B2F7F120015115E /* PBXTargetDependency */,
162 | );
163 | name = LeftSlideoutMenuTests;
164 | productName = LeftSlideoutMenuTests;
165 | productReference = FCDFC0961B2F7F120015115E /* LeftSlideoutMenuTests.xctest */;
166 | productType = "com.apple.product-type.bundle.unit-test";
167 | };
168 | /* End PBXNativeTarget section */
169 |
170 | /* Begin PBXProject section */
171 | FCDFC0771B2F7F110015115E /* Project object */ = {
172 | isa = PBXProject;
173 | attributes = {
174 | LastSwiftMigration = 0720;
175 | LastSwiftUpdateCheck = 0720;
176 | LastUpgradeCheck = 0910;
177 | ORGANIZATIONNAME = "Thorn Technologies";
178 | TargetAttributes = {
179 | FCDFC07E1B2F7F110015115E = {
180 | CreatedOnToolsVersion = 6.3.1;
181 | ProvisioningStyle = Manual;
182 | };
183 | FCDFC0951B2F7F120015115E = {
184 | CreatedOnToolsVersion = 6.3.1;
185 | TestTargetID = FCDFC07E1B2F7F110015115E;
186 | };
187 | };
188 | };
189 | buildConfigurationList = FCDFC07A1B2F7F110015115E /* Build configuration list for PBXProject "LeftSlideoutMenu" */;
190 | compatibilityVersion = "Xcode 3.2";
191 | developmentRegion = English;
192 | hasScannedForEncodings = 0;
193 | knownRegions = (
194 | en,
195 | Base,
196 | );
197 | mainGroup = FCDFC0761B2F7F110015115E;
198 | productRefGroup = FCDFC0801B2F7F110015115E /* Products */;
199 | projectDirPath = "";
200 | projectRoot = "";
201 | targets = (
202 | FCDFC07E1B2F7F110015115E /* LeftSlideoutMenu */,
203 | FCDFC0951B2F7F120015115E /* LeftSlideoutMenuTests */,
204 | );
205 | };
206 | /* End PBXProject section */
207 |
208 | /* Begin PBXResourcesBuildPhase section */
209 | FCDFC07D1B2F7F110015115E /* Resources */ = {
210 | isa = PBXResourcesBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | FCDFC08C1B2F7F110015115E /* Main.storyboard in Resources */,
214 | FCDFC0911B2F7F120015115E /* LaunchScreen.xib in Resources */,
215 | FCDFC08E1B2F7F120015115E /* Images.xcassets in Resources */,
216 | );
217 | runOnlyForDeploymentPostprocessing = 0;
218 | };
219 | FCDFC0941B2F7F120015115E /* Resources */ = {
220 | isa = PBXResourcesBuildPhase;
221 | buildActionMask = 2147483647;
222 | files = (
223 | );
224 | runOnlyForDeploymentPostprocessing = 0;
225 | };
226 | /* End PBXResourcesBuildPhase section */
227 |
228 | /* Begin PBXSourcesBuildPhase section */
229 | FCDFC07B1B2F7F110015115E /* Sources */ = {
230 | isa = PBXSourcesBuildPhase;
231 | buildActionMask = 2147483647;
232 | files = (
233 | FCE2F37B1B7248BC00773729 /* LeftMenu.swift in Sources */,
234 | FCDFC0A71B2F87B00015115E /* ContainerVC.swift in Sources */,
235 | FCDFC0891B2F7F110015115E /* SecondViewController.swift in Sources */,
236 | FCE2F37D1B724B6B00773729 /* ModalWindow.swift in Sources */,
237 | FCE2F37F1B724C4D00773729 /* TabVCTemplate.swift in Sources */,
238 | FCDFC0851B2F7F110015115E /* AppDelegate.swift in Sources */,
239 | FCDFC0871B2F7F110015115E /* FirstViewController.swift in Sources */,
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | };
243 | FCDFC0921B2F7F120015115E /* Sources */ = {
244 | isa = PBXSourcesBuildPhase;
245 | buildActionMask = 2147483647;
246 | files = (
247 | FCDFC09D1B2F7F120015115E /* LeftSlideoutMenuTests.swift in Sources */,
248 | );
249 | runOnlyForDeploymentPostprocessing = 0;
250 | };
251 | /* End PBXSourcesBuildPhase section */
252 |
253 | /* Begin PBXTargetDependency section */
254 | FCDFC0981B2F7F120015115E /* PBXTargetDependency */ = {
255 | isa = PBXTargetDependency;
256 | target = FCDFC07E1B2F7F110015115E /* LeftSlideoutMenu */;
257 | targetProxy = FCDFC0971B2F7F120015115E /* PBXContainerItemProxy */;
258 | };
259 | /* End PBXTargetDependency section */
260 |
261 | /* Begin PBXVariantGroup section */
262 | FCDFC08A1B2F7F110015115E /* Main.storyboard */ = {
263 | isa = PBXVariantGroup;
264 | children = (
265 | FCDFC08B1B2F7F110015115E /* Base */,
266 | );
267 | name = Main.storyboard;
268 | sourceTree = "";
269 | };
270 | FCDFC08F1B2F7F120015115E /* LaunchScreen.xib */ = {
271 | isa = PBXVariantGroup;
272 | children = (
273 | FCDFC0901B2F7F120015115E /* Base */,
274 | );
275 | name = LaunchScreen.xib;
276 | sourceTree = "";
277 | };
278 | /* End PBXVariantGroup section */
279 |
280 | /* Begin XCBuildConfiguration section */
281 | FCDFC09E1B2F7F120015115E /* Debug */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ALWAYS_SEARCH_USER_PATHS = NO;
285 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
286 | CLANG_CXX_LIBRARY = "libc++";
287 | CLANG_ENABLE_MODULES = YES;
288 | CLANG_ENABLE_OBJC_ARC = YES;
289 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
290 | CLANG_WARN_BOOL_CONVERSION = YES;
291 | CLANG_WARN_COMMA = YES;
292 | CLANG_WARN_CONSTANT_CONVERSION = YES;
293 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
294 | CLANG_WARN_EMPTY_BODY = YES;
295 | CLANG_WARN_ENUM_CONVERSION = YES;
296 | CLANG_WARN_INFINITE_RECURSION = YES;
297 | CLANG_WARN_INT_CONVERSION = YES;
298 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
301 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
302 | CLANG_WARN_STRICT_PROTOTYPES = YES;
303 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
304 | CLANG_WARN_UNREACHABLE_CODE = YES;
305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
306 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
307 | COPY_PHASE_STRIP = NO;
308 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
309 | ENABLE_STRICT_OBJC_MSGSEND = YES;
310 | ENABLE_TESTABILITY = YES;
311 | GCC_C_LANGUAGE_STANDARD = gnu99;
312 | GCC_DYNAMIC_NO_PIC = NO;
313 | GCC_NO_COMMON_BLOCKS = YES;
314 | GCC_OPTIMIZATION_LEVEL = 0;
315 | GCC_PREPROCESSOR_DEFINITIONS = (
316 | "DEBUG=1",
317 | "$(inherited)",
318 | );
319 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
322 | GCC_WARN_UNDECLARED_SELECTOR = YES;
323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
324 | GCC_WARN_UNUSED_FUNCTION = YES;
325 | GCC_WARN_UNUSED_VARIABLE = YES;
326 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
327 | MTL_ENABLE_DEBUG_INFO = YES;
328 | ONLY_ACTIVE_ARCH = YES;
329 | SDKROOT = iphoneos;
330 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
331 | SWIFT_VERSION = 4.0;
332 | TARGETED_DEVICE_FAMILY = "1,2";
333 | };
334 | name = Debug;
335 | };
336 | FCDFC09F1B2F7F120015115E /* Release */ = {
337 | isa = XCBuildConfiguration;
338 | buildSettings = {
339 | ALWAYS_SEARCH_USER_PATHS = NO;
340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
341 | CLANG_CXX_LIBRARY = "libc++";
342 | CLANG_ENABLE_MODULES = YES;
343 | CLANG_ENABLE_OBJC_ARC = YES;
344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
345 | CLANG_WARN_BOOL_CONVERSION = YES;
346 | CLANG_WARN_COMMA = YES;
347 | CLANG_WARN_CONSTANT_CONVERSION = YES;
348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
349 | CLANG_WARN_EMPTY_BODY = YES;
350 | CLANG_WARN_ENUM_CONVERSION = YES;
351 | CLANG_WARN_INFINITE_RECURSION = YES;
352 | CLANG_WARN_INT_CONVERSION = YES;
353 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
356 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
357 | CLANG_WARN_STRICT_PROTOTYPES = YES;
358 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
359 | CLANG_WARN_UNREACHABLE_CODE = YES;
360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
362 | COPY_PHASE_STRIP = NO;
363 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
364 | ENABLE_NS_ASSERTIONS = NO;
365 | ENABLE_STRICT_OBJC_MSGSEND = YES;
366 | GCC_C_LANGUAGE_STANDARD = gnu99;
367 | GCC_NO_COMMON_BLOCKS = YES;
368 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
369 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
370 | GCC_WARN_UNDECLARED_SELECTOR = YES;
371 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
372 | GCC_WARN_UNUSED_FUNCTION = YES;
373 | GCC_WARN_UNUSED_VARIABLE = YES;
374 | IPHONEOS_DEPLOYMENT_TARGET = 8.3;
375 | MTL_ENABLE_DEBUG_INFO = NO;
376 | SDKROOT = iphoneos;
377 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
378 | SWIFT_VERSION = 4.0;
379 | TARGETED_DEVICE_FAMILY = "1,2";
380 | VALIDATE_PRODUCT = YES;
381 | };
382 | name = Release;
383 | };
384 | FCDFC0A11B2F7F120015115E /* Debug */ = {
385 | isa = XCBuildConfiguration;
386 | buildSettings = {
387 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
388 | CODE_SIGN_STYLE = Manual;
389 | DEVELOPMENT_TEAM = "";
390 | INFOPLIST_FILE = LeftSlideoutMenu/Info.plist;
391 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
392 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
393 | PRODUCT_NAME = "$(TARGET_NAME)";
394 | PROVISIONING_PROFILE_SPECIFIER = "";
395 | SWIFT_VERSION = 4.0;
396 | };
397 | name = Debug;
398 | };
399 | FCDFC0A21B2F7F120015115E /* Release */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
403 | CODE_SIGN_STYLE = Manual;
404 | DEVELOPMENT_TEAM = "";
405 | INFOPLIST_FILE = LeftSlideoutMenu/Info.plist;
406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
407 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
408 | PRODUCT_NAME = "$(TARGET_NAME)";
409 | PROVISIONING_PROFILE_SPECIFIER = "";
410 | SWIFT_VERSION = 4.0;
411 | };
412 | name = Release;
413 | };
414 | FCDFC0A41B2F7F120015115E /* Debug */ = {
415 | isa = XCBuildConfiguration;
416 | buildSettings = {
417 | BUNDLE_LOADER = "$(TEST_HOST)";
418 | FRAMEWORK_SEARCH_PATHS = (
419 | "$(SDKROOT)/Developer/Library/Frameworks",
420 | "$(inherited)",
421 | );
422 | GCC_PREPROCESSOR_DEFINITIONS = (
423 | "DEBUG=1",
424 | "$(inherited)",
425 | );
426 | INFOPLIST_FILE = LeftSlideoutMenuTests/Info.plist;
427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
428 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
429 | PRODUCT_NAME = "$(TARGET_NAME)";
430 | SWIFT_VERSION = 4.0;
431 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LeftSlideoutMenu.app/LeftSlideoutMenu";
432 | };
433 | name = Debug;
434 | };
435 | FCDFC0A51B2F7F120015115E /* Release */ = {
436 | isa = XCBuildConfiguration;
437 | buildSettings = {
438 | BUNDLE_LOADER = "$(TEST_HOST)";
439 | FRAMEWORK_SEARCH_PATHS = (
440 | "$(SDKROOT)/Developer/Library/Frameworks",
441 | "$(inherited)",
442 | );
443 | INFOPLIST_FILE = LeftSlideoutMenuTests/Info.plist;
444 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
445 | PRODUCT_BUNDLE_IDENTIFIER = "com.thorntech.$(PRODUCT_NAME:rfc1034identifier)";
446 | PRODUCT_NAME = "$(TARGET_NAME)";
447 | SWIFT_VERSION = 4.0;
448 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LeftSlideoutMenu.app/LeftSlideoutMenu";
449 | };
450 | name = Release;
451 | };
452 | /* End XCBuildConfiguration section */
453 |
454 | /* Begin XCConfigurationList section */
455 | FCDFC07A1B2F7F110015115E /* Build configuration list for PBXProject "LeftSlideoutMenu" */ = {
456 | isa = XCConfigurationList;
457 | buildConfigurations = (
458 | FCDFC09E1B2F7F120015115E /* Debug */,
459 | FCDFC09F1B2F7F120015115E /* Release */,
460 | );
461 | defaultConfigurationIsVisible = 0;
462 | defaultConfigurationName = Release;
463 | };
464 | FCDFC0A01B2F7F120015115E /* Build configuration list for PBXNativeTarget "LeftSlideoutMenu" */ = {
465 | isa = XCConfigurationList;
466 | buildConfigurations = (
467 | FCDFC0A11B2F7F120015115E /* Debug */,
468 | FCDFC0A21B2F7F120015115E /* Release */,
469 | );
470 | defaultConfigurationIsVisible = 0;
471 | defaultConfigurationName = Release;
472 | };
473 | FCDFC0A31B2F7F120015115E /* Build configuration list for PBXNativeTarget "LeftSlideoutMenuTests" */ = {
474 | isa = XCConfigurationList;
475 | buildConfigurations = (
476 | FCDFC0A41B2F7F120015115E /* Debug */,
477 | FCDFC0A51B2F7F120015115E /* Release */,
478 | );
479 | defaultConfigurationIsVisible = 0;
480 | defaultConfigurationName = Release;
481 | };
482 | /* End XCConfigurationList section */
483 | };
484 | rootObject = FCDFC0771B2F7F110015115E /* Project object */;
485 | }
486 |
--------------------------------------------------------------------------------
/LeftSlideoutMenu/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Helvetica
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
206 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
--------------------------------------------------------------------------------