added iOS source code
[wl-app.git] / iOS / Pods / SideMenu / Pod / Classes / UISideMenuNavigationController.swift
1 //
2 //  UISideMenuNavigationController.swift
3 //
4 //  Created by Jon Kent on 1/14/16.
5 //  Copyright © 2016 Jon Kent. All rights reserved.
6 //
7
8 import UIKit
9
10 @objc public protocol UISideMenuNavigationControllerDelegate {
11     @objc optional func sideMenuWillAppear(menu: UISideMenuNavigationController, animated: Bool)
12     @objc optional func sideMenuDidAppear(menu: UISideMenuNavigationController, animated: Bool)
13     @objc optional func sideMenuWillDisappear(menu: UISideMenuNavigationController, animated: Bool)
14     @objc optional func sideMenuDidDisappear(menu: UISideMenuNavigationController, animated: Bool)
15 }
16
17 @objcMembers
18 open class UISideMenuNavigationController: UINavigationController {
19     
20     fileprivate weak var foundDelegate: UISideMenuNavigationControllerDelegate?
21     fileprivate weak var activeDelegate: UISideMenuNavigationControllerDelegate? {
22         get {
23             guard !view.isHidden else {
24                 return nil
25             }
26             
27             return sideMenuDelegate ?? foundDelegate ?? findDelegate(forViewController: presentingViewController)
28         }
29     }
30     fileprivate func findDelegate(forViewController: UIViewController?) -> UISideMenuNavigationControllerDelegate? {
31         if let navigationController = forViewController as? UINavigationController {
32             return findDelegate(forViewController: navigationController.topViewController)
33         }
34         if let tabBarController = forViewController as? UITabBarController {
35             return findDelegate(forViewController: tabBarController.selectedViewController)
36         }
37         if let splitViewController = forViewController as? UISplitViewController {
38             return findDelegate(forViewController: splitViewController.viewControllers.last)
39         }
40         
41         foundDelegate = forViewController as? UISideMenuNavigationControllerDelegate
42         return foundDelegate
43     }
44     fileprivate var usingInterfaceBuilder = false
45     internal var locked = false
46     internal var originalMenuBackgroundColor: UIColor?
47     internal var transition: SideMenuTransition {
48         get {
49             return sideMenuManager.transition
50         }
51     }
52     
53     /// Delegate for receiving appear and disappear related events. If `nil` the visible view controller that displays a `UISideMenuNavigationController` automatically receives these events.
54     open weak var sideMenuDelegate: UISideMenuNavigationControllerDelegate?
55     
56     /// SideMenuManager instance associated with this menu. Default is `SideMenuManager.default`. This property cannot be changed after the menu has loaded.
57     open weak var sideMenuManager: SideMenuManager! = SideMenuManager.default {
58         didSet {
59             if locked && oldValue != nil {
60                 print("SideMenu Warning: a menu's sideMenuManager property cannot be changed after it has loaded.")
61                 sideMenuManager = oldValue
62             }
63         }
64     }
65     
66     /// Width of the menu when presented on screen, showing the existing view controller in the remaining space. Default is zero. When zero, `sideMenuManager.menuWidth` is used. This property cannot be changed while the isHidden property is false.
67     @IBInspectable open var menuWidth: CGFloat = 0 {
68         didSet {
69             if !isHidden && oldValue != menuWidth {
70                 print("SideMenu Warning: a menu's width property can only be changed when it is hidden.")
71                 menuWidth = oldValue
72             }
73         }
74     }
75     
76     /// Whether the menu appears on the right or left side of the screen. Right is the default. This property cannot be changed after the menu has loaded.
77     @IBInspectable open var leftSide: Bool = false {
78         didSet {
79             if locked && leftSide != oldValue {
80                 print("SideMenu Warning: a menu's leftSide property cannot be changed after it has loaded.")
81                 leftSide = oldValue
82             }
83         }
84     }
85     
86     /// Indicates if the menu is anywhere in the view hierarchy, even if covered by another view controller.
87     open var isHidden: Bool {
88         get {
89             return self.presentingViewController == nil
90         }
91     }
92     
93     #if !STFU_SIDEMENU
94     // This override prevents newbie developers from creating black/blank menus and opening newbie issues.
95     // If you would like to remove this override, define STFU_SIDEMENU in the Active Compilation Conditions of your .plist file.
96     // Sorry for the inconvenience experienced developers :(
97     @available(*, unavailable, renamed: "init(rootViewController:)")
98     public init() {
99         fatalError("init is not available")
100     }
101     
102     public override init(rootViewController: UIViewController) {
103         super.init(rootViewController: rootViewController)
104     }
105
106     public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
107         super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
108     }
109     
110     public required init?(coder aDecoder: NSCoder) {
111         super.init(coder: aDecoder)
112     }
113     #endif
114     
115     open override func awakeFromNib() {
116         super.awakeFromNib()
117         
118         usingInterfaceBuilder = true
119     }
120     
121     override open func viewDidLoad() {
122         super.viewDidLoad()
123         
124         if !locked && usingInterfaceBuilder {
125             if leftSide {
126                 sideMenuManager.menuLeftNavigationController = self
127             } else {
128                 sideMenuManager.menuRightNavigationController = self
129             }
130         }
131     }
132     
133     open override func viewWillAppear(_ animated: Bool) {
134         super.viewWillAppear(animated)
135         
136         // Dismiss keyboard to prevent weird keyboard animations from occurring during transition
137         presentingViewController?.view.endEditing(true)
138         
139         foundDelegate = nil
140         activeDelegate?.sideMenuWillAppear?(menu: self, animated: animated)
141     }
142     
143     override open func viewDidAppear(_ animated: Bool) {
144         super.viewDidAppear(animated)
145         
146         // We had presented a view before, so lets dismiss ourselves as already acted upon
147         if view.isHidden {
148             transition.hideMenuComplete()
149             dismiss(animated: false, completion: { () -> Void in
150                 self.view.isHidden = false
151             })
152             
153             return
154         }
155         
156         activeDelegate?.sideMenuDidAppear?(menu: self, animated: animated)
157         
158         #if !STFU_SIDEMENU
159         if topViewController == nil {
160             print("SideMenu Warning: the menu doesn't have a view controller to show! UISideMenuNavigationController needs a view controller to display just like a UINavigationController.")
161         }
162         #endif
163     }
164     
165     override open func viewWillDisappear(_ animated: Bool) {
166         super.viewWillDisappear(animated)
167         
168         // When presenting a view controller from the menu, the menu view gets moved into another transition view above our transition container
169         // which can break the visual layout we had before. So, we move the menu view back to its original transition view to preserve it.
170         if !isBeingDismissed {
171             guard let sideMenuManager = sideMenuManager else {
172                 return
173             }
174             
175             if let mainView = transition.mainViewController?.view {
176                 switch sideMenuManager.menuPresentMode {
177                 case .viewSlideOut, .viewSlideInOut:
178                     mainView.superview?.insertSubview(view, belowSubview: mainView)
179                 case .menuSlideIn, .menuDissolveIn:
180                     if let tapView = transition.tapView {
181                         mainView.superview?.insertSubview(view, aboveSubview: tapView)
182                     } else {
183                         mainView.superview?.insertSubview(view, aboveSubview: mainView)
184                     }
185                 }
186             }
187             
188             // We're presenting a view controller from the menu, so we need to hide the menu so it isn't showing when the presented view is dismissed.
189             UIView.animate(withDuration: animated ? sideMenuManager.menuAnimationDismissDuration : 0,
190                            delay: 0,
191                            usingSpringWithDamping: sideMenuManager.menuAnimationUsingSpringWithDamping,
192                            initialSpringVelocity: sideMenuManager.menuAnimationInitialSpringVelocity,
193                            options: sideMenuManager.menuAnimationOptions,
194                            animations: {
195                             self.transition.hideMenuStart()
196                             self.activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
197             }) { (finished) -> Void in
198                 self.activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
199                 self.view.isHidden = true
200             }
201             
202             return
203         }
204         
205         activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
206     }
207     
208     override open func viewDidDisappear(_ animated: Bool) {
209         super.viewDidDisappear(animated)
210         
211         // Work-around: if the menu is dismissed without animation the transition logic is never called to restore the
212         // the view hierarchy leaving the screen black/empty. This is because the transition moves views within a container
213         // view, but dismissing without animation removes the container view before the original hierarchy is restored.
214         // This check corrects that.
215         if let sideMenuDelegate = activeDelegate as? UIViewController, sideMenuDelegate.view.window == nil {
216             transition.hideMenuStart().hideMenuComplete()
217         }
218         
219         activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
220         
221         // Clear selecton on UITableViewControllers when reappearing using custom transitions
222         guard let tableViewController = topViewController as? UITableViewController,
223             let tableView = tableViewController.tableView,
224             let indexPaths = tableView.indexPathsForSelectedRows,
225             tableViewController.clearsSelectionOnViewWillAppear else {
226             return
227         }
228         
229         for indexPath in indexPaths {
230             tableView.deselectRow(at: indexPath, animated: false)
231         }
232     }
233     
234     override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
235         super.viewWillTransition(to: size, with: coordinator)
236         
237         // Don't bother resizing if the view isn't visible
238         guard !view.isHidden else {
239             return
240         }
241         
242         NotificationCenter.default.removeObserver(self.transition, name: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil)
243         coordinator.animate(alongsideTransition: { (context) in
244             self.transition.presentMenuStart()
245         }) { (context) in
246             NotificationCenter.default.addObserver(self.transition, selector:#selector(SideMenuTransition.handleNotification), name: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil)
247         }
248     }
249     
250     override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
251         guard let sideMenuManager = sideMenuManager, viewControllers.count > 0 && sideMenuManager.menuPushStyle != .subMenu else {
252             // NOTE: pushViewController is called by init(rootViewController: UIViewController)
253             // so we must perform the normal super method in this case.
254             super.pushViewController(viewController, animated: animated)
255             return
256         }
257
258         let splitViewController = presentingViewController as? UISplitViewController
259         let tabBarController = presentingViewController as? UITabBarController
260         let potentialNavigationController = (splitViewController?.viewControllers.first ?? tabBarController?.selectedViewController) ?? presentingViewController
261         guard let navigationController = potentialNavigationController as? UINavigationController else {
262             print("SideMenu Warning: attempt to push a View Controller from \(String(describing: potentialNavigationController.self)) where its navigationController == nil. It must be embedded in a Navigation Controller for this to work.")
263             return
264         }
265         
266         let activeDelegate = self.activeDelegate
267         foundDelegate = nil
268         
269         // To avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
270         // is dismissed after showing the appropriate screen
271         CATransaction.begin()
272         if sideMenuManager.menuDismissOnPush {
273             let animated = animated || sideMenuManager.menuAlwaysAnimate
274             
275             CATransaction.setCompletionBlock( { () -> Void in
276                 activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
277                 if !animated {
278                     self.transition.hideMenuStart().hideMenuComplete()
279                 }
280                 self.dismiss(animated: animated, completion: nil)
281             })
282         
283             if animated {
284                 let areAnimationsEnabled = UIView.areAnimationsEnabled
285                 UIView.setAnimationsEnabled(true)
286                 UIView.animate(withDuration: sideMenuManager.menuAnimationDismissDuration,
287                                delay: 0,
288                                usingSpringWithDamping: sideMenuManager.menuAnimationUsingSpringWithDamping,
289                                initialSpringVelocity: sideMenuManager.menuAnimationInitialSpringVelocity,
290                                options: sideMenuManager.menuAnimationOptions,
291                                animations: {
292                                 activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
293                                 self.transition.hideMenuStart()
294                 })
295                 UIView.setAnimationsEnabled(areAnimationsEnabled)
296             }
297         }
298         
299         if let lastViewController = navigationController.viewControllers.last, !sideMenuManager.menuAllowPushOfSameClassTwice && type(of: lastViewController) == type(of: viewController) {
300             CATransaction.commit()
301             return
302         }
303         
304         switch sideMenuManager.menuPushStyle {
305         case .subMenu, .defaultBehavior: break // .subMenu handled earlier, .defaultBehavior falls through to end
306         case .popWhenPossible:
307             for subViewController in navigationController.viewControllers.reversed() {
308                 if type(of: subViewController) == type(of: viewController) {
309                     navigationController.popToViewController(subViewController, animated: animated)
310                     CATransaction.commit()
311                     return
312                 }
313             }
314         case .preserve, .preserveAndHideBackButton:
315             var viewControllers = navigationController.viewControllers
316             let filtered = viewControllers.filter { preservedViewController in type(of: preservedViewController) == type(of: viewController) }
317             if let preservedViewController = filtered.last {
318                 viewControllers = viewControllers.filter { subViewController in subViewController !== preservedViewController }
319                 if sideMenuManager.menuPushStyle == .preserveAndHideBackButton {
320                     preservedViewController.navigationItem.hidesBackButton = true
321                 }
322                 viewControllers.append(preservedViewController)
323                 navigationController.setViewControllers(viewControllers, animated: animated)
324                 CATransaction.commit()
325                 return
326             }
327             if sideMenuManager.menuPushStyle == .preserveAndHideBackButton {
328                 viewController.navigationItem.hidesBackButton = true
329             }
330         case .replace:
331             viewController.navigationItem.hidesBackButton = true
332             navigationController.setViewControllers([viewController], animated: animated)
333             CATransaction.commit()
334             return
335         }
336         
337         navigationController.pushViewController(viewController, animated: animated)
338         CATransaction.commit()
339     }
340
341 }
342
343