├── .circleci
└── config.yml
├── .github
├── CONTRIBUTING.md
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
└── ISSUE_TEMPLATE
│ ├── bug-report.md
│ └── feature-request.md
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── Example
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ ├── background.imageset
│ │ ├── Contents.json
│ │ └── stars.png
│ └── saturn.imageset
│ │ ├── Contents.json
│ │ └── saturn.png
├── Base.lproj
│ └── LaunchScreen.xib
├── Info.plist
├── Launch Screen.storyboard
├── Main.storyboard
├── MainViewController.swift
├── PresentedViewController.swift
└── SideMenuTableViewController.swift
├── ExampleTests
├── ExampleTests.swift
└── Info.plist
├── LICENSE
├── Package.swift
├── Pod
└── Classes
│ ├── Deprecations.swift
│ ├── Extensions.swift
│ ├── Initializable.swift
│ ├── Print.swift
│ ├── Protected.swift
│ ├── SideMenuAnimationController.swift
│ ├── SideMenuInteractionController.swift
│ ├── SideMenuManager.swift
│ ├── SideMenuNavigationController.swift
│ ├── SideMenuPresentationController.swift
│ ├── SideMenuPresentationStyle.swift
│ ├── SideMenuPushCoordinator.swift
│ ├── SideMenuTransitionController.swift
│ └── UITableViewVibrantCell.swift
├── Podfile
├── Podfile.lock
├── Pods
├── Local Podspecs
│ └── SideMenu.podspec.json
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
└── Target Support Files
│ ├── Pods-Example
│ ├── Info.plist
│ ├── Pods-Example-Info.plist
│ ├── Pods-Example-acknowledgements.markdown
│ ├── Pods-Example-acknowledgements.plist
│ ├── Pods-Example-dummy.m
│ ├── Pods-Example-frameworks.sh
│ ├── Pods-Example-resources.sh
│ ├── Pods-Example-umbrella.h
│ ├── Pods-Example.debug.xcconfig
│ ├── Pods-Example.modulemap
│ └── Pods-Example.release.xcconfig
│ └── SideMenu
│ ├── Info.plist
│ ├── SideMenu-Info.plist
│ ├── SideMenu-dummy.m
│ ├── SideMenu-prefix.pch
│ ├── SideMenu-umbrella.h
│ ├── SideMenu.modulemap
│ └── SideMenu.xcconfig
├── README.md
├── SideMenu.podspec
├── SideMenu.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ ├── SideMenu Example.xcscheme
│ └── SideMenu.xcscheme
├── SideMenu.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── SideMenu
├── Info.plist
├── SideMenu.h
└── module.modulemap
└── etc
├── Dissolve.gif
├── InOut.gif
├── Screenshot1.png
├── Screenshot2.png
├── Screenshot3.png
├── SlideIn.gif
└── SlideOut.gif
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # iOS CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/ios-migrating-from-1-2/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 |
9 | # Specify the Xcode version to use
10 | macos:
11 | xcode: "10.3.0"
12 |
13 | steps:
14 | - checkout
15 |
16 | # Install CocoaPods
17 | - run:
18 | name: Install CocoaPods
19 | command: pod install
20 |
21 | # Build the app and run tests
22 | - run:
23 | name: Build and run tests
24 | command: fastlane scan
25 | environment:
26 | SCAN_DEVICE: iPhone Xʀ
27 | SCAN_SCHEME: SideMenu Example
28 |
29 | # Collect XML test results data to show in the UI,
30 | # and save the same XML files under test-results folder
31 | # in the Artifacts tab
32 | - store_test_results:
33 | path: test_output/report.xml
34 | - store_artifacts:
35 | path: /tmp/test-results
36 | destination: scan-test-results
37 | - store_artifacts:
38 | path: ~/Library/Logs/scan
39 | destination: scan-logs
40 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | Thank you for your interest in SideMenu!
4 |
5 | I have received a surprising amount of questions about SideMenu since putting it up here. A few people in the community have identified some problems and helped contribute to SideMenu to make it better for everyone and I'm truly grateful for the support! Keep them coming!
6 |
7 | I have also received a number of questions about people having issues implementing SideMenu, mostly from beginners learning how to code. As much as I would love to help all of you, **I do not have time to teach you**. I am only supporting bugfixes or reviewing pull requests.
8 |
9 | I spent a lot of time putting together a detailed [README](https://github.com/jonkykong/SideMenu/blob/master/README.md), adding comments about usage in code, and provided a [demo project](https://github.com/jonkykong/SideMenu/tree/master/Example). These will give you all the information you need to work through any problem, **saving _you_ the time it takes for me to personally respond.**
10 |
11 | ### If your question begins with _"How do I..."_
12 | - That's **not** a bug. Go back through the [README](https://github.com/jonkykong/SideMenu/blob/master/README.md). Check out the [demo project](https://github.com/jonkykong/SideMenu/tree/master/Example). Stay persistent, try a few different things, and you will figure it out! **It is generally faster for you to figure it out for yourself instead of waiting for me to respond to you.** I also recommend searching and posting your questions on [stackoverflow.com](stackoverflow.com). If you're interested in hiring me to teach you, please email me using the email address listed at the top of my [README](https://github.com/jonkykong/SideMenu/blob/master/README.md).
13 |
14 | ### If your question begins with _"How can I..."_
15 | - That's a **new feature request**. I am no longer investing my personal time to implement one-off features because SideMenu currently meets the majority of people's needs with the features it already has. However, this is a great opportunity for you to [join the proud members](https://github.com/jonkykong/SideMenu/graphs/contributors) who have contributed to this open source project! Feel free to open an issue to ask any clarifying questions for your new feature before you start building. Open a [pull request](https://github.com/jonkykong/SideMenu/pull/new/master) when you're ready for me to merge it.
16 |
17 | ### If your question is about SideMenu not working the way it's described in the [README](https://github.com/jonkykong/SideMenu/blob/master/README.md)...
18 | - This *may* be a bug. You must be able to reproduce the bug in the [demo project](https://github.com/jonkykong/SideMenu/tree/master/Example) which has a minimal amount of code. This helps ensure you don't have a bug in your code unrelated to SideMenu. If the bug is reproducable, open an issue and I will respond to it when I find time.
19 |
20 | **Again**, please do **not** email me or open any issues if you want to know how to use SideMenu or are having trouble getting it to behave a specific way not described in the [README](https://github.com/jonkykong/SideMenu/blob/master/README.md). I am not **tech support**. I am not a **teacher**. If you open an issue while failing to follow these guidelines or use the provided templates your **request for help will be ignored**.
21 |
22 | ### Thanks again for your support and for being respectful of my time.
23 | I apologize if this seems harsh, but there have been too many developers that have willfully ignored all of this and continued to contact me.
24 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [jonkykong]
2 | custom: ["https://www.paypal.me/jonkykong"]
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | ## New Issue Checklist
3 |
4 | I have read the [guidelines for contributing](https://github.com/jonkykong/SideMenu/blob/master/.github/CONTRIBUTING.md) and I understand:
5 | - [ ] My issue is happening in the **latest version** of SideMenu.
6 | - [ ] My issue was **not** solved in the [README](https://github.com/jonkykong/SideMenu/blob/master/README.md).
7 | - [ ] My issue can **not** be answered on [stackoverflow.com](stackoverflow.com).
8 | - [ ] My issue is **not** a request for new functionality that I am unwilling to build and contribute with a pull request.
9 | - [ ] My issue **is** reproducible in the [demo project](https://github.com/jonkykong/SideMenu/tree/master/Example).
10 |
11 | ## Issue Description
12 |
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: You must complete this template or your report will be automatically closed.
4 | title: ''
5 | labels: Bug - Help Wanted!
6 | assignees: ''
7 |
8 | ---
9 |
10 | **I have read the [guidelines for contributing](https://github.com/jonkykong/SideMenu/blob/master/.github/CONTRIBUTING.md) and I understand**
11 | - [ ] My issue is happening in the **latest version** of SideMenu (older versions are no longer maintained).
12 | - [ ] My issue was **not** solved in the [README](https://github.com/jonkykong/SideMenu/blob/master/README.md).
13 | - [ ] My issue can **not** be answered on [stackoverflow.com](stackoverflow.com).
14 | - [ ] My issue is **not** a request for new functionality that I am unwilling to build and contribute with a pull request.
15 | - [ ] My issue **is** reproducible in the [demo project](https://github.com/jonkykong/SideMenu/tree/master/Example).
16 |
17 | **Describe the bug**
18 | A clear and concise description of what the bug is.
19 |
20 | **To Reproduce**
21 | Steps to reproduce the behavior:
22 | 1. Go to '...'
23 | 2. Click on '....'
24 | 3. Scroll down to '....'
25 | 4. See error
26 |
27 | **Expected behavior**
28 | A clear and concise description of what you expected to happen.
29 |
30 | **Screenshots**
31 | If applicable, add screenshots to help explain your problem.
32 |
33 | **Additional context**
34 | Add any other context about the problem here.
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Suggest an idea for this project.
4 | title: ''
5 | labels: Feature Request - Help Wanted!
6 | assignees: ''
7 |
8 | ---
9 |
10 | SideMenu currently meets the majority of people's needs with the features it already has. However, this is a great opportunity for you to [join the proud members](https://github.com/jonkykong/SideMenu/graphs/contributors) who have contributed to this open source project! Feel free to open an issue to ask any clarifying questions for your new feature before you start building. Open a [pull request](https://github.com/jonkykong/SideMenu/pull/new/master) when you're ready for me to merge it.
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Ask any questions you have related to developing your solution**
16 | A clear and concise set of questions you'd like to ask.
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | Carthage
26 | # We recommend against adding the Pods directory to your .gitignore. However
27 | # you should judge for yourself, the pros and cons are mentioned at:
28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
29 | #
30 | # Note: if you ignore the Pods directory, make sure to uncomment
31 | # `pod install` in .travis.yml
32 | #
33 | # Pods/
34 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | language: objective-c
6 | # cache: cocoapods
7 | # podfile: Example/Podfile
8 | # before_install:
9 | # - gem install cocoapods # Since Travis is not always on latest version
10 | # - pod install --project-directory=Example
11 | script:
12 | - set -o pipefail && xcodebuild test -workspace SideMenu.xcworkspace -scheme Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
13 | - pod lib lint
14 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at yo@massappeal.co. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SideMenu
4 | //
5 | // Created by jonkykong on 12/23/2015.
6 | // Copyright (c) 2015 jonkykong. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | func applicationWillResignActive(_ application: UIApplication) {
22 | // 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.
23 | // 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.
24 | }
25 |
26 | func applicationDidEnterBackground(_ application: UIApplication) {
27 | // 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.
28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29 | }
30 |
31 | func applicationWillEnterForeground(_ application: UIApplication) {
32 | // 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.
33 | }
34 |
35 | func applicationDidBecomeActive(_ application: UIApplication) {
36 | // 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.
37 | }
38 |
39 | func applicationWillTerminate(_ application: UIApplication) {
40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Example/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "size" : "1024x1024",
46 | "scale" : "1x"
47 | }
48 | ],
49 | "info" : {
50 | "version" : 1,
51 | "author" : "xcode"
52 | }
53 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/background.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "stars.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/background.imageset/stars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonkykong/SideMenu/8bd4fd128923cf5494fa726839af8afe12908ad9/Example/Assets.xcassets/background.imageset/stars.png
--------------------------------------------------------------------------------
/Example/Assets.xcassets/saturn.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "saturn.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example/Assets.xcassets/saturn.imageset/saturn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jonkykong/SideMenu/8bd4fd128923cf5494fa726839af8afe12908ad9/Example/Assets.xcassets/saturn.imageset/saturn.png
--------------------------------------------------------------------------------
/Example/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | SideMenu
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | Launch Screen
29 | UIMainStoryboardFile
30 | Main
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UIStatusBarStyle
36 | UIStatusBarStyleLightContent
37 | UISupportedInterfaceOrientations
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationLandscapeLeft
41 | UIInterfaceOrientationLandscapeRight
42 | UIInterfaceOrientationPortraitUpsideDown
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example/Launch Screen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Example/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | //
4 | // Created by Jon Kent on 11/12/15.
5 | // Copyright © 2015 Jon Kent. All rights reserved.
6 | //
7 |
8 | import SideMenu
9 |
10 | class MainViewController: UIViewController {
11 | @IBOutlet private weak var blackOutStatusBar: UISwitch!
12 | @IBOutlet private weak var blurSegmentControl: UISegmentedControl!
13 | @IBOutlet private weak var menuAlphaSlider: UISlider!
14 | @IBOutlet private weak var menuScaleFactorSlider: UISlider!
15 | @IBOutlet private weak var presentingAlphaSlider: UISlider!
16 | @IBOutlet private weak var presentingScaleFactorSlider: UISlider!
17 | @IBOutlet private weak var presentationStyleSegmentedControl: UISegmentedControl!
18 | @IBOutlet private weak var screenWidthSlider: UISlider!
19 | @IBOutlet private weak var shadowOpacitySlider: UISlider!
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | setupSideMenu()
24 | updateUI(settings: SideMenuSettings())
25 | updateMenus()
26 | }
27 |
28 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
29 | guard let sideMenuNavigationController = segue.destination as? SideMenuNavigationController else { return }
30 | sideMenuNavigationController.settings = makeSettings()
31 | }
32 |
33 | private func setupSideMenu() {
34 | // Define the menus
35 | SideMenuManager.default.leftMenuNavigationController = storyboard?.instantiateViewController(withIdentifier: "LeftMenuNavigationController") as? SideMenuNavigationController
36 | SideMenuManager.default.rightMenuNavigationController = storyboard?.instantiateViewController(withIdentifier: "RightMenuNavigationController") as? SideMenuNavigationController
37 |
38 | // Enable gestures. The left and/or right menus must be set up above for these to work.
39 | // Note that these continue to work on the Navigation Controller independent of the View Controller it displays!
40 | SideMenuManager.default.addPanGestureToPresent(toView: navigationController!.navigationBar)
41 | SideMenuManager.default.addScreenEdgePanGesturesToPresent(toView: view)
42 | }
43 |
44 | private func updateUI(settings: SideMenuSettings) {
45 | let styles:[UIBlurEffect.Style] = [.dark, .light, .extraLight]
46 | if let menuBlurEffectStyle = settings.blurEffectStyle {
47 | blurSegmentControl.selectedSegmentIndex = (styles.firstIndex(of: menuBlurEffectStyle) ?? 0) + 1
48 | } else {
49 | blurSegmentControl.selectedSegmentIndex = 0
50 | }
51 |
52 | blackOutStatusBar.isOn = settings.statusBarEndAlpha > 0
53 | menuAlphaSlider.value = Float(settings.presentationStyle.menuStartAlpha)
54 | menuScaleFactorSlider.value = Float(settings.presentationStyle.menuScaleFactor)
55 | presentingAlphaSlider.value = Float(settings.presentationStyle.presentingEndAlpha)
56 | presentingScaleFactorSlider.value = Float(settings.presentationStyle.presentingScaleFactor)
57 | screenWidthSlider.value = Float(settings.menuWidth / min(view.frame.width, view.frame.height))
58 | shadowOpacitySlider.value = Float(settings.presentationStyle.onTopShadowOpacity)
59 | }
60 |
61 | @IBAction private func changeControl(_ control: UIControl) {
62 | if control == presentationStyleSegmentedControl {
63 | var settings = makeSettings()
64 | settings.presentationStyle = selectedPresentationStyle()
65 | updateUI(settings: settings)
66 | }
67 | updateMenus()
68 | }
69 |
70 | private func updateMenus() {
71 | let settings = makeSettings()
72 | SideMenuManager.default.leftMenuNavigationController?.settings = settings
73 | SideMenuManager.default.rightMenuNavigationController?.settings = settings
74 | }
75 |
76 | private func selectedPresentationStyle() -> SideMenuPresentationStyle {
77 | let modes: [SideMenuPresentationStyle] = [.menuSlideIn, .viewSlideOut, .viewSlideOutMenuIn, .menuDissolveIn]
78 | return modes[presentationStyleSegmentedControl.selectedSegmentIndex]
79 | }
80 |
81 | private func makeSettings() -> SideMenuSettings {
82 | let presentationStyle = selectedPresentationStyle()
83 | presentationStyle.backgroundColor = UIColor(patternImage: #imageLiteral(resourceName: "background"))
84 | presentationStyle.menuStartAlpha = CGFloat(menuAlphaSlider.value)
85 | presentationStyle.menuScaleFactor = CGFloat(menuScaleFactorSlider.value)
86 | presentationStyle.onTopShadowOpacity = shadowOpacitySlider.value
87 | presentationStyle.presentingEndAlpha = CGFloat(presentingAlphaSlider.value)
88 | presentationStyle.presentingScaleFactor = CGFloat(presentingScaleFactorSlider.value)
89 |
90 | var settings = SideMenuSettings()
91 | settings.presentationStyle = presentationStyle
92 | settings.menuWidth = min(view.frame.width, view.frame.height) * CGFloat(screenWidthSlider.value)
93 | let styles:[UIBlurEffect.Style?] = [nil, .dark, .light, .extraLight]
94 | settings.blurEffectStyle = styles[blurSegmentControl.selectedSegmentIndex]
95 | settings.statusBarEndAlpha = blackOutStatusBar.isOn ? 1 : 0
96 |
97 | return settings
98 | }
99 | }
100 |
101 | extension MainViewController: SideMenuNavigationControllerDelegate {
102 |
103 | func sideMenuWillAppear(menu: SideMenuNavigationController, animated: Bool) {
104 | print("SideMenu Appearing! (animated: \(animated))")
105 | }
106 |
107 | func sideMenuDidAppear(menu: SideMenuNavigationController, animated: Bool) {
108 | print("SideMenu Appeared! (animated: \(animated))")
109 | }
110 |
111 | func sideMenuWillDisappear(menu: SideMenuNavigationController, animated: Bool) {
112 | print("SideMenu Disappearing! (animated: \(animated))")
113 | }
114 |
115 | func sideMenuDidDisappear(menu: SideMenuNavigationController, animated: Bool) {
116 | print("SideMenu Disappeared! (animated: \(animated))")
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Example/PresentedViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentedViewController.swift
3 | //
4 | // Created by Jon Kent on 12/14/15.
5 | // Copyright © 2015 Jon Kent. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | class PresentedViewController: UIViewController {
11 |
12 | @IBAction private func close() {
13 | dismiss(animated: true, completion: nil)
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Example/SideMenuTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SideMenuTableViewController.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 4/5/16.
6 | // Copyright © 2016 CocoaPods. All rights reserved.
7 | //
8 |
9 | import SideMenu
10 |
11 | class SideMenuTableViewController: UITableViewController {
12 |
13 | override func viewWillAppear(_ animated: Bool) {
14 | super.viewWillAppear(animated)
15 |
16 | // refresh cell blur effect in case it changed
17 | tableView.reloadData()
18 |
19 | guard let menu = navigationController as? SideMenuNavigationController, menu.blurEffectStyle == nil else {
20 | return
21 | }
22 |
23 | // Set up a cool background image for demo purposes
24 | let imageView = UIImageView(image: #imageLiteral(resourceName: "saturn"))
25 | imageView.contentMode = .scaleAspectFit
26 | imageView.backgroundColor = UIColor.black.withAlphaComponent(0.2)
27 | tableView.backgroundView = imageView
28 | }
29 |
30 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
31 | let cell = super.tableView(tableView, cellForRowAt: indexPath) as! UITableViewVibrantCell
32 |
33 | if let menu = navigationController as? SideMenuNavigationController {
34 | cell.blurEffectStyle = menu.blurEffectStyle
35 | }
36 |
37 | return cell
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/ExampleTests/ExampleTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleTests.swift
3 | // ExampleTests
4 | //
5 | // Created by Jon Kent on 8/10/19.
6 | // Copyright © 2019 jonkykong. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ExampleTests: XCTestCase {
12 |
13 | private let styleTitles = ["Slide In", "Slide Out", "In + Out", "Dissolve"]
14 | private let swipeHere = "Swipe Here"
15 |
16 | private let app = XCUIApplication()
17 | private var mainViewController: XCUIElement {
18 | return app.navigationBars[swipeHere]
19 | }
20 |
21 | override func setUp() {
22 | // Put setup code here. This method is called before the invocation of each test method in the class.
23 |
24 | // In UI tests it is usually best to stop immediately when a failure occurs.
25 | continueAfterFailure = false
26 |
27 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
28 | XCUIApplication().launch()
29 |
30 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
31 | }
32 |
33 | override func tearDown() {
34 | // Put teardown code here. This method is called after the invocation of each test method in the class.
35 | }
36 |
37 | func testTapLeft() {
38 | let elementsQuery = app.scrollViews.otherElements
39 | for title in styleTitles {
40 | elementsQuery.buttons[title].tap()
41 | mainViewController.buttons["Left Menu"].tap()
42 | app.tables/*@START_MENU_TOKEN@*/.staticTexts["Push View Controller 1"]/*[[".cells.staticTexts[\"Push View Controller 1\"]",".staticTexts[\"Push View Controller 1\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap()
43 | app.navigationBars["You Can Still Swipe!"].buttons[swipeHere].tap()
44 | validate()
45 | }
46 | }
47 |
48 | func testTapRight() {
49 | let elementsQuery = app.scrollViews.otherElements
50 | for title in styleTitles {
51 | elementsQuery.buttons[title].tap()
52 | mainViewController.buttons["Right Menu"].tap()
53 | app.tables/*@START_MENU_TOKEN@*/.staticTexts["Present View Controller 1"]/*[[".cells.staticTexts[\"Present View Controller 1\"]",".staticTexts[\"Present View Controller 1\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap()
54 | app.buttons["Dismiss"].tap()
55 | mainViewController.tap()
56 | validate()
57 | }
58 | }
59 |
60 | func testSwiping() {
61 | mainViewController.swipeRight()
62 | mainViewController.swipeLeft()
63 | validate()
64 |
65 | mainViewController.swipeLeft()
66 | mainViewController.swipeRight()
67 | validate()
68 | }
69 |
70 | private func validate() {
71 | XCTAssertTrue(mainViewController.exists)
72 | }
73 |
74 | /* TODO - More tests:
75 | - Rotation
76 | - All menu settings
77 | */
78 | }
79 |
--------------------------------------------------------------------------------
/ExampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Jonathan Kent
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SideMenu",
8 | products: [
9 | .library(name: "SideMenu", targets: ["SideMenu"])
10 | ],
11 | dependencies: [],
12 | targets: [
13 | .target(name: "SideMenu", path: "Pod/Classes")
14 | ],
15 | swiftLanguageVersions: [.v4_2, .v5]
16 | )
17 |
18 |
--------------------------------------------------------------------------------
/Pod/Classes/Deprecations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Deprecations.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 7/3/19.
6 | //
7 |
8 | import UIKit
9 |
10 | // Deprecations; to be removed at a future date.
11 | extension SideMenuManager {
12 |
13 | @available(*, deprecated, renamed: "leftMenuNavigationController")
14 | open var menuLeftNavigationController: SideMenuNavigationController? {
15 | get { return nil }
16 | set {}
17 | }
18 |
19 | @available(*, deprecated, renamed: "rightMenuNavigationController")
20 | open var menuRightNavigationController: SideMenuNavigationController? {
21 | get { return nil }
22 | set {}
23 | }
24 |
25 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
26 | public var menuPresentMode: SideMenuPresentationStyle {
27 | get { return .viewSlideOut }
28 | set {}
29 | }
30 |
31 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
32 | public var menuPushStyle: SideMenuPushStyle {
33 | get { return .default }
34 | set {}
35 | }
36 |
37 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
38 | public var menuAllowPushOfSameClassTwice: Bool {
39 | get { return true }
40 | set {}
41 | }
42 |
43 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
44 | public var menuWidth: CGFloat {
45 | get { return 0 }
46 | set {}
47 | }
48 |
49 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
50 | public var menuAnimationPresentDuration: Double {
51 | get { return 0.35 }
52 | set {}
53 | }
54 |
55 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
56 | public var menuAnimationDismissDuration: Double {
57 | get { return 0.35 }
58 | set {}
59 | }
60 |
61 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
62 | public var menuAnimationCompleteGestureDuration: Double {
63 | get { return 0.35 }
64 | set {}
65 | }
66 |
67 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
68 | public var menuAnimationFadeStrength: CGFloat {
69 | get { return 0 }
70 | set {}
71 | }
72 |
73 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
74 | public var menuAnimationTransformScaleFactor: CGFloat {
75 | get { return 1 }
76 | set {}
77 | }
78 |
79 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
80 | public var menuAnimationBackgroundColor: UIColor? {
81 | get { return nil }
82 | set {}
83 | }
84 |
85 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
86 | public var menuShadowOpacity: Float {
87 | get { return 0.5 }
88 | set {}
89 | }
90 |
91 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
92 | public var menuShadowColor: UIColor {
93 | get { return .black }
94 | set {}
95 | }
96 |
97 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
98 | public var menuShadowRadius: CGFloat {
99 | get { return 5 }
100 | set {}
101 | }
102 |
103 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
104 | public var menuPresentingViewControllerUserInteractionEnabled: Bool {
105 | get { return false }
106 | set {}
107 | }
108 |
109 | @available(*, deprecated, message: "This property has been moved to the SideMenuPresentationStyle class.")
110 | public var menuParallaxStrength: Int {
111 | get { return 0 }
112 | set {}
113 | }
114 |
115 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
116 | public var menuFadeStatusBar: Bool {
117 | get { return true }
118 | set {}
119 | }
120 |
121 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
122 | public var menuAnimationOptions: UIView.AnimationOptions {
123 | get { return .curveEaseInOut }
124 | set {}
125 | }
126 |
127 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
128 | public var menuAnimationCompletionCurve: UIView.AnimationCurve {
129 | get { return .easeIn }
130 | set {}
131 | }
132 |
133 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
134 | public var menuAnimationUsingSpringWithDamping: CGFloat {
135 | get { return 1 }
136 | set {}
137 | }
138 |
139 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
140 | public var menuAnimationInitialSpringVelocity: CGFloat {
141 | get { return 1 }
142 | set {}
143 | }
144 |
145 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
146 | public var menuDismissOnPush: Bool {
147 | get { return true }
148 | set {}
149 | }
150 |
151 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
152 | public var menuAlwaysAnimate: Bool {
153 | get { return false }
154 | set {}
155 | }
156 |
157 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
158 | public var menuDismissWhenBackgrounded: Bool {
159 | get { return true }
160 | set {}
161 | }
162 |
163 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
164 | public var menuBlurEffectStyle: UIBlurEffect.Style? {
165 | get { return nil }
166 | set {}
167 | }
168 |
169 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
170 | public weak var menuLeftSwipeToDismissGesture: UIPanGestureRecognizer? {
171 | get { return nil }
172 | set {}
173 | }
174 |
175 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
176 | public weak var menuRightSwipeToDismissGesture: UIPanGestureRecognizer? {
177 | get { return nil }
178 | set {}
179 | }
180 |
181 | @available(*, deprecated, message: "This property has been moved to the SideMenuNavigationController class.")
182 | public var menuEnableSwipeGestures: Bool {
183 | get { return true }
184 | set {}
185 | }
186 |
187 | @available(*, deprecated, renamed: "enableSwipeToDismissGesture")
188 | public var enableSwipeGestures: Bool {
189 | get { return true }
190 | set {}
191 | }
192 |
193 | @available(*, deprecated, renamed: "SideMenuPresentationStyle")
194 | public typealias MenuPresentMode = SideMenuPresentationStyle
195 |
196 | @available(*, deprecated, renamed: "addScreenEdgePanGesturesToPresent")
197 | @discardableResult public func menuAddScreenEdgePanGesturesToPresent(toView view: UIView, forMenu sides: [PresentDirection] = [.left, .right]) -> [UIScreenEdgePanGestureRecognizer] {
198 | return []
199 | }
200 |
201 | @available(*, deprecated, renamed: "addPanGestureToPresent")
202 | @discardableResult public func menuAddPanGestureToPresent(toView view: UIView) -> UIPanGestureRecognizer {
203 | return UIPanGestureRecognizer()
204 | }
205 | }
206 |
207 | extension SideMenuPresentationStyle {
208 | @available(*, deprecated, renamed: "viewSlideOutMenuIn")
209 | public static var viewSlideInOut: SideMenuPresentationStyle { return viewSlideOutMenuIn }
210 | }
211 |
212 | @available(*, deprecated, renamed: "SideMenuNavigationController")
213 | public typealias UISideMenuNavigationController = SideMenuNavigationController
214 |
215 | @available(*, deprecated, renamed: "SideMenuNavigationControllerDelegate")
216 | public typealias UISideMenuNavigationControllerDelegate = SideMenuNavigationControllerDelegate
217 |
--------------------------------------------------------------------------------
/Pod/Classes/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // Pods-Example
4 | //
5 | // Created by Jon Kent on 7/1/19.
6 | //
7 |
8 | import UIKit
9 |
10 | extension NSObject: InitializableClass {}
11 |
12 | internal extension UIView {
13 |
14 | @discardableResult func untransformed(_ block: () -> CGFloat) -> CGFloat {
15 | let t = transform
16 | transform = .identity
17 | let value = block()
18 | transform = t
19 | return value
20 | }
21 |
22 | func bringToFront() {
23 | superview?.bringSubviewToFront(self)
24 | }
25 |
26 | func untransform(_ block: () -> Void) {
27 | untransformed { () -> CGFloat in
28 | block()
29 | return 0
30 | }
31 | }
32 |
33 | static func animationsEnabled(_ enabled: Bool = true, _ block: () -> Void) {
34 | let a = areAnimationsEnabled
35 | setAnimationsEnabled(enabled)
36 | block()
37 | setAnimationsEnabled(a)
38 | }
39 | }
40 |
41 | internal extension UIViewController {
42 |
43 | // View controller actively displayed in that layer. It may not be visible if it's presenting another view controller.
44 | var activeViewController: UIViewController {
45 | switch self {
46 | case let navigationController as UINavigationController:
47 | return navigationController.topViewController?.activeViewController ?? self
48 | case let tabBarController as UITabBarController:
49 | return tabBarController.selectedViewController?.activeViewController ?? self
50 | case let splitViewController as UISplitViewController:
51 | return splitViewController.viewControllers.last?.activeViewController ?? self
52 | default:
53 | return self
54 | }
55 | }
56 |
57 | // View controller being displayed on screen to the user.
58 | var topMostViewController: UIViewController {
59 | let activeViewController = self.activeViewController
60 | return activeViewController.presentedViewController?.topMostViewController ?? activeViewController
61 | }
62 |
63 | var containerViewController: UIViewController {
64 | return navigationController?.containerViewController ??
65 | tabBarController?.containerViewController ??
66 | splitViewController?.containerViewController ??
67 | self
68 | }
69 |
70 | @objc var isHidden: Bool {
71 | return presentingViewController == nil
72 | }
73 | }
74 |
75 | internal extension UIGestureRecognizer {
76 |
77 | convenience init(addTo view: UIView, target: Any, action: Selector) {
78 | self.init(target: target, action: action)
79 | view.addGestureRecognizer(self)
80 | }
81 |
82 | convenience init?(addTo view: UIView?, target: Any, action: Selector) {
83 | guard let view = view else { return nil }
84 | self.init(addTo: view, target: target, action: action)
85 | }
86 |
87 | func remove() {
88 | view?.removeGestureRecognizer(self)
89 | }
90 | }
91 |
92 | internal extension UIPanGestureRecognizer {
93 |
94 | var canSwitch: Bool {
95 | return !(self is UIScreenEdgePanGestureRecognizer)
96 | }
97 |
98 | var xTranslation: CGFloat {
99 | return view?.untransformed {
100 | return self.translation(in: view).x
101 | } ?? 0
102 | }
103 |
104 | var xVelocity: CGFloat {
105 | return view?.untransformed {
106 | return self.velocity(in: view).x
107 | } ?? 0
108 | }
109 | }
110 |
111 | internal extension UIApplication {
112 |
113 | var keyWindow: UIWindow? {
114 | return UIApplication.shared.windows.filter { $0.isKeyWindow }.first
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Pod/Classes/Initializable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Initializable.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 7/2/19.
6 | //
7 |
8 | import Foundation
9 |
10 | internal protocol InitializableClass: class {
11 | init()
12 | }
13 |
14 | extension InitializableClass {
15 | init(_ block: (Self) -> Void) {
16 | self.init()
17 | block(self)
18 | }
19 |
20 | @discardableResult func with(_ block: (Self) -> Void) -> Self {
21 | block(self)
22 | return self
23 | }
24 | }
25 |
26 | public protocol InitializableStruct {
27 | init()
28 | }
29 |
30 | public extension InitializableStruct {
31 | init(_ block: (inout Self) -> Void) {
32 | self.init()
33 | block(&self)
34 | }
35 |
36 | @discardableResult mutating func with(_ block: (inout Self) -> Void) -> Self {
37 | block(&self)
38 | return self
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Pod/Classes/Print.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Print.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 12/5/18.
6 | //
7 |
8 | import Foundation
9 |
10 | internal enum Print: String { case
11 | cannotPush = "Attempt to push a View Controller from %@ where its navigationController == nil. It must be embedded in a UINavigationController for this to work.",
12 | emptyMenu = "The menu doesn't have a view controller to show! SideMenuNavigationController needs a view controller to display just like a UINavigationController.",
13 | menuAlreadyAssigned = "%@ was already assigned to the %@ of %@. When using multiple SideMenuManagers you may want to use new instances of SideMenuNavigationController instead of existing instances to avoid crashes if the menu is presented more than once.",
14 | menuInUse = "%@ cannot be modified while it's presented.",
15 | panGestureAdded = "%@ was called before %@ or %@ was set. Gestures will not work without a menu.",
16 | property = "A menu's %@ property can only be changed when it is hidden.",
17 | screenGestureAdded = "%@ was called before %@ was set. The gesture will not work without a menu. Use %@ to add gestures for only one menu.",
18 | transitioningDelegate = "SideMenu requires use of the transitioningDelegate. It cannot be modified."
19 |
20 | enum PropertyName: String { case
21 | leftSide
22 | }
23 |
24 | static func warning(_ print: Print, arguments: CVarArg..., required: Bool = false) {
25 | warning(String(format: print.rawValue, arguments: arguments), required: required)
26 | }
27 |
28 | static func warning(_ print: Print, arguments: PropertyName..., required: Bool = false) {
29 | warning(String(format: print.rawValue, arguments: arguments.map { $0.rawValue }), required: required)
30 | }
31 |
32 | static func warning(_ print: Print, required: Bool = false) {
33 | warning(print.rawValue, required: required)
34 | }
35 | }
36 |
37 | private extension Print {
38 |
39 | static func warning(_ message: String, required: Bool = false) {
40 | let message = "SideMenu Warning: \(message)"
41 |
42 | if required {
43 | print(message)
44 | return
45 | }
46 | #if !STFU_SIDEMENU
47 | print(message)
48 | #endif
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Pod/Classes/Protected.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Protected.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 2/9/19.
6 | //
7 |
8 | import Foundation
9 |
10 | internal final class Protected {
11 |
12 | typealias ConditionBlock = (_ oldValue: T, T) -> T
13 |
14 | private var _value: T
15 | private var condition: ConditionBlock
16 |
17 | public var value: T {
18 | get { return _value }
19 | set { _value = condition(_value, newValue) }
20 | }
21 |
22 | init(_ value: T, when condition: @escaping ConditionBlock) {
23 | self._value = value
24 | self.condition = condition
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Pod/Classes/SideMenuAnimationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SideMenuAnimationController.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 10/24/18.
6 | //
7 |
8 | import UIKit
9 |
10 | internal protocol AnimationModel {
11 | /// The animation options when a menu is displayed. Ignored when displayed with a gesture.
12 | var animationOptions: UIView.AnimationOptions { get }
13 | /// Duration of the remaining animation when the menu is partially dismissed with gestures. Default is 0.35 seconds.
14 | var completeGestureDuration: Double { get }
15 | /// Duration of the animation when the menu is dismissed without gestures. Default is 0.35 seconds.
16 | var dismissDuration: Double { get }
17 | /// The animation initial spring velocity when a menu is displayed. Ignored when displayed with a gesture.
18 | var initialSpringVelocity: CGFloat { get }
19 | /// Duration of the animation when the menu is presented without gestures. Default is 0.35 seconds.
20 | var presentDuration: Double { get }
21 | /// The animation spring damping when a menu is displayed. Ignored when displayed with a gesture.
22 | var usingSpringWithDamping: CGFloat { get }
23 | }
24 |
25 | internal protocol SideMenuAnimationControllerDelegate: class {
26 | func sideMenuAnimationController(_ animationController: SideMenuAnimationController, didDismiss viewController: UIViewController)
27 | func sideMenuAnimationController(_ animationController: SideMenuAnimationController, didPresent viewController: UIViewController)
28 | }
29 |
30 | internal final class SideMenuAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
31 |
32 | typealias Model = AnimationModel & PresentationModel
33 |
34 | private var config: Model
35 | private weak var containerView: UIView?
36 | private let leftSide: Bool
37 | private weak var originalSuperview: UIView?
38 | private var presentationController: SideMenuPresentationController?
39 | private unowned var presentedViewController: UIViewController?
40 | private unowned var presentingViewController: UIViewController?
41 | weak var delegate: SideMenuAnimationControllerDelegate?
42 |
43 | init(config: Model, leftSide: Bool, delegate: SideMenuAnimationControllerDelegate? = nil) {
44 | self.config = config
45 | self.leftSide = leftSide
46 | self.delegate = delegate
47 | }
48 |
49 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
50 | guard
51 | let presentedViewController = transitionContext.presentedViewController,
52 | let presentingViewController = transitionContext.presentingViewController
53 | else { return }
54 |
55 | if transitionContext.isPresenting {
56 | self.containerView = transitionContext.containerView
57 | self.presentedViewController = presentedViewController
58 | self.presentingViewController = presentingViewController
59 | self.presentationController = SideMenuPresentationController(
60 | config: config,
61 | leftSide: leftSide,
62 | presentedViewController: presentedViewController,
63 | presentingViewController: presentingViewController,
64 | containerView: transitionContext.containerView
65 | )
66 | }
67 |
68 | transition(using: transitionContext)
69 | }
70 |
71 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
72 | guard let transitionContext = transitionContext else { return 0 }
73 | return duration(presenting: transitionContext.isPresenting, interactive: transitionContext.isInteractive)
74 | }
75 |
76 | func animationEnded(_ transitionCompleted: Bool) {
77 | guard let presentedViewController = presentedViewController else { return }
78 | if presentedViewController.isHidden {
79 | delegate?.sideMenuAnimationController(self, didDismiss: presentedViewController)
80 | } else {
81 | delegate?.sideMenuAnimationController(self, didPresent: presentedViewController)
82 | }
83 | }
84 |
85 | func transition(presenting: Bool, animated: Bool = true, interactive: Bool = false, alongsideTransition: (() -> Void)? = nil, complete: Bool = true, completion: ((Bool) -> Void)? = nil) {
86 | prepare(presenting: presenting)
87 | transitionWillBegin(presenting: presenting)
88 | transition(
89 | presenting: presenting,
90 | animated: animated,
91 | interactive: interactive,
92 | animations: { [weak self] in
93 | guard let self = self else { return }
94 | self.transition(presenting: presenting)
95 | alongsideTransition?()
96 | }, completion: { [weak self] _ in
97 | guard let self = self else { return }
98 | if complete {
99 | self.transitionDidEnd(presenting: presenting, completed: true)
100 | self.finish(presenting: presenting, completed: true)
101 | }
102 | completion?(true)
103 | })
104 | }
105 |
106 | func layout() {
107 | presentationController?.containerViewWillLayoutSubviews()
108 | }
109 | }
110 |
111 | private extension SideMenuAnimationController {
112 |
113 | func duration(presenting: Bool, interactive: Bool) -> Double {
114 | if interactive { return config.completeGestureDuration }
115 | return presenting ? config.presentDuration : config.dismissDuration
116 | }
117 |
118 | func prepare(presenting: Bool) {
119 | guard
120 | presenting,
121 | let presentingViewController = presentingViewController,
122 | let presentedViewController = presentedViewController
123 | else { return }
124 |
125 | originalSuperview = presentingViewController.view.superview
126 | containerView?.addSubview(presentingViewController.view)
127 | containerView?.addSubview(presentedViewController.view)
128 | }
129 |
130 | func transitionWillBegin(presenting: Bool) {
131 | // prevent any other menu gestures from firing
132 | containerView?.isUserInteractionEnabled = false
133 | if presenting {
134 | presentationController?.presentationTransitionWillBegin()
135 | } else {
136 | presentationController?.dismissalTransitionWillBegin()
137 | }
138 | }
139 |
140 | func transition(presenting: Bool) {
141 | if presenting {
142 | presentationController?.presentationTransition()
143 | } else {
144 | presentationController?.dismissalTransition()
145 | }
146 | }
147 |
148 | func transitionDidEnd(presenting: Bool, completed: Bool) {
149 | if presenting {
150 | presentationController?.presentationTransitionDidEnd(completed)
151 | } else {
152 | presentationController?.dismissalTransitionDidEnd(completed)
153 | }
154 | containerView?.isUserInteractionEnabled = true
155 | }
156 |
157 | func finish(presenting: Bool, completed: Bool) {
158 | guard
159 | presenting != completed,
160 | let presentingViewController = self.presentingViewController
161 | else { return }
162 | presentedViewController?.view.removeFromSuperview()
163 | originalSuperview?.addSubview(presentingViewController.view)
164 | }
165 |
166 | func transition(using transitionContext: UIViewControllerContextTransitioning) {
167 | prepare(presenting: transitionContext.isPresenting)
168 | transitionWillBegin(presenting: transitionContext.isPresenting)
169 | transition(
170 | presenting: transitionContext.isPresenting,
171 | animated: transitionContext.isAnimated,
172 | interactive: transitionContext.isInteractive,
173 | animations: { [weak self] in
174 | guard let self = self else { return }
175 | self.transition(presenting: transitionContext.isPresenting)
176 | }, completion: { [weak self] _ in
177 | guard let self = self else { return }
178 | let completed = !transitionContext.transitionWasCancelled
179 | self.transitionDidEnd(presenting: transitionContext.isPresenting, completed: completed)
180 | self.finish(presenting: transitionContext.isPresenting, completed: completed)
181 |
182 | // Called last. This causes the transition container to be removed and animationEnded() to be called.
183 | transitionContext.completeTransition(completed)
184 | })
185 | }
186 |
187 | func transition(presenting: Bool, animated: Bool = true, interactive: Bool = false, animations: @escaping (() -> Void) = {}, completion: @escaping ((Bool) -> Void) = { _ in }) {
188 | if !animated {
189 | animations()
190 | completion(true)
191 | return
192 | }
193 |
194 | let duration = self.duration(presenting: presenting, interactive: interactive)
195 | if interactive {
196 | // IMPORTANT: The non-interactive animation block will not complete if adapted for interactive. The below animation block must be used!
197 | UIView.animate(
198 | withDuration: duration,
199 | delay: duration, // HACK: If zero, the animation briefly flashes in iOS 11.
200 | options: .curveLinear,
201 | animations: animations,
202 | completion: completion
203 | )
204 | return
205 | }
206 |
207 | UIView.animate(
208 | withDuration: duration,
209 | delay: 0,
210 | usingSpringWithDamping: config.usingSpringWithDamping,
211 | initialSpringVelocity: config.initialSpringVelocity,
212 | options: config.animationOptions,
213 | animations: animations,
214 | completion: completion
215 | )
216 | }
217 | }
218 |
219 | private extension UIViewControllerContextTransitioning {
220 |
221 | var isPresenting: Bool {
222 | return viewController(forKey: .from)?.presentedViewController === viewController(forKey: .to)
223 | }
224 |
225 | var presentingViewController: UIViewController? {
226 | return viewController(forKey: isPresenting ? .from : .to)
227 | }
228 |
229 | var presentedViewController: UIViewController? {
230 | return viewController(forKey: isPresenting ? .to : .from)
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/Pod/Classes/SideMenuInteractionController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SideMenuInteractiveTransitionController.swift
3 | // SideMenu
4 | //
5 | // Created by Jon Kent on 12/28/18.
6 | //
7 |
8 | import UIKit
9 |
10 | internal final class SideMenuInteractionController: UIPercentDrivenInteractiveTransition {
11 |
12 | enum State { case
13 | update(progress: CGFloat),
14 | finish,
15 | cancel
16 | }
17 |
18 | private(set) var isCancelled: Bool = false
19 | private(set) var isFinished: Bool = false
20 |
21 | init(cancelWhenBackgrounded: Bool = true, completionCurve: UIView.AnimationCurve = .easeIn) {
22 | super.init()
23 | self.completionCurve = completionCurve
24 |
25 | guard cancelWhenBackgrounded else { return }
26 | NotificationCenter.default.addObserver(self, selector: #selector(handleNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
27 | }
28 |
29 | override func cancel() {
30 | isCancelled = true
31 | super.cancel()
32 | }
33 |
34 | override func finish() {
35 | isFinished = true
36 | super.finish()
37 | }
38 |
39 | override func update(_ percentComplete: CGFloat) {
40 | guard !isCancelled && !isFinished else { return }
41 | super.update(percentComplete)
42 | }
43 |
44 | func handle(state: State) {
45 | switch state {
46 | case .update(let progress):
47 | update(progress)
48 | case .finish:
49 | finish()
50 | case .cancel:
51 | cancel()
52 | }
53 | }
54 | }
55 |
56 | private extension SideMenuInteractionController {
57 |
58 | @objc func handleNotification(notification: NSNotification) {
59 | switch notification.name {
60 | case UIApplication.didEnterBackgroundNotification:
61 | cancel()
62 | default: break
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Pod/Classes/SideMenuManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SideMenuManager.swift
3 | //
4 | // Created by Jon Kent on 12/6/15.
5 | // Copyright © 2015 Jon Kent. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | @objcMembers
11 | public class SideMenuManager: NSObject {
12 |
13 | final private class SideMenuPanGestureRecognizer: UIPanGestureRecognizer {}
14 | final private class SideMenuScreenEdgeGestureRecognizer: UIScreenEdgePanGestureRecognizer {}
15 |
16 | @objc public enum PresentDirection: Int { case
17 | left = 1,
18 | right = 0
19 |
20 | init(leftSide: Bool) {
21 | self.init(rawValue: leftSide ? 1 : 0)!
22 | }
23 |
24 | var edge: UIRectEdge {
25 | switch self {
26 | case .left: return .left
27 | case .right: return .right
28 | }
29 | }
30 |
31 | var name: String {
32 | switch self {
33 | case .left: return "leftMenuNavigationController"
34 | case .right: return "rightMenuNavigationController"
35 | }
36 | }
37 | }
38 |
39 | private var _leftMenu: Protected