├── .github ├── ISSUE_TEMPLATE.md └── contributing.md ├── .gitignore ├── GuillotineMenu.podspec ├── GuillotineMenu ├── GuillotineMenuTransitionAnimation.swift └── ic_menu@3x.png ├── GuillotineMenuExample ├── .gitignore ├── GuillotineMenuExample.xcodeproj │ └── project.pbxproj └── GuillotineMenuExample │ ├── AppDelegate.swift │ ├── Assets │ ├── content_1@3x.png │ ├── content_2@3x.png │ ├── ic_activity@3x.png │ ├── ic_feed@3x.png │ ├── ic_menuRotated@3x.png │ ├── ic_profile@3x.png │ └── ic_settings@3x.png │ ├── Base.lproj │ └── Main.storyboard │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── MenuViewController.swift │ └── ViewController.swift ├── LICENSE ├── README.md ├── badge_dark.png └── example.gif /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Report 2 | 3 | > The more information you provide, the faster we can help you. 4 | 5 | ⚠️ Select what you want - **a feature request** or **report a bug**. Please remove the section you aren't interested in. 6 | 7 | ## A feature request 8 | 9 | ### What do you want to add? 10 | 11 | > Please describe what you want to add to the component. 12 | 13 | ### How should it look like? 14 | 15 | > Please add images. 16 | 17 | ## Report a bug 18 | 19 | ### What did you do? 20 | 21 | > Please replace this with what you did. 22 | 23 | ### What did you expect to happen? 24 | 25 | > Please replace this with what you expected to happen. 26 | 27 | ### What happened instead? 28 | 29 | > Please replace this with what happened instead. 30 | 31 | ### Your Environment 32 | 33 | - [ ] Version of the component: _insert here_ 34 | - [ ] Swift version: _insert here_ 35 | - [ ] iOS version: _insert here_ 36 | - [ ] Device: _insert here_ 37 | - [ ] Xcode version: _insert here_ 38 | - [ ] If you use Cocoapods: _run `pod env | pbcopy` and insert here_ 39 | - [ ] If you use Carthage: _run `carthage version | pbcopy` and insert here_ 40 | 41 | ### Project that demonstrates the bug 42 | 43 | > Please add a link to a project we can download that reproduces the bug. 44 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to GuillotineMenu 2 | 3 | #### **Did you find a bug?** 4 | 5 | * **Ensure the bug was not already reported** by searching under [Issues](https://github.com/Yalantis/GuillotineMenu/issues). 6 | 7 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Yalantis/GuillotineMenu/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **example project** demonstrating the expected behavior that is not occurring. 8 | 9 | * Fill appropriate section in issue template and remove the section you aren't interested in. 10 | 11 | #### **Did you write a patch that fixes a bug?** 12 | 13 | * Open a new GitHub pull request with the patch. 14 | 15 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 16 | 17 | * Ensure the PR doesn't extend the number of existing issues. 18 | 19 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 20 | 21 | * Changes that are **cosmetic** in nature and **do not add anything substantial** to the stability or functionality of GuillotineMenu will generally **not be accepted**. 22 | 23 | #### **Did you write patch that extends functionality?** 24 | 25 | * Ensure the functionality you trying to add needed not only for your case. 26 | 27 | #### Each issue will be labeled by it's `type`, `priority` and `status`. 28 | 29 | **Issue types:** 30 | * Bug 31 | * Enhancement 32 | 33 | **These are the available priority labels:** 34 | * Critical 35 | * High 36 | * Medium 37 | * Low 38 | 39 | **Status label will be assigned to your issue to keep the issue tracker easy to follow:** 40 | * Queued (will be reviewed soon) 41 | * Reviewed (assignee has read it) 42 | * Pending (will work on it soon) 43 | * Work in progress (is working on it now) 44 | * On hold 45 | * Invalid (if bug it's not reproducible) 46 | * Need feedback (signal to get people to read and comment or provide help) 47 | 48 | #### **Coding Style** 49 | 50 | * Most importantly, match the existing code style as much as possible. 51 | 52 | #### **Do you have a question?** 53 | 54 | For any usage questions that are not specific to the project itself, please ask on [Stack Overflow](https://stackoverflow.com/). By doing so, you'll be more likely to quickly solve your problem, and you'll allow anyone else with the same question to find the answer. This also allows maintainers to focus on improving the project for others. 55 | 56 | ## Thank you! 57 | 58 | #### [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToMakeSoup/master/PullToMakeSoupDemo/Resouces/badge_dark.png)](https://Yalantis.com/?utm_source=github) 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | *.xcodeproj/** 21 | !*.xcodeproj/*.pbxproj 22 | !*.xcodeproj/xcshareddata 23 | 24 | *.xcworkspace/** 25 | !*.xcworkspace/*.xcworkspacedata 26 | 27 | pkg/* 28 | 29 | Pods 30 | Podfile.lock 31 | -------------------------------------------------------------------------------- /GuillotineMenu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "GuillotineMenu" 3 | spec.version = "4.1" 4 | 5 | spec.homepage = "http://yalantis.com/blog/how-we-created-guillotine-menu-animation/" 6 | spec.summary = "Custom menu transition from Navigation Bar" 7 | 8 | spec.author = "Yalantis" 9 | spec.license = { :type => "MIT", :file => "LICENSE" } 10 | spec.social_media_url = "https://twitter.com/yalantis" 11 | 12 | spec.swift_version = '5.0' 13 | spec.platform = :ios, '8.0' 14 | spec.ios.deployment_target = '8.0' 15 | 16 | spec.source = { :git => "https://github.com/Yalantis/GuillotineMenu.git", :tag => spec.version } 17 | 18 | spec.requires_arc = true 19 | 20 | spec.source_files = 'GuillotineMenu/*.swift' 21 | spec.requires_arc = true 22 | end 23 | -------------------------------------------------------------------------------- /GuillotineMenu/GuillotineMenuTransitionAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GuillotineTransitionAnimation.swift 3 | // GuillotineMenu 4 | // 5 | // Created by Maksym Lazebnyi on 3/24/15. 6 | // Copyright (c) 2015 Yalantis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol GuillotineMenu { 12 | 13 | var dismissButton: UIButton? { get } 14 | var titleLabel: UILabel? { get } 15 | } 16 | 17 | public protocol GuillotineAnimationDelegate: class { 18 | 19 | func animatorDidFinishPresentation(_ animator: GuillotineTransitionAnimation) 20 | func animatorDidFinishDismissal(_ animator: GuillotineTransitionAnimation) 21 | func animatorWillStartPresentation(_ animator: GuillotineTransitionAnimation) 22 | func animatorWillStartDismissal(_ animator: GuillotineTransitionAnimation) 23 | } 24 | 25 | extension GuillotineAnimationDelegate { 26 | 27 | func animatorDidFinishPresentation(_ animator: GuillotineTransitionAnimation) {} 28 | func animatorDidFinishDismissal(_ animator: GuillotineTransitionAnimation) {} 29 | func animatorWillStartPresentation(_ animator: GuillotineTransitionAnimation) {} 30 | func animatorWillStartDismissal(_ animator: GuillotineTransitionAnimation) {} 31 | } 32 | 33 | open class GuillotineTransitionAnimation: NSObject { 34 | 35 | public enum Mode { 36 | case presentation, dismissal 37 | } 38 | 39 | //MARK: - Public properties 40 | open weak var animationDelegate: GuillotineAnimationDelegate? 41 | open var mode: Mode = .presentation 42 | open var supportView: UIView? 43 | open var presentButton: UIView? 44 | open var animationDuration = 0.6 45 | 46 | //MARK: - Private properties 47 | fileprivate var chromeView: UIView! 48 | fileprivate var containerMenuButton: UIButton? { 49 | didSet { 50 | presentButton?.addObserver( 51 | self, 52 | forKeyPath: "frame", 53 | options: .new, 54 | context: myContext 55 | ) 56 | } 57 | } 58 | 59 | fileprivate var displayLink: CADisplayLink! 60 | fileprivate var vectorDY: CGFloat = 1500 61 | fileprivate var fromYPresentationLandscapeAdjustment: CGFloat = 1.0 62 | fileprivate var fromYDismissalLandscapeAdjustment: CGFloat = 1.0 63 | fileprivate var toYDismissalLandscapeAdjustment: CGFloat = 1.0 64 | fileprivate var fromYPresentationAdjustment: CGFloat = 1.0 65 | fileprivate var fromYDismissalAdjustment: CGFloat = 1.0 66 | fileprivate var toXPresentationLandscapeAdjustment: CGFloat = 1.0 67 | fileprivate let initialMenuRotationAngle: CGFloat = -90 68 | fileprivate let menuElasticity: CGFloat = 0.6 69 | fileprivate let vectorDYCoefficient: Double = 2 / Double.pi 70 | fileprivate let menuDensity: CGFloat = 1.5 71 | fileprivate var topOffset: CGFloat = 0 72 | fileprivate var anchorPoint: CGPoint! 73 | fileprivate var menu: UIViewController! 74 | fileprivate var animationContext: UIViewControllerContextTransitioning! 75 | fileprivate var animator: UIDynamicAnimator! 76 | fileprivate let myContext: UnsafeMutableRawPointer? = nil 77 | 78 | //MARK: - Deinitialization 79 | deinit { 80 | displayLink.invalidate() 81 | presentButton?.removeObserver(self, forKeyPath: "frame") 82 | } 83 | 84 | //MARK: - Initialization 85 | override public init() { 86 | super.init() 87 | 88 | setupDisplayLink() 89 | setupSystemVersionAdjustment() 90 | } 91 | 92 | //MARK: - Private methods 93 | 94 | fileprivate func showHostTitleLabel(_ show: Bool, animated: Bool) { 95 | guard let guillotineMenu = menu as? GuillotineMenu else { return } 96 | guard let titleLabel = guillotineMenu.titleLabel else { return } 97 | if let supportView = supportView { 98 | titleLabel.center = CGPoint( 99 | x: supportView.frame.height / 2, 100 | y: supportView.frame.width / 2 101 | ) 102 | } 103 | titleLabel.transform = CGAffineTransform(rotationAngle: degreesToRadians(90)) 104 | menu.view.addSubview(titleLabel) 105 | 106 | switch mode { 107 | case .presentation: 108 | titleLabel.alpha = 1 109 | case .dismissal: 110 | titleLabel.alpha = 0 111 | } 112 | 113 | let showTitle = { 114 | titleLabel.alpha = show ? 1 : 0 115 | } 116 | 117 | if animated { 118 | UIView.animate(withDuration: animationDuration, animations: showTitle) 119 | } else { 120 | showTitle() 121 | } 122 | } 123 | 124 | fileprivate func updateChromeView() { 125 | chromeView = { 126 | let size = CGRect( 127 | x: 0, 128 | y: menu.view.frame.height, 129 | width: menu.view.frame.width, 130 | height: menu.view.frame.height 131 | ) 132 | let view = UIView(frame: size) 133 | view.backgroundColor = menu.view.backgroundColor 134 | return view 135 | }() 136 | } 137 | 138 | fileprivate func setupDisplayLink() { 139 | displayLink = { 140 | let displayLink = CADisplayLink( 141 | target: self, 142 | selector: #selector(updateContainerMenuButton) 143 | ) 144 | displayLink.add(to: .current, forMode: .common) 145 | displayLink.isPaused = true 146 | return displayLink 147 | }() 148 | } 149 | 150 | fileprivate func setupSystemVersionAdjustment() { 151 | let device = UIDevice.current 152 | let iosVersion = Double(device.systemVersion) ?? 0 153 | let iOS9 = iosVersion >= 9 154 | 155 | if iOS9 { 156 | fromYPresentationLandscapeAdjustment = 1.5 157 | fromYDismissalLandscapeAdjustment = 1.0 158 | fromYPresentationAdjustment = -1.0 159 | fromYDismissalAdjustment = -1.0 160 | toXPresentationLandscapeAdjustment = 1.0 161 | toYDismissalLandscapeAdjustment = -1.0 162 | } else { 163 | fromYPresentationLandscapeAdjustment = 0.5 164 | fromYDismissalLandscapeAdjustment = 0.0 165 | fromYPresentationAdjustment = -1.5 166 | fromYDismissalAdjustment = 1.0 167 | toXPresentationLandscapeAdjustment = -1.0 168 | toYDismissalLandscapeAdjustment = 1.5 169 | } 170 | } 171 | 172 | @objc fileprivate func updateContainerMenuButton() { 173 | let rotationTransform = menu.view.layer.presentation()!.transform 174 | let angle: CGFloat 175 | if rotationTransform.m11 < 0.0 { 176 | angle = 180.0 - radiansToDegrees(asin(rotationTransform.m12)) 177 | } else { 178 | angle = radiansToDegrees(asin(rotationTransform.m12)) 179 | } 180 | let degrees = 90 - abs(angle) 181 | containerMenuButton?.layer.transform = CATransform3DRotate(CATransform3DIdentity, degreesToRadians(degrees), 0, 0, 1) 182 | } 183 | 184 | func setupContainerMenuButtonFrameAndTopOffset() { 185 | if let supportView = supportView, let presentButton = presentButton { 186 | topOffset = supportView.frame.origin.y + supportView.bounds.height 187 | let presentButtonFrame = presentButton.convert(presentButton.bounds, to: supportView) 188 | let senderRect = supportView.convert(presentButtonFrame , to: nil) 189 | containerMenuButton?.frame = senderRect 190 | } 191 | } 192 | 193 | //MARK: - Observer 194 | override open func observeValue( 195 | forKeyPath keyPath: String?, 196 | of object: Any?, 197 | change: [NSKeyValueChangeKey : Any]?, 198 | context: UnsafeMutableRawPointer?) 199 | { 200 | if context == myContext { 201 | setupContainerMenuButtonFrameAndTopOffset() 202 | } else { 203 | super.observeValue( 204 | forKeyPath: keyPath, 205 | of: object, 206 | change: change, 207 | context: context 208 | ) 209 | } 210 | } 211 | } 212 | 213 | // MARK: - Animation 214 | fileprivate extension GuillotineTransitionAnimation { 215 | 216 | func animatePresentation(using context: UIViewControllerContextTransitioning) { 217 | menu = context.viewController(forKey: UITransitionContextViewControllerKey.to)! 218 | context.containerView.addSubview(menu.view) 219 | 220 | if UIDevice.current.orientation.isLandscape || UIApplication.shared.statusBarOrientation == .landscapeLeft || UIApplication.shared.statusBarOrientation == .landscapeRight { 221 | updateChromeView() 222 | menu.view.addSubview(chromeView) 223 | } 224 | 225 | if menu is GuillotineMenu { 226 | if supportView != nil && presentButton != nil { 227 | let guillotineMenu = menu as! GuillotineMenu 228 | containerMenuButton = guillotineMenu.dismissButton 229 | setupContainerMenuButtonFrameAndTopOffset() 230 | context.containerView.addSubview(containerMenuButton!) 231 | } 232 | } 233 | 234 | let fromVC = context.viewController(forKey: UITransitionContextViewControllerKey.from) 235 | fromVC?.beginAppearanceTransition(false, animated: true) 236 | 237 | animationDelegate?.animatorWillStartPresentation(self) 238 | 239 | animateMenu(menu.view, context: context) 240 | } 241 | 242 | func animateDismissal(using context: UIViewControllerContextTransitioning) { 243 | menu = context.viewController(forKey: UITransitionContextViewControllerKey.from)! 244 | if menu.navigationController != nil { 245 | let toVC = context.viewController(forKey: UITransitionContextViewControllerKey.to)! 246 | context.containerView.addSubview(toVC.view) 247 | context.containerView.sendSubviewToBack(toVC.view) 248 | } 249 | 250 | 251 | if UIDevice.current.orientation.isLandscape || UIApplication.shared.statusBarOrientation == .landscapeLeft || UIApplication.shared.statusBarOrientation == .landscapeRight { 252 | updateChromeView() 253 | menu.view.addSubview(chromeView) 254 | } 255 | 256 | let toVC = context.viewController(forKey: UITransitionContextViewControllerKey.to) 257 | toVC?.beginAppearanceTransition(true, animated: true) 258 | 259 | animationDelegate?.animatorWillStartDismissal(self) 260 | 261 | animateMenu(menu.view, context: context) 262 | } 263 | 264 | func animateMenu(_ view: UIView, context:UIViewControllerContextTransitioning) { 265 | animationContext = context 266 | vectorDY = CGFloat(vectorDYCoefficient * Double(UIScreen.main.bounds.size.height) / animationDuration) 267 | 268 | var rotationDirection = CGVector(dx: 0, dy: -vectorDY) 269 | var fromX: CGFloat 270 | var fromY: CGFloat 271 | var toX: CGFloat 272 | var toY: CGFloat 273 | if mode == .presentation { 274 | if supportView != nil { 275 | showHostTitleLabel(false, animated: true) 276 | } 277 | view.transform = CGAffineTransform.identity.rotated( 278 | by: degreesToRadians(initialMenuRotationAngle) 279 | ) 280 | view.frame = CGRect( 281 | x: 0, 282 | y: -view.frame.height + topOffset, 283 | width: view.frame.width, 284 | height: view.frame.height 285 | ) 286 | rotationDirection = CGVector(dx: 0, dy: vectorDY) 287 | 288 | if UIDevice.current.orientation.isLandscape || UIApplication.shared.statusBarOrientation == .landscapeLeft || UIApplication.shared.statusBarOrientation == .landscapeRight { 289 | fromX = context.containerView.frame.width - 1 290 | fromY = context.containerView.frame.height + fromYPresentationLandscapeAdjustment 291 | toX = fromX + toXPresentationLandscapeAdjustment 292 | toY = fromY 293 | } else { 294 | fromX = -1 295 | fromY = context.containerView.frame.height + fromYPresentationAdjustment 296 | toX = fromX 297 | toY = fromY + 1 298 | } 299 | } else { 300 | if supportView != nil { 301 | showHostTitleLabel(true, animated: true) 302 | } 303 | if UIDevice.current.orientation.isLandscape || UIApplication.shared.statusBarOrientation == .landscapeLeft || UIApplication.shared.statusBarOrientation == .landscapeRight { 304 | fromX = -1 305 | fromY = -context.containerView.frame.width + topOffset + fromYDismissalLandscapeAdjustment 306 | toX = fromX 307 | toY = fromY + toYDismissalLandscapeAdjustment 308 | } else { 309 | fromX = context.containerView.frame.height - 1 310 | fromY = -context.containerView.frame.width + topOffset + fromYDismissalAdjustment 311 | toX = fromX + 1 312 | toY = fromY 313 | } 314 | } 315 | 316 | animator = UIDynamicAnimator(referenceView: context.containerView) 317 | animator.delegate = self 318 | 319 | let anchorPoint = CGPoint(x: topOffset / 2, y: topOffset / 2) 320 | let viewOffset = UIOffset(horizontal: -view.bounds.size.width / 2 + anchorPoint.x, vertical: -view.bounds.size.height / 2 + anchorPoint.y) 321 | let attachmentBehaviour = UIAttachmentBehavior(item: view, offsetFromCenter: viewOffset, attachedToAnchor: anchorPoint) 322 | animator.addBehavior(attachmentBehaviour) 323 | 324 | let collisionBehaviour = UICollisionBehavior() 325 | collisionBehaviour.addBoundary( 326 | withIdentifier: "collide" as NSCopying, 327 | from: CGPoint(x: fromX, y: fromY), 328 | to: CGPoint(x: toX, y: toY) 329 | ) 330 | collisionBehaviour.addItem(view) 331 | animator.addBehavior(collisionBehaviour) 332 | 333 | let itemBehaviour = UIDynamicItemBehavior(items: [view]) 334 | itemBehaviour.elasticity = menuElasticity 335 | itemBehaviour.density = menuDensity 336 | animator.addBehavior(itemBehaviour) 337 | 338 | let fallBehaviour = UIPushBehavior(items:[view], mode: .continuous) 339 | fallBehaviour.pushDirection = rotationDirection 340 | animator.addBehavior(fallBehaviour) 341 | 342 | displayLink.isPaused = false 343 | } 344 | } 345 | 346 | //MARK: - UIViewControllerAnimatedTransitioning protocol implementation 347 | extension GuillotineTransitionAnimation: UIViewControllerAnimatedTransitioning { 348 | 349 | public func animateTransition(using context: UIViewControllerContextTransitioning) { 350 | switch mode { 351 | case .presentation: 352 | animatePresentation(using: context) 353 | case .dismissal: 354 | animateDismissal(using: context) 355 | } 356 | } 357 | 358 | public func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 359 | return animationDuration 360 | } 361 | } 362 | 363 | //MARK: - UIDynamicAnimatorDelegate protocol implementation 364 | extension GuillotineTransitionAnimation: UIDynamicAnimatorDelegate { 365 | 366 | public func dynamicAnimatorDidPause(_ animator: UIDynamicAnimator) { 367 | if mode == .presentation { 368 | animator.removeAllBehaviors() 369 | menu.view.transform = .identity 370 | menu.view.frame = animationContext.containerView.bounds 371 | anchorPoint = .zero 372 | } 373 | 374 | chromeView?.removeFromSuperview() 375 | animationContext.completeTransition(true) 376 | 377 | if mode == .presentation { 378 | let fromVC = animationContext.viewController(forKey: UITransitionContextViewControllerKey.from) 379 | fromVC?.endAppearanceTransition() 380 | animationDelegate?.animatorDidFinishPresentation(self) 381 | } else { 382 | let toVC = animationContext.viewController(forKey: UITransitionContextViewControllerKey.to) 383 | toVC?.endAppearanceTransition() 384 | animationDelegate?.animatorDidFinishDismissal(self) 385 | } 386 | 387 | displayLink.isPaused = true 388 | } 389 | } 390 | 391 | fileprivate func degreesToRadians(_ degrees: CGFloat) -> CGFloat { 392 | return degrees / 180.0 * CGFloat(Double.pi) 393 | } 394 | 395 | fileprivate func radiansToDegrees(_ radians: CGFloat) -> CGFloat { 396 | return radians * 180.0 / CGFloat(Double.pi) 397 | } 398 | -------------------------------------------------------------------------------- /GuillotineMenu/ic_menu@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenu/ic_menu@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | *.xcodeproj/** 21 | !*.xcodeproj/*.pbxproj 22 | !*.xcodeproj/xcshareddata 23 | 24 | *.xcworkspace/** 25 | !*.xcworkspace/*.xcworkspacedata 26 | 27 | pkg/* 28 | 29 | Pods 30 | Podfile.lock 31 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2D004B2F1BC6683B00B3E015 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D004B2E1BC6683A00B3E015 /* MenuViewController.swift */; }; 11 | 2D1F8E8B1AD7D9B40049A439 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1F8E8A1AD7D9B40049A439 /* AppDelegate.swift */; }; 12 | 2D1F8E8D1AD7D9B40049A439 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D1F8E8C1AD7D9B40049A439 /* ViewController.swift */; }; 13 | 2D1F8E901AD7D9B40049A439 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2D1F8E8E1AD7D9B40049A439 /* Main.storyboard */; }; 14 | 2D1F8E921AD7D9B40049A439 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2D1F8E911AD7D9B40049A439 /* Images.xcassets */; }; 15 | 2D725B2B1AD7DDDE00555AD3 /* content_1@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B231AD7DDDE00555AD3 /* content_1@3x.png */; }; 16 | 2D725B2C1AD7DDDE00555AD3 /* content_2@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B241AD7DDDE00555AD3 /* content_2@3x.png */; }; 17 | 2D725B2D1AD7DDDE00555AD3 /* ic_activity@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B251AD7DDDE00555AD3 /* ic_activity@3x.png */; }; 18 | 2D725B2E1AD7DDDE00555AD3 /* ic_feed@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B261AD7DDDE00555AD3 /* ic_feed@3x.png */; }; 19 | 2D725B301AD7DDDE00555AD3 /* ic_menuRotated@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B281AD7DDDE00555AD3 /* ic_menuRotated@3x.png */; }; 20 | 2D725B311AD7DDDE00555AD3 /* ic_profile@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B291AD7DDDE00555AD3 /* ic_profile@3x.png */; }; 21 | 2D725B321AD7DDDE00555AD3 /* ic_settings@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2D725B2A1AD7DDDE00555AD3 /* ic_settings@3x.png */; }; 22 | 2D725B541AD7DEAC00555AD3 /* GuillotineMenuTransitionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D725B501AD7DEAC00555AD3 /* GuillotineMenuTransitionAnimation.swift */; }; 23 | 2DD3E2621BC803B900226F86 /* ic_menu@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 2DD3E2611BC803B900226F86 /* ic_menu@3x.png */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 2D004B2E1BC6683A00B3E015 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 28 | 2D1F8E851AD7D9B40049A439 /* GuillotineMenuExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GuillotineMenuExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 2D1F8E891AD7D9B40049A439 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | 2D1F8E8A1AD7D9B40049A439 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 31 | 2D1F8E8C1AD7D9B40049A439 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 32 | 2D1F8E8F1AD7D9B40049A439 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 2D1F8E911AD7D9B40049A439 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 34 | 2D725B231AD7DDDE00555AD3 /* content_1@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "content_1@3x.png"; sourceTree = ""; }; 35 | 2D725B241AD7DDDE00555AD3 /* content_2@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "content_2@3x.png"; sourceTree = ""; }; 36 | 2D725B251AD7DDDE00555AD3 /* ic_activity@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_activity@3x.png"; sourceTree = ""; }; 37 | 2D725B261AD7DDDE00555AD3 /* ic_feed@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_feed@3x.png"; sourceTree = ""; }; 38 | 2D725B281AD7DDDE00555AD3 /* ic_menuRotated@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_menuRotated@3x.png"; sourceTree = ""; }; 39 | 2D725B291AD7DDDE00555AD3 /* ic_profile@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_profile@3x.png"; sourceTree = ""; }; 40 | 2D725B2A1AD7DDDE00555AD3 /* ic_settings@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_settings@3x.png"; sourceTree = ""; }; 41 | 2D725B501AD7DEAC00555AD3 /* GuillotineMenuTransitionAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GuillotineMenuTransitionAnimation.swift; sourceTree = ""; }; 42 | 2DD3E2611BC803B900226F86 /* ic_menu@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_menu@3x.png"; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 2D1F8E821AD7D9B40049A439 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 2D1F8E7C1AD7D9B40049A439 = { 57 | isa = PBXGroup; 58 | children = ( 59 | 2D1F8E871AD7D9B40049A439 /* GuillotineMenuExample */, 60 | 2D1F8E861AD7D9B40049A439 /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 2D1F8E861AD7D9B40049A439 /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 2D1F8E851AD7D9B40049A439 /* GuillotineMenuExample.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 2D1F8E871AD7D9B40049A439 /* GuillotineMenuExample */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 2D725B4E1AD7DEAC00555AD3 /* GuillotineMenu */, 76 | 2D725B221AD7DDDE00555AD3 /* Assets */, 77 | 2D1F8E8A1AD7D9B40049A439 /* AppDelegate.swift */, 78 | 2D1F8E8C1AD7D9B40049A439 /* ViewController.swift */, 79 | 2D004B2E1BC6683A00B3E015 /* MenuViewController.swift */, 80 | 2D1F8E8E1AD7D9B40049A439 /* Main.storyboard */, 81 | 2D1F8E911AD7D9B40049A439 /* Images.xcassets */, 82 | 2D1F8E881AD7D9B40049A439 /* Supporting Files */, 83 | ); 84 | path = GuillotineMenuExample; 85 | sourceTree = ""; 86 | }; 87 | 2D1F8E881AD7D9B40049A439 /* Supporting Files */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 2D1F8E891AD7D9B40049A439 /* Info.plist */, 91 | ); 92 | name = "Supporting Files"; 93 | sourceTree = ""; 94 | }; 95 | 2D725B221AD7DDDE00555AD3 /* Assets */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 2D725B231AD7DDDE00555AD3 /* content_1@3x.png */, 99 | 2D725B241AD7DDDE00555AD3 /* content_2@3x.png */, 100 | 2D725B251AD7DDDE00555AD3 /* ic_activity@3x.png */, 101 | 2D725B261AD7DDDE00555AD3 /* ic_feed@3x.png */, 102 | 2D725B281AD7DDDE00555AD3 /* ic_menuRotated@3x.png */, 103 | 2D725B291AD7DDDE00555AD3 /* ic_profile@3x.png */, 104 | 2D725B2A1AD7DDDE00555AD3 /* ic_settings@3x.png */, 105 | ); 106 | path = Assets; 107 | sourceTree = ""; 108 | }; 109 | 2D725B4E1AD7DEAC00555AD3 /* GuillotineMenu */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 2DD3E2611BC803B900226F86 /* ic_menu@3x.png */, 113 | 2D725B501AD7DEAC00555AD3 /* GuillotineMenuTransitionAnimation.swift */, 114 | ); 115 | name = GuillotineMenu; 116 | path = ../../GuillotineMenu; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 2D1F8E841AD7D9B40049A439 /* GuillotineMenuExample */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 2D1F8EA41AD7D9B40049A439 /* Build configuration list for PBXNativeTarget "GuillotineMenuExample" */; 125 | buildPhases = ( 126 | 2D1F8E811AD7D9B40049A439 /* Sources */, 127 | 2D1F8E821AD7D9B40049A439 /* Frameworks */, 128 | 2D1F8E831AD7D9B40049A439 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = GuillotineMenuExample; 135 | productName = GuillotineMenuExample; 136 | productReference = 2D1F8E851AD7D9B40049A439 /* GuillotineMenuExample.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | 2D1F8E7D1AD7D9B40049A439 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastSwiftMigration = 0700; 146 | LastSwiftUpdateCheck = 0700; 147 | LastUpgradeCheck = 1000; 148 | ORGANIZATIONNAME = Yalantis; 149 | TargetAttributes = { 150 | 2D1F8E841AD7D9B40049A439 = { 151 | CreatedOnToolsVersion = 6.3; 152 | LastSwiftMigration = 1110; 153 | }; 154 | }; 155 | }; 156 | buildConfigurationList = 2D1F8E801AD7D9B40049A439 /* Build configuration list for PBXProject "GuillotineMenuExample" */; 157 | compatibilityVersion = "Xcode 3.2"; 158 | developmentRegion = en; 159 | hasScannedForEncodings = 0; 160 | knownRegions = ( 161 | en, 162 | Base, 163 | ); 164 | mainGroup = 2D1F8E7C1AD7D9B40049A439; 165 | productRefGroup = 2D1F8E861AD7D9B40049A439 /* Products */; 166 | projectDirPath = ""; 167 | projectRoot = ""; 168 | targets = ( 169 | 2D1F8E841AD7D9B40049A439 /* GuillotineMenuExample */, 170 | ); 171 | }; 172 | /* End PBXProject section */ 173 | 174 | /* Begin PBXResourcesBuildPhase section */ 175 | 2D1F8E831AD7D9B40049A439 /* Resources */ = { 176 | isa = PBXResourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 2D1F8E901AD7D9B40049A439 /* Main.storyboard in Resources */, 180 | 2D725B2B1AD7DDDE00555AD3 /* content_1@3x.png in Resources */, 181 | 2D725B321AD7DDDE00555AD3 /* ic_settings@3x.png in Resources */, 182 | 2D725B2D1AD7DDDE00555AD3 /* ic_activity@3x.png in Resources */, 183 | 2DD3E2621BC803B900226F86 /* ic_menu@3x.png in Resources */, 184 | 2D725B2E1AD7DDDE00555AD3 /* ic_feed@3x.png in Resources */, 185 | 2D725B2C1AD7DDDE00555AD3 /* content_2@3x.png in Resources */, 186 | 2D725B311AD7DDDE00555AD3 /* ic_profile@3x.png in Resources */, 187 | 2D1F8E921AD7D9B40049A439 /* Images.xcassets in Resources */, 188 | 2D725B301AD7DDDE00555AD3 /* ic_menuRotated@3x.png in Resources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXResourcesBuildPhase section */ 193 | 194 | /* Begin PBXSourcesBuildPhase section */ 195 | 2D1F8E811AD7D9B40049A439 /* Sources */ = { 196 | isa = PBXSourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 2D1F8E8D1AD7D9B40049A439 /* ViewController.swift in Sources */, 200 | 2D004B2F1BC6683B00B3E015 /* MenuViewController.swift in Sources */, 201 | 2D725B541AD7DEAC00555AD3 /* GuillotineMenuTransitionAnimation.swift in Sources */, 202 | 2D1F8E8B1AD7D9B40049A439 /* AppDelegate.swift in Sources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXSourcesBuildPhase section */ 207 | 208 | /* Begin PBXVariantGroup section */ 209 | 2D1F8E8E1AD7D9B40049A439 /* Main.storyboard */ = { 210 | isa = PBXVariantGroup; 211 | children = ( 212 | 2D1F8E8F1AD7D9B40049A439 /* Base */, 213 | ); 214 | name = Main.storyboard; 215 | sourceTree = ""; 216 | }; 217 | /* End PBXVariantGroup section */ 218 | 219 | /* Begin XCBuildConfiguration section */ 220 | 2D1F8EA21AD7D9B40049A439 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 225 | CLANG_CXX_LIBRARY = "libc++"; 226 | CLANG_ENABLE_MODULES = YES; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 229 | CLANG_WARN_BOOL_CONVERSION = YES; 230 | CLANG_WARN_COMMA = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 233 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 234 | CLANG_WARN_EMPTY_BODY = YES; 235 | CLANG_WARN_ENUM_CONVERSION = YES; 236 | CLANG_WARN_INFINITE_RECURSION = YES; 237 | CLANG_WARN_INT_CONVERSION = YES; 238 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 239 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 240 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 242 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 243 | CLANG_WARN_STRICT_PROTOTYPES = YES; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 248 | COPY_PHASE_STRIP = NO; 249 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | ENABLE_TESTABILITY = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | GCC_PREPROCESSOR_DEFINITIONS = ( 257 | "DEBUG=1", 258 | "$(inherited)", 259 | ); 260 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 261 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 263 | GCC_WARN_UNDECLARED_SELECTOR = YES; 264 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 265 | GCC_WARN_UNUSED_FUNCTION = YES; 266 | GCC_WARN_UNUSED_VARIABLE = YES; 267 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 268 | MTL_ENABLE_DEBUG_INFO = YES; 269 | ONLY_ACTIVE_ARCH = YES; 270 | SDKROOT = iphoneos; 271 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 272 | SWIFT_VERSION = 4.2; 273 | }; 274 | name = Debug; 275 | }; 276 | 2D1F8EA31AD7D9B40049A439 /* Release */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INFINITE_RECURSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 295 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 296 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 306 | ENABLE_NS_ASSERTIONS = NO; 307 | ENABLE_STRICT_OBJC_MSGSEND = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu99; 309 | GCC_NO_COMMON_BLOCKS = YES; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 317 | MTL_ENABLE_DEBUG_INFO = NO; 318 | SDKROOT = iphoneos; 319 | SWIFT_COMPILATION_MODE = wholemodule; 320 | SWIFT_VERSION = 4.2; 321 | VALIDATE_PRODUCT = YES; 322 | }; 323 | name = Release; 324 | }; 325 | 2D1F8EA51AD7D9B40049A439 /* Debug */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 329 | INFOPLIST_FILE = GuillotineMenuExample/Info.plist; 330 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 331 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 332 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.$(PRODUCT_NAME:rfc1034identifier)"; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | SWIFT_VERSION = 5.0; 335 | }; 336 | name = Debug; 337 | }; 338 | 2D1F8EA61AD7D9B40049A439 /* Release */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 342 | INFOPLIST_FILE = GuillotineMenuExample/Info.plist; 343 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 344 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 345 | PRODUCT_BUNDLE_IDENTIFIER = "com.yalantis.$(PRODUCT_NAME:rfc1034identifier)"; 346 | PRODUCT_NAME = "$(TARGET_NAME)"; 347 | SWIFT_VERSION = 5.0; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | 2D1F8E801AD7D9B40049A439 /* Build configuration list for PBXProject "GuillotineMenuExample" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | 2D1F8EA21AD7D9B40049A439 /* Debug */, 358 | 2D1F8EA31AD7D9B40049A439 /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | 2D1F8EA41AD7D9B40049A439 /* Build configuration list for PBXNativeTarget "GuillotineMenuExample" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | 2D1F8EA51AD7D9B40049A439 /* Debug */, 367 | 2D1F8EA61AD7D9B40049A439 /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | }; 374 | rootObject = 2D1F8E7D1AD7D9B40049A439 /* Project object */; 375 | } 376 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GuillotineMenu 4 | // 5 | // Created by Maksym Lazebnyi on 3/24/15. 6 | // Copyright (c) 2015 Yalantis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | } 16 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/content_1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/content_1@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/content_2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/content_2@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/ic_activity@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/ic_activity@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/ic_feed@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/ic_feed@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/ic_menuRotated@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/ic_menuRotated@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/ic_profile@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/ic_profile@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Assets/ic_settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/GuillotineMenuExample/GuillotineMenuExample/Assets/ic_settings@3x.png -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 126 | 143 | 160 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 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 | 247 | 248 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/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 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // GuillotineMenuExample 4 | // 5 | // Created by Maksym Lazebnyi on 10/8/15. 6 | // Copyright © 2015 Yalantis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MenuViewController: UIViewController, GuillotineMenu { 12 | 13 | var dismissButton: UIButton? 14 | var titleLabel: UILabel? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | dismissButton = { 20 | let button = UIButton(frame: .zero) 21 | button.setImage(UIImage(named: "ic_menu"), for: .normal) 22 | button.addTarget(self, action: #selector(dismissButtonTapped), for: .touchUpInside) 23 | return button 24 | }() 25 | 26 | titleLabel = { 27 | let label = UILabel() 28 | label.numberOfLines = 1; 29 | label.text = "Activity" 30 | label.font = UIFont.boldSystemFont(ofSize: 17) 31 | label.textColor = UIColor.white 32 | label.sizeToFit() 33 | return label 34 | }() 35 | } 36 | 37 | override func viewWillAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | print("Menu: viewWillAppear") 40 | } 41 | 42 | override func viewDidAppear(_ animated: Bool) { 43 | super.viewDidAppear(animated) 44 | print("Menu: viewDidAppear") 45 | } 46 | 47 | override func viewWillDisappear(_ animated: Bool) { 48 | super.viewWillDisappear(animated) 49 | print("Menu: viewWillDisappear") 50 | } 51 | 52 | override func viewDidDisappear(_ animated: Bool) { 53 | super.viewDidDisappear(animated) 54 | print("Menu: viewDidDisappear") 55 | } 56 | 57 | @objc func dismissButtonTapped(_ sender: UIButton) { 58 | presentingViewController!.dismiss(animated: true, completion: nil) 59 | } 60 | 61 | @IBAction func menuButtonTapped(_ sender: UIButton) { 62 | presentingViewController!.dismiss(animated: true, completion: nil) 63 | } 64 | 65 | @IBAction func closeMenu(_ sender: UIButton) { 66 | presentingViewController!.dismiss(animated: true, completion: nil) 67 | } 68 | } 69 | 70 | extension MenuViewController: GuillotineAnimationDelegate { 71 | 72 | func animatorDidFinishPresentation(_ animator: GuillotineTransitionAnimation) { 73 | print("menuDidFinishPresentation") 74 | } 75 | func animatorDidFinishDismissal(_ animator: GuillotineTransitionAnimation) { 76 | print("menuDidFinishDismissal") 77 | } 78 | 79 | func animatorWillStartPresentation(_ animator: GuillotineTransitionAnimation) { 80 | print("willStartPresentation") 81 | } 82 | 83 | func animatorWillStartDismissal(_ animator: GuillotineTransitionAnimation) { 84 | print("willStartDismissal") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /GuillotineMenuExample/GuillotineMenuExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // GuillotineMenu 4 | // 5 | // Created by Maksym Lazebnyi on 3/24/15. 6 | // Copyright (c) 2015 Yalantis. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | fileprivate let cellHeight: CGFloat = 210 14 | fileprivate let cellSpacing: CGFloat = 20 15 | fileprivate lazy var presentationAnimator = GuillotineTransitionAnimation() 16 | 17 | @IBOutlet fileprivate var barButton: UIButton! 18 | 19 | override func viewWillAppear(_ animated: Bool) { 20 | super.viewWillAppear(animated) 21 | 22 | print("VC: viewWillAppear") 23 | } 24 | 25 | override func viewDidAppear(_ animated: Bool) { 26 | super.viewDidAppear(animated) 27 | 28 | print("VC: viewDidAppear") 29 | } 30 | 31 | override func viewWillDisappear(_ animated: Bool) { 32 | super.viewWillDisappear(animated) 33 | 34 | print("VC: viewWillDisappear") 35 | } 36 | 37 | override func viewDidDisappear(_ animated: Bool) { 38 | super.viewDidDisappear(animated) 39 | 40 | print("VC: viewDidDisappear") 41 | } 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | let navBar = self.navigationController?.navigationBar 46 | navBar?.barTintColor = UIColor( 47 | red: 65.0 / 255.0, 48 | green: 62.0 / 255.0, 49 | blue: 79.0 / 255.0, 50 | alpha: 1 51 | ) 52 | navBar?.titleTextAttributes = [.foregroundColor: UIColor.white] 53 | } 54 | 55 | @IBAction func showMenuAction(_ sender: UIButton) { 56 | let menuViewController = storyboard!.instantiateViewController(withIdentifier: "MenuViewController") 57 | menuViewController.modalPresentationStyle = .custom 58 | menuViewController.transitioningDelegate = self 59 | 60 | presentationAnimator.animationDelegate = menuViewController as? GuillotineAnimationDelegate 61 | presentationAnimator.supportView = navigationController!.navigationBar 62 | presentationAnimator.presentButton = sender 63 | present(menuViewController, animated: true, completion: nil) 64 | } 65 | } 66 | 67 | extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate { 68 | 69 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 70 | return 5 71 | } 72 | 73 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 74 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ContentCell", for: indexPath) 75 | return cell 76 | } 77 | 78 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize { 79 | return CGSize(width: collectionView.bounds.width - cellSpacing, height: cellHeight) 80 | } 81 | } 82 | 83 | extension ViewController: UIViewControllerTransitioningDelegate { 84 | 85 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 86 | presentationAnimator.mode = .presentation 87 | return presentationAnimator 88 | } 89 | 90 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 91 | presentationAnimator.mode = .dismissal 92 | return presentationAnimator 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Yalantis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GuillotineMenu.swift 2 | 3 | [![pod version](https://img.shields.io/cocoapods/v/GuillotineMenu.svg)](https://img.shields.io/cocoapods/v/GuillotineMenu.svg) 4 | [![Platform](https://img.shields.io/cocoapods/p/GuillotineMenu.svg?style=flat)](https://github.com/Yalantis/GuillotineMenu) 5 | [![Yalantis](https://github.com/Yalantis/GuillotineMenu/blob/master/badge_dark.png)](https://yalantis.com/?utm_source=github) 6 | 7 | ![Preview](https://github.com/Yalantis/GuillotineMenu/blob/master/example.gif) 8 | 9 | Inspired by [this project on Dribbble](https://dribbble.com/shots/2018249-Side-Topbar-Animation) 10 | 11 | Also, read how it was done in our [blog](https://yalantis.com/blog/how-we-created-guillotine-menu-animation/) 12 | 13 | 14 | ## Requirements 15 | 16 | - iOS 8.0+ 17 | - Xcode 10 18 | - Swift 5.0 (v 4.1+) 19 | - Swift 4.2 (v 4.0) 20 | 21 | ## Installation 22 | 23 | #### [CocoaPods](http://cocoapods.org) 24 | 25 | ```ruby 26 | pod 'GuillotineMenu' 27 | ``` 28 | 29 | #### Manual Installation 30 | 31 | You are welcome to see the sample of the project for fully operating sample in the Example folder. 32 | 33 | * You must add "GuillotineMenuTransitionAnimation.swift" to your project, that's all. 34 | 35 | ### Usage 36 | 37 | * Now, it's for you to decide, should or not your menu drop from top left corner of the screen or from your navigation bar, because if you want animation like in example, you must make your menu view controller confirm to "GuillotineMenu" protocol. When you confirm to this protocol, you must make a menu button and title, you don't need to make frame for them, because animator will make it itself. 38 | * In view controller, that will present your menu, you must make a property for "GuillotineMenuTransitionAnimator". It's necessary for proper animation when you show or dismiss menu. 39 | * When you present menu, you must ensure, that model presentation style set to Custom and menu's transition delegate set to view controller, that presents menu: 40 | 41 | ```swift 42 | let menuViewController = storyboard!.instantiateViewController(withIdentifier: "MenuViewController") 43 | menuViewController.modalPresentationStyle = .custom 44 | menuViewController.transitioningDelegate = self 45 | ``` 46 | 47 | * Implement UIViewControllerTransitionDelegate methods in your presenting view controller: 48 | 49 | ```swift 50 | extension ViewController: UIViewControllerTransitioningDelegate { 51 | 52 | func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 53 | presentationAnimator.mode = .presentation 54 | return presentationAnimator 55 | } 56 | 57 | func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 58 | presentationAnimator.mode = .dismissal 59 | return presentationAnimator 60 | } 61 | ``` 62 | 63 | * At last, you can assign offset view, from where your menu will be dropped and button for it, and present your menu: 64 | 65 | ```swift 66 | presentationAnimator.supportView = navigationController!.navigationBar 67 | presentationAnimator.presentButton = sender 68 | present(menuViewController, animated: true, completion: nil) 69 | ``` 70 | 71 | ### Customisation 72 | 73 | Of course, you can assign different "supportView" or "presentButton" for menu, but we think that's the best case would be behaviour like in Example project. 74 | 75 | To specify the length of an animation effect, change the value of the "duration" property. 76 | 77 | Also, you have wonderful delegate methods of animator: 78 | 79 | ```swift 80 | public protocol GuillotineAnimationDelegate: class { 81 | 82 | func animatorDidFinishPresentation(_ animator: GuillotineTransitionAnimation) 83 | func animatorDidFinishDismissal(_ animator: GuillotineTransitionAnimation) 84 | func animatorWillStartPresentation(_ animator: GuillotineTransitionAnimation) 85 | func animatorWillStartDismissal(_ animator: GuillotineTransitionAnimation) 86 | } 87 | ``` 88 | You can do whatever you want alongside menu is animating. 89 | 90 | ### Let us know! 91 | 92 | We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the animation. 93 | 94 | P.S. We’re going to publish more awesomeness wrapped in code and a tutorial on how to make UI for iOS (Android) better than better. Stay tuned! 95 | 96 | 97 | ### License 98 | 99 | The MIT License (MIT) 100 | 101 | Copyright © 2017 Yalantis 102 | 103 | Permission is hereby granted, free of charge, to any person obtaining a copy 104 | of this software and associated documentation files (the "Software"), to deal 105 | in the Software without restriction, including without limitation the rights 106 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 107 | copies of the Software, and to permit persons to whom the Software is 108 | furnished to do so, subject to the following conditions: 109 | 110 | The above copyright notice and this permission notice shall be included in all 111 | copies or substantial portions of the Software. 112 | 113 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 114 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 115 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 116 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 117 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 118 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 119 | SOFTWARE. 120 | 121 | -------------------------------------------------------------------------------- /badge_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/badge_dark.png -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yalantis/GuillotineMenu/0f3819752ecc25a2cc9c4b2e1c7ebb26105a687c/example.gif --------------------------------------------------------------------------------