├── .gitignore ├── Demo ├── NonStoryboard │ ├── LGSideMenuControllerDemo.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── LGSideMenuControllerDemo.xcscheme │ └── LGSideMenuControllerDemo │ │ └── Info.plist ├── Storyboard │ ├── LGSideMenuControllerDemo.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── LGSideMenuControllerDemo.xcscheme │ └── LGSideMenuControllerDemo │ │ ├── AsContainerForNavigationController.storyboard │ │ ├── AsContainerForTabBarController.storyboard │ │ ├── Info.plist │ │ ├── InsideNavigationController.storyboard │ │ └── InsideTabBarController.storyboard └── _shared_files │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── imageLeft.imageset │ │ ├── Contents.json │ │ └── imageLeft.jpg │ ├── imageRight.imageset │ │ ├── Contents.json │ │ └── imageRight.jpg │ ├── imageRoot0.imageset │ │ ├── Contents.json │ │ └── imageRoot0.jpg │ ├── imageRoot1.imageset │ │ ├── Contents.json │ │ └── imageRoot1.jpg │ ├── imageRoot2.imageset │ │ ├── Contents.json │ │ └── imageRoot2.jpg │ └── imageRoot3.imageset │ │ ├── Contents.json │ │ └── imageRoot3.jpg │ ├── ChooseViewController │ ├── Choose.storyboard │ ├── ChooseNavigationController.swift │ └── ChooseTableViewController.swift │ ├── DemoEntries.swift │ ├── Helper.swift │ ├── LaunchScreen.storyboard │ └── SideMenuController │ ├── LeftViewController │ ├── LeftViewCell.swift │ ├── LeftViewCellBackgroundView.swift │ └── LeftViewController.swift │ ├── MainViewController.swift │ ├── RightViewController │ ├── RightViewCell.swift │ ├── RightViewCellHeaderView.swift │ └── RightViewController.swift │ └── RootViewController │ ├── RootNavigationController.swift │ ├── RootTabBarController.swift │ ├── RootViewController.swift │ ├── RootViewControllerWithScrollView.swift │ ├── RootViewControllerWithTableView.swift │ └── RootViewControllerWithTextLabel.swift ├── Framework ├── LGSideMenuControllerFramework.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── LGSideMenuControllerFramework.xcscheme └── LGSideMenuControllerFramework │ ├── Info.plist │ ├── LGSideMenuControllerFramework.h │ └── module.modulemap ├── LGSideMenuController.podspec ├── LGSideMenuController ├── Extensions │ ├── LGSideMenuController+Callbacks.swift │ ├── LGSideMenuController+GesturesHandler.swift │ ├── LGSideMenuController+Helpers.swift │ ├── LGSideMenuController+Layouting.swift │ ├── LGSideMenuController+LeftViewActions.swift │ ├── LGSideMenuController+RightViewActions.swift │ ├── LGSideMenuController+Rotating.swift │ ├── LGSideMenuController+States.swift │ ├── LGSideMenuController+StatusBarHandler.swift │ ├── LGSideMenuController+ViewsRemoving.swift │ └── Validators │ │ ├── LGSideMenuController+ValidatingUserInteraction.swift │ │ ├── LGSideMenuController+ValidatingViewsFrames.swift │ │ ├── LGSideMenuController+ValidatingViewsHierarchy.swift │ │ ├── LGSideMenuController+ValidatingViewsInit.swift │ │ ├── LGSideMenuController+ValidatingViewsStyles.swift │ │ ├── LGSideMenuController+ValidatingViewsTransforms.swift │ │ └── LGSideMenuController+ValidatingViewsVisibility.swift ├── LGSideMenuBackgroundDecorationView.swift ├── LGSideMenuBackgroundShadowView.swift ├── LGSideMenuController.swift ├── LGSideMenuDelegate.swift ├── LGSideMenuHelper.swift ├── LGSideMenuSegue.swift ├── LGSideMenuStatusBarBackgroundView.swift ├── LGSideMenuWrapperView.swift └── UIViewController+LGSideMenuController.swift ├── LICENSE ├── Package.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/ 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 | .swiftpm 20 | 21 | # CocoaPods 22 | Pods/ 23 | 24 | # Carthage 25 | Carthage/Build 26 | 27 | # Mercurial 28 | .hgignore 29 | 30 | # MacOS 31 | .DS_Store 32 | 33 | # Other 34 | ._* 35 | -------------------------------------------------------------------------------- /Demo/NonStoryboard/LGSideMenuControllerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/NonStoryboard/LGSideMenuControllerDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/NonStoryboard/LGSideMenuControllerDemo.xcodeproj/xcshareddata/xcschemes/LGSideMenuControllerDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/NonStoryboard/LGSideMenuControllerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | IsStoryboardBased 28 | 29 | LSRequiresIPhoneOS 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Choose 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | 39 | UIRequiresFullScreen 40 | 41 | UIStatusBarHidden 42 | 43 | UIStatusBarStyle 44 | UIStatusBarStyleDefault 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UISupportedInterfaceOrientations~ipad 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | UIInterfaceOrientationPortraitUpsideDown 57 | 58 | UIViewControllerBasedStatusBarAppearance 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Demo/Storyboard/LGSideMenuControllerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Storyboard/LGSideMenuControllerDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Storyboard/LGSideMenuControllerDemo.xcodeproj/xcshareddata/xcschemes/LGSideMenuControllerDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/Storyboard/LGSideMenuControllerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | IsStoryboardBased 28 | 29 | LSRequiresIPhoneOS 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Choose 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | 39 | UIRequiresFullScreen 40 | 41 | UIStatusBarHidden 42 | 43 | UIStatusBarStyle 44 | UIStatusBarStyleDefault 45 | UISupportedInterfaceOrientations 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | UISupportedInterfaceOrientations~ipad 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | UIInterfaceOrientationPortraitUpsideDown 57 | 58 | UIViewControllerBasedStatusBarAppearance 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Demo/_shared_files/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | @main 10 | class AppDelegate: UIResponder, UIApplicationDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | return true 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageLeft.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageLeft.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageLeft.imageset/imageLeft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageLeft.imageset/imageLeft.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageRight.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRight.imageset/imageRight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageRight.imageset/imageRight.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageRoot0.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot0.imageset/imageRoot0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageRoot0.imageset/imageRoot0.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageRoot1.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot1.imageset/imageRoot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageRoot1.imageset/imageRoot1.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageRoot2.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot2.imageset/imageRoot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageRoot2.imageset/imageRoot2.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "imageRoot3.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Demo/_shared_files/Assets.xcassets/imageRoot3.imageset/imageRoot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Friend-LGA/LGSideMenuController/153733fceb6cc8f4c530f03bbc209562279846c7/Demo/_shared_files/Assets.xcassets/imageRoot3.imageset/imageRoot3.jpg -------------------------------------------------------------------------------- /Demo/_shared_files/ChooseViewController/Choose.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 | 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 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Demo/_shared_files/ChooseViewController/ChooseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChooseNavigationController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class ChooseNavigationController: UINavigationController { 10 | 11 | override var shouldAutorotate : Bool { 12 | return true 13 | } 14 | 15 | override var prefersStatusBarHidden : Bool { 16 | return UIApplication.shared.statusBarOrientation.isLandscape && UIDevice.current.userInterfaceIdiom == .phone 17 | } 18 | 19 | override var preferredStatusBarStyle : UIStatusBarStyle { 20 | return .lightContent 21 | } 22 | 23 | override var preferredStatusBarUpdateAnimation : UIStatusBarAnimation { 24 | return .none 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Demo/_shared_files/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let isStoryboardBasedKey = "IsStoryboardBased" 10 | 11 | private let backgroundImageNames: [String] = ["imageRoot0", "imageRoot1", "imageRoot2", "imageRoot3"] 12 | private var currentBackgroundImageNameIndex = 0 13 | 14 | let menuIconImage: UIImage = { 15 | let size = CGSize(width: 24.0, height: 16.0) 16 | let renderer = UIGraphicsImageRenderer(size: size) 17 | return renderer.image { context in 18 | let ctx = context.cgContext 19 | let lineHeight: CGFloat = 2.0 20 | 21 | ctx.setFillColor(UIColor.black.cgColor) 22 | ctx.setStrokeColor(UIColor.black.cgColor) 23 | ctx.setLineWidth(lineHeight) 24 | ctx.setLineCap(.round) 25 | 26 | ctx.beginPath() 27 | 28 | ctx.move(to: CGPoint(x: lineHeight, y: lineHeight / 2.0)) 29 | ctx.addLine(to: CGPoint(x: size.width - lineHeight, y: lineHeight / 2.0)) 30 | 31 | ctx.move(to: CGPoint(x: lineHeight, y: size.height / 2.0)) 32 | ctx.addLine(to: CGPoint(x: size.width - lineHeight, y: size.height / 2.0)) 33 | 34 | ctx.move(to: CGPoint(x: lineHeight, y: size.height - lineHeight / 2.0)) 35 | ctx.addLine(to: CGPoint(x: size.width - lineHeight, y: size.height - lineHeight / 2.0)) 36 | 37 | ctx.strokePath() 38 | } 39 | }() 40 | 41 | func tabBarIconImage(_ title: String) -> UIImage { 42 | let fontSize: CGFloat = 24.0 43 | let insetVertical: CGFloat = 4.0 44 | let size = CGSize(width: fontSize, height: fontSize + insetVertical) 45 | let renderer = UIGraphicsImageRenderer(size: size) 46 | return renderer.image { context in 47 | let paragraphStyle = NSMutableParagraphStyle() 48 | paragraphStyle.alignment = .center 49 | let attrs = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize), 50 | NSAttributedString.Key.paragraphStyle: paragraphStyle] 51 | title.draw(with: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), 52 | options: .usesLineFragmentOrigin, 53 | attributes: attrs, 54 | context: nil) 55 | } 56 | } 57 | 58 | func isStoryboardBasedDemo() -> Bool { 59 | guard let dict = Bundle.main.infoDictionary else { return false } 60 | return dict[isStoryboardBasedKey] as! Bool 61 | } 62 | 63 | func isLightTheme() -> Bool { 64 | if #available(iOS 13.0, *) { 65 | let currentStyle = UITraitCollection.current.userInterfaceStyle 66 | return currentStyle == .light || currentStyle == .unspecified 67 | } 68 | else { 69 | return true 70 | } 71 | } 72 | 73 | func getBackgroundImageNameDefault() -> String { 74 | currentBackgroundImageNameIndex = 0 75 | return backgroundImageNames[currentBackgroundImageNameIndex] 76 | } 77 | 78 | func getBackgroundImageNameRandom() -> String { 79 | var newIndex = currentBackgroundImageNameIndex 80 | while newIndex == currentBackgroundImageNameIndex { 81 | newIndex = Int.random(in: 0...3) 82 | } 83 | currentBackgroundImageNameIndex = newIndex 84 | return backgroundImageNames[currentBackgroundImageNameIndex] 85 | } 86 | 87 | func getKeyWindow() -> UIWindow? { 88 | if #available(iOS 13.0, *) { 89 | return UIApplication.shared.windows.first(where: { $0.isKeyWindow }) 90 | } else { 91 | return UIApplication.shared.keyWindow 92 | } 93 | } 94 | 95 | func getStatusBarFrame() -> CGRect { 96 | if #available(iOS 13.0, *) { 97 | return getKeyWindow()?.windowScene?.statusBarManager?.statusBarFrame ?? .zero 98 | } else { 99 | return UIApplication.shared.statusBarFrame 100 | } 101 | } 102 | 103 | func getTitle(for presentationStyle: LGSideMenuController.PresentationStyle) -> String { 104 | switch presentationStyle { 105 | case .scaleFromBig: 106 | return "Scale From Big" 107 | case .scaleFromLittle: 108 | return "Scale From Little" 109 | case .slideBelow: 110 | return "Slide Below" 111 | case .slideBelowShifted: 112 | return "Slide Below Shifted" 113 | case .slideAbove: 114 | return "Slide Above" 115 | case .slideAboveBlurred: 116 | return "Slide Above Blurred" 117 | case .slideAside: 118 | return "Slide Aside" 119 | } 120 | } 121 | 122 | extension UIView { 123 | class func fromNib() -> Self { 124 | return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)!.first as! Self 125 | } 126 | } 127 | 128 | extension UIColor { 129 | convenience init(hexString: String, alpha: CGFloat = 1.0) { 130 | let hexint = Int(Self.intFromHexString(hexStr: hexString)) 131 | let red = CGFloat((hexint & 0xff0000) >> 16) / 255.0 132 | let green = CGFloat((hexint & 0xff00) >> 8) / 255.0 133 | let blue = CGFloat((hexint & 0xff) >> 0) / 255.0 134 | self.init(red: red, green: green, blue: blue, alpha: alpha) 135 | } 136 | 137 | static private func intFromHexString(hexStr: String) -> UInt32 { 138 | var hexInt: UInt32 = 0 139 | let scanner: Scanner = Scanner(string: hexStr) 140 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "#") 141 | scanner.scanHexInt32(&hexInt) 142 | return hexInt 143 | } 144 | } 145 | 146 | enum SideViewCellItem: Equatable { 147 | case close 148 | case openLeft 149 | case openRight 150 | case changeRootVC 151 | case pushVC(title: String) 152 | 153 | var description: String { 154 | switch self { 155 | case .close: 156 | return "Close" 157 | case .openLeft: 158 | return "Open Left View" 159 | case .openRight: 160 | return "Open Right View" 161 | case .changeRootVC: 162 | return "Change Root VC" 163 | case let .pushVC(title): 164 | return title 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Demo/_shared_files/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/LeftViewController/LeftViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftViewCell.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let textFont = UIFont.boldSystemFont(ofSize: 16.0) 10 | private let fillColorNormal = UIColor(white: 0.0, alpha: 0.2) 11 | private let fillColorNormalInverted = UIColor(white: 1.0, alpha: 0.2) 12 | private let fillColorHighlighted = UIColor(white: 1.0, alpha: 0.6) 13 | private let textColorNormal: UIColor = .white 14 | private let textColorHighlighted: UIColor = .black 15 | 16 | class LeftViewCell: UITableViewCell { 17 | 18 | var isFirst: Bool = false { 19 | didSet { 20 | guard let backgroundView = self.backgroundView as? LeftViewCellBackgroundView else { return } 21 | backgroundView.isFirstCell = isFirst 22 | } 23 | } 24 | 25 | var isLast: Bool = false { 26 | didSet { 27 | guard let backgroundView = self.backgroundView as? LeftViewCellBackgroundView else { return } 28 | backgroundView.isLastCell = isLast 29 | } 30 | } 31 | 32 | var isFillColorInverted: Bool = false 33 | 34 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 35 | super.init(style: style, reuseIdentifier: reuseIdentifier) 36 | 37 | selectionStyle = .none 38 | backgroundColor = .clear 39 | 40 | backgroundView = LeftViewCellBackgroundView() 41 | setBackgroundColor(getFillColorNormal()) 42 | 43 | textLabel!.font = textFont 44 | textLabel!.textColor = textColorNormal 45 | textLabel!.numberOfLines = 1 46 | textLabel!.textAlignment = .left 47 | } 48 | 49 | required init?(coder: NSCoder) { 50 | fatalError("init(coder:) has not been implemented") 51 | } 52 | 53 | override func layoutSubviews() { 54 | super.layoutSubviews() 55 | 56 | backgroundView!.frame = CGRect(x: 0.0, 57 | y: 0.0, 58 | width: bounds.width - 16.0, 59 | height: bounds.height) 60 | 61 | textLabel!.frame = CGRect(x: 8.0, 62 | y: 0.0, 63 | width: bounds.width - 16.0 - 16.0, 64 | height: bounds.height) 65 | } 66 | 67 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 68 | super.setHighlighted(highlighted, animated: animated) 69 | setBackgroundColor(highlighted ? fillColorHighlighted : getFillColorNormal()) 70 | textLabel!.textColor = highlighted ? textColorHighlighted : textColorNormal 71 | } 72 | 73 | func getFillColorNormal() -> UIColor { 74 | return isFillColorInverted ? fillColorNormalInverted : fillColorNormal 75 | } 76 | 77 | func setBackgroundColor(_ color: UIColor) { 78 | let backgroundView = self.backgroundView as! LeftViewCellBackgroundView 79 | backgroundView.fillColor = color 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/LeftViewController/LeftViewCellBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftViewCellBackgroundView.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private func rad(_ deg: CGFloat) -> CGFloat { 10 | return (deg - 90.0) * .pi / 180.0 11 | } 12 | 13 | class LeftViewCellBackgroundView: UIView { 14 | 15 | var isFirstCell: Bool = false { 16 | didSet { 17 | setNeedsDisplay() 18 | } 19 | } 20 | 21 | var isLastCell: Bool = false { 22 | didSet { 23 | setNeedsDisplay() 24 | } 25 | } 26 | 27 | var fillColor: UIColor = .clear { 28 | didSet { 29 | setNeedsDisplay() 30 | } 31 | } 32 | 33 | init() { 34 | super.init(frame: .zero) 35 | backgroundColor = .clear 36 | } 37 | 38 | required init?(coder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | override func draw(_ rect: CGRect) { 43 | super.draw(rect) 44 | 45 | guard let context = UIGraphicsGetCurrentContext() else { return } 46 | 47 | context.clear(rect) 48 | context.setFillColor(fillColor.cgColor) 49 | 50 | var drawingRect = getDrawingRect(from: rect) 51 | 52 | do { 53 | drawingRect.origin.y += 1.0 54 | drawingRect.size.height -= 2.0 55 | drawingRect.size.width -= 1.0 56 | 57 | let path = getPath(inside: drawingRect) 58 | 59 | path.close() 60 | context.beginPath() 61 | context.addPath(path.cgPath) 62 | context.fillPath() 63 | } 64 | } 65 | 66 | private func getDrawingRect(from rect: CGRect) -> CGRect { 67 | var result = CGRect(origin: CGPoint(x: 0.0, y: 0.0), 68 | size: CGSize(width: rect.width - 1.0, height: rect.height)) 69 | if isFirstCell { 70 | result.origin.y += 1.0 71 | result.size.height -= 1.0 72 | } 73 | if isLastCell { 74 | result.size.height -= 1.0 75 | } 76 | return result 77 | } 78 | 79 | private func getPath(inside rect: CGRect) -> UIBezierPath { 80 | let cornerRadius: CGFloat = 16.0 81 | let path = UIBezierPath() 82 | 83 | path.move(to: CGPoint(x: rect.minX, y: rect.minY)) 84 | path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) 85 | 86 | if isFirstCell { 87 | path.addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), 88 | radius: cornerRadius, 89 | startAngle: rad(0.0), 90 | endAngle: rad(90.0), 91 | clockwise: true) 92 | } 93 | else { 94 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) 95 | } 96 | 97 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) 98 | 99 | if isLastCell { 100 | path.addArc(withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), 101 | radius: cornerRadius, 102 | startAngle: rad(90.0), 103 | endAngle: rad(180.0), 104 | clockwise: true) 105 | } 106 | else { 107 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) 108 | } 109 | 110 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) 111 | 112 | return path 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/LeftViewController/LeftViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftViewController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let cellIdentifier = "cell" 10 | private let tableViewInset: CGFloat = 44.0 * 2.0 11 | private let cellHeight: CGFloat = 44.0 12 | 13 | class LeftViewController: UITableViewController { 14 | 15 | private var type: DemoType? 16 | 17 | private let sections: [[SideViewCellItem]] = [ 18 | [.close, .openRight], 19 | [.changeRootVC], 20 | [.pushVC(title: "Profile"), 21 | .pushVC(title: "News"), 22 | .pushVC(title: "Friends"), 23 | .pushVC(title: "Music"), 24 | .pushVC(title: "Video"), 25 | .pushVC(title: "Articles")] 26 | ] 27 | 28 | // For NonStoryboard Demo 29 | init(type: DemoType) { 30 | self.type = type 31 | super.init(style: .grouped) 32 | } 33 | 34 | required init?(coder: NSCoder) { 35 | super.init(coder: coder) 36 | } 37 | 38 | // For Storyboard Demo 39 | func setup(type: DemoType) { 40 | self.type = type 41 | } 42 | 43 | // MARK: - Lifecycle - 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | struct Counter { static var count = 0 } 48 | Counter.count += 1 49 | print("LeftViewController.viewDidLoad(), counter: \(Counter.count)") 50 | 51 | // For iOS < 11.0 use this property to avoid artefacts if necessary 52 | // automaticallyAdjustsScrollViewInsets = false 53 | 54 | tableView.register(LeftViewCell.self, forCellReuseIdentifier: cellIdentifier) 55 | tableView.separatorStyle = .none 56 | tableView.contentInset = UIEdgeInsets(top: tableViewInset, left: 0.0, bottom: tableViewInset, right: 0.0) 57 | tableView.showsVerticalScrollIndicator = false 58 | tableView.backgroundColor = .clear 59 | tableView.contentInsetAdjustmentBehavior = .never 60 | tableView.contentOffset = CGPoint(x: 0.0, y: -tableViewInset) 61 | } 62 | 63 | // MARK: - Status Bar - 64 | 65 | override var prefersStatusBarHidden: Bool { 66 | switch type?.demoRow { 67 | case .statusBarHidden, 68 | .statusBarOnlyRoot: 69 | return true 70 | default: 71 | return false 72 | } 73 | } 74 | 75 | override var preferredStatusBarStyle: UIStatusBarStyle { 76 | switch type?.demoRow { 77 | case .statusBarDifferentStyles: 78 | if isLightTheme() { 79 | return .lightContent 80 | } else { 81 | if #available(iOS 13.0, *) { 82 | return .darkContent 83 | } else { 84 | return .default 85 | } 86 | } 87 | default: 88 | return .default 89 | } 90 | } 91 | 92 | override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 93 | return .fade 94 | } 95 | 96 | // MARK: - UITableViewDataSource - 97 | 98 | override func numberOfSections(in tableView: UITableView) -> Int { 99 | return sections.count 100 | } 101 | 102 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 103 | return sections[section].count 104 | } 105 | 106 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 107 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! LeftViewCell 108 | let item = sections[indexPath.section][indexPath.row] 109 | 110 | cell.textLabel!.text = item.description 111 | cell.isFirst = (indexPath.row == 0) 112 | cell.isLast = (indexPath.row == sections[indexPath.section].count - 1) 113 | cell.isFillColorInverted = sideMenuController?.leftViewPresentationStyle == .slideAboveBlurred 114 | 115 | return cell 116 | } 117 | 118 | // MARK: - UITableViewDelegate - 119 | 120 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 121 | guard let sideMenuController = sideMenuController else { return } 122 | let item = sections[indexPath.section][indexPath.row] 123 | 124 | func getNavigationController() -> UINavigationController { 125 | if type?.demoRow == .usageInsideNavigationController { 126 | return sideMenuController.parent as! RootNavigationController 127 | } 128 | else if type?.demoRow == .usageAsContainerForTabBarController { 129 | let tabBarController = sideMenuController.rootViewController as! RootTabBarController 130 | return tabBarController.selectedViewController as! RootNavigationController 131 | } 132 | else { 133 | return sideMenuController.rootViewController as! RootNavigationController 134 | } 135 | } 136 | 137 | switch item { 138 | case .close: 139 | sideMenuController.hideLeftView(animated: true) 140 | case .openRight: 141 | sideMenuController.showRightView(animated: true) 142 | case .changeRootVC: 143 | let viewController = RootViewController(imageName: getBackgroundImageNameRandom()) 144 | if type?.demoRow == .usageInsideNavigationController { 145 | sideMenuController.rootViewController = viewController 146 | UIView.transition(with: sideMenuController.rootViewWrapperView!, 147 | duration: sideMenuController.leftViewAnimationDuration, 148 | options: [.transitionCrossDissolve], 149 | animations: nil) 150 | } 151 | else { 152 | let navigationController = getNavigationController() 153 | navigationController.setViewControllers([viewController], animated: false) 154 | UIView.transition(with: navigationController.view, 155 | duration: sideMenuController.leftViewAnimationDuration, 156 | options: [.transitionCrossDissolve], 157 | animations: nil) 158 | } 159 | sideMenuController.hideLeftView(animated: true) 160 | case .pushVC(let title): 161 | let viewController = RootViewControllerWithTextLabel(title: title, 162 | imageName: getBackgroundImageNameRandom()) 163 | if type?.demoRow == .usageInsideNavigationController { 164 | sideMenuController.rootViewController = viewController 165 | UIView.transition(with: sideMenuController.rootViewWrapperView!, 166 | duration: sideMenuController.leftViewAnimationDuration, 167 | options: [.transitionCrossDissolve], 168 | animations: nil) 169 | } 170 | else { 171 | let navigationController = getNavigationController() 172 | navigationController.pushViewController(viewController, animated: true) 173 | } 174 | sideMenuController.hideLeftView(animated: true) 175 | default: 176 | return 177 | } 178 | } 179 | 180 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 181 | return cellHeight 182 | } 183 | 184 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 185 | if section == 0 { 186 | return 0.0 187 | } 188 | return cellHeight / 2.0 189 | } 190 | 191 | override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 192 | return 0.0 193 | } 194 | 195 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 196 | return nil 197 | } 198 | 199 | override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 200 | return nil 201 | } 202 | 203 | // MARK: - Logging - 204 | 205 | deinit { 206 | struct Counter { static var count = 0 } 207 | Counter.count += 1 208 | print("LeftViewController.deinit(), counter: \(Counter.count)") 209 | } 210 | 211 | override func viewWillAppear(_ animated: Bool) { 212 | super.viewWillAppear(animated) 213 | struct Counter { static var count = 0 } 214 | Counter.count += 1 215 | print("LeftViewController.viewWillAppear(\(animated)), counter: \(Counter.count)") 216 | } 217 | 218 | override func viewDidAppear(_ animated: Bool) { 219 | super.viewDidAppear(animated) 220 | struct Counter { static var count = 0 } 221 | Counter.count += 1 222 | print("LeftViewController.viewDidAppear(\(animated)), counter: \(Counter.count)") 223 | } 224 | 225 | override func viewWillDisappear(_ animated: Bool) { 226 | super.viewWillDisappear(animated) 227 | struct Counter { static var count = 0 } 228 | Counter.count += 1 229 | print("LeftViewController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 230 | } 231 | 232 | override func viewDidDisappear(_ animated: Bool) { 233 | super.viewDidDisappear(animated) 234 | struct Counter { static var count = 0 } 235 | Counter.count += 1 236 | print("LeftViewController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 237 | } 238 | 239 | override func viewWillLayoutSubviews() { 240 | super.viewWillLayoutSubviews() 241 | struct Counter { static var count = 0 } 242 | Counter.count += 1 243 | print("LeftViewController.viewWillLayoutSubviews(), counter: \(Counter.count)") 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RightViewController/RightViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RightViewCell.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let cornerRadius: CGFloat = 16.0 10 | private let fillColorNormal: UIColor = .clear 11 | private let fillColorHighlighted = UIColor(white: 1.0, alpha: 0.3) 12 | private let textColorNormal: UIColor = .white 13 | private let textColorHighlighted: UIColor = .black 14 | 15 | class RightViewCell: UITableViewCell { 16 | 17 | var separatorView: UIView 18 | 19 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 20 | separatorView = UIView() 21 | 22 | super.init(style: style, reuseIdentifier: reuseIdentifier) 23 | 24 | selectionStyle = .none 25 | backgroundColor = .clear 26 | 27 | backgroundView = UIView() 28 | backgroundView!.backgroundColor = fillColorNormal 29 | backgroundView!.layer.masksToBounds = true 30 | backgroundView!.layer.cornerRadius = cornerRadius 31 | 32 | textLabel!.textAlignment = .center 33 | textLabel!.numberOfLines = 0 34 | textLabel!.lineBreakMode = .byWordWrapping 35 | textLabel!.textColor = textColorNormal 36 | 37 | separatorView.backgroundColor = UIColor.white.withAlphaComponent(0.3) 38 | addSubview(self.separatorView) 39 | } 40 | 41 | required init?(coder: NSCoder) { 42 | fatalError("init(coder:) has not been implemented") 43 | } 44 | 45 | override func layoutSubviews() { 46 | super.layoutSubviews() 47 | 48 | let backgroundOffsetX: CGFloat = 8.0 49 | 50 | backgroundView!.frame = { 51 | let offsetY: CGFloat = 8.0 52 | return CGRect(x: backgroundOffsetX, 53 | y: offsetY, 54 | width: bounds.width - backgroundOffsetX + cornerRadius, 55 | height: bounds.height - (offsetY * 2.0)) 56 | }() 57 | 58 | textLabel!.frame = { 59 | let offsetX: CGFloat = 8.0 60 | return CGRect(x: backgroundOffsetX + offsetX, 61 | y: 0.0, 62 | width: bounds.width - backgroundOffsetX - (offsetX * 2.0), 63 | height: bounds.height) 64 | }() 65 | 66 | separatorView.frame = CGRect(x: 16.0, 67 | y: bounds.height - 1.0, 68 | width: bounds.width - 16.0, 69 | height: 1.0) 70 | } 71 | 72 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 73 | super.setHighlighted(highlighted, animated: animated) 74 | backgroundView!.backgroundColor = highlighted ? fillColorHighlighted : fillColorNormal 75 | textLabel!.textColor = highlighted ? textColorHighlighted : textColorNormal 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RightViewController/RightViewCellHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RightViewCellHeaderView.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class RightViewCellHeaderView: UIView { 10 | 11 | init() { 12 | super.init(frame: .zero) 13 | backgroundColor = .clear 14 | } 15 | 16 | required init?(coder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | override func draw(_ rect: CGRect) { 21 | super.draw(rect) 22 | 23 | guard let context = UIGraphicsGetCurrentContext() else { return } 24 | 25 | context.clear(rect) 26 | context.setFillColor(UIColor(white: 1.0, alpha: 0.3).cgColor) 27 | 28 | let offset: CGFloat = 16.0 29 | let height: CGFloat = 4.0 30 | 31 | context.fill(CGRect(x: offset, 32 | y: rect.height / 2.0 - height / 2.0, 33 | width: rect.width - offset, 34 | height: height)) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RightViewController/RightViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RightViewController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let cellIdentifier = "cell" 10 | private let tableViewInset: CGFloat = 44.0 * 2.0 11 | private let cellHeight: CGFloat = 100.0 12 | 13 | class RightViewController: UITableViewController { 14 | 15 | private var type: DemoType? 16 | 17 | private let sections: [[SideViewCellItem]] = [ 18 | [.close, .openLeft], 19 | [.changeRootVC], 20 | [.pushVC(title: "1"), 21 | .pushVC(title: "2"), 22 | .pushVC(title: "3"), 23 | .pushVC(title: "4"), 24 | .pushVC(title: "5"), 25 | .pushVC(title: "6"), 26 | .pushVC(title: "7"), 27 | .pushVC(title: "8"), 28 | .pushVC(title: "9"), 29 | .pushVC(title: "10")] 30 | ] 31 | 32 | init(type: DemoType) { 33 | self.type = type 34 | super.init(style: .grouped) 35 | } 36 | 37 | required init?(coder: NSCoder) { 38 | super.init(coder: coder) 39 | } 40 | 41 | // For Storyboard Demo 42 | func setup(type: DemoType) { 43 | self.type = type 44 | } 45 | 46 | // MARK: - Lifecycle - 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | struct Counter { static var count = 0 } 51 | Counter.count += 1 52 | print("RightViewController.viewDidLoad(), counter: \(Counter.count)") 53 | 54 | // For iOS < 11.0 use this property to avoid artefacts if necessary 55 | // automaticallyAdjustsScrollViewInsets = false 56 | 57 | tableView.register(RightViewCell.self, forCellReuseIdentifier: cellIdentifier) 58 | tableView.separatorStyle = .none 59 | tableView.contentInset = UIEdgeInsets(top: tableViewInset, left: 0.0, bottom: tableViewInset, right: 0.0) 60 | tableView.showsVerticalScrollIndicator = false 61 | tableView.backgroundColor = .clear 62 | tableView.contentInsetAdjustmentBehavior = .never 63 | tableView.contentOffset = CGPoint(x: 0.0, y: -tableViewInset) 64 | } 65 | 66 | // MARK: - Status Bar - 67 | 68 | override var prefersStatusBarHidden: Bool { 69 | switch type?.demoRow { 70 | case .statusBarHidden, 71 | .statusBarOnlyRoot: 72 | return true 73 | default: 74 | return false 75 | } 76 | } 77 | 78 | override var preferredStatusBarStyle: UIStatusBarStyle { 79 | guard let type = type else { return .default } 80 | 81 | switch type.demoRow { 82 | case .statusBarDifferentStyles: 83 | if isLightTheme() { 84 | return .lightContent 85 | } else { 86 | if #available(iOS 13.0, *) { 87 | return .darkContent 88 | } else { 89 | return .default 90 | } 91 | } 92 | case .statusBarNoBackground: 93 | if type.presentationStyle.shouldRootViewScale { 94 | return .lightContent 95 | } 96 | fallthrough 97 | default: 98 | return .default 99 | } 100 | } 101 | 102 | override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 103 | return .slide 104 | } 105 | 106 | // MARK: - UITableViewDataSource - 107 | 108 | override func numberOfSections(in tableView: UITableView) -> Int { 109 | return sections.count 110 | } 111 | 112 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 113 | return sections[section].count 114 | } 115 | 116 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 117 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! RightViewCell 118 | let item = sections[indexPath.section][indexPath.row] 119 | 120 | cell.textLabel!.text = item.description 121 | cell.textLabel!.font = UIFont.boldSystemFont(ofSize: { 122 | if case .pushVC = item { 123 | return 32.0 124 | } 125 | return 16.0 126 | }()) 127 | cell.separatorView.isHidden = (indexPath.row == sections[indexPath.section].count - 1) 128 | 129 | return cell 130 | } 131 | 132 | // MARK: - UITableViewDelegate - 133 | 134 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 135 | guard let sideMenuController = self.sideMenuController else { return } 136 | let item = sections[indexPath.section][indexPath.row] 137 | 138 | func getNavigationController() -> UINavigationController { 139 | if type?.demoRow == .usageInsideNavigationController { 140 | return sideMenuController.parent as! RootNavigationController 141 | } 142 | else if type?.demoRow == .usageAsContainerForTabBarController { 143 | let tabBarController = sideMenuController.rootViewController as! RootTabBarController 144 | return tabBarController.selectedViewController as! RootNavigationController 145 | } 146 | else { 147 | return sideMenuController.rootViewController as! RootNavigationController 148 | } 149 | } 150 | 151 | switch item { 152 | case .close: 153 | sideMenuController.hideRightView(animated: true) 154 | case .openLeft: 155 | sideMenuController.showLeftView(animated: true) 156 | case .changeRootVC: 157 | let viewController = RootViewController(imageName: getBackgroundImageNameRandom()) 158 | if type?.demoRow == .usageInsideNavigationController { 159 | sideMenuController.rootViewController = viewController 160 | UIView.transition(with: sideMenuController.rootViewWrapperView!, 161 | duration: sideMenuController.rightViewAnimationDuration, 162 | options: [.transitionCrossDissolve], 163 | animations: nil) 164 | } 165 | else { 166 | let navigationController = getNavigationController() 167 | navigationController.setViewControllers([viewController], animated: false) 168 | UIView.transition(with: navigationController.view, 169 | duration: sideMenuController.rightViewAnimationDuration, 170 | options: [.transitionCrossDissolve], 171 | animations: nil) 172 | } 173 | sideMenuController.hideRightView(animated: true) 174 | case .pushVC(let title): 175 | let viewController = RootViewControllerWithTextLabel(title: title, 176 | imageName: getBackgroundImageNameRandom()) 177 | if type?.demoRow == .usageInsideNavigationController { 178 | sideMenuController.rootViewController = viewController 179 | UIView.transition(with: sideMenuController.rootViewWrapperView!, 180 | duration: sideMenuController.rightViewAnimationDuration, 181 | options: [.transitionCrossDissolve], 182 | animations: nil) 183 | } 184 | else { 185 | let navigationController = getNavigationController() 186 | navigationController.pushViewController(viewController, animated: true) 187 | } 188 | sideMenuController.hideRightView(animated: true) 189 | default: 190 | return 191 | } 192 | } 193 | 194 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 195 | let item = sections[indexPath.section][indexPath.row] 196 | if case .pushVC = item { 197 | return cellHeight 198 | } 199 | return cellHeight * 0.75 200 | } 201 | 202 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 203 | if section == 0 { 204 | return 0.0 205 | } 206 | return cellHeight * 0.75 207 | } 208 | 209 | override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 210 | return 0.0 211 | } 212 | 213 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 214 | if section == 0 { 215 | return nil 216 | } 217 | return RightViewCellHeaderView() 218 | } 219 | 220 | override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 221 | return nil 222 | } 223 | 224 | // MARK: - Logging - 225 | 226 | deinit { 227 | struct Counter { static var count = 0 } 228 | Counter.count += 1 229 | print("RightViewController.deinit(), counter: \(Counter.count)") 230 | } 231 | 232 | override func viewWillAppear(_ animated: Bool) { 233 | super.viewWillAppear(animated) 234 | struct Counter { static var count = 0 } 235 | Counter.count += 1 236 | print("RightViewController.viewWillAppear(\(animated)), counter: \(Counter.count)") 237 | } 238 | 239 | override func viewDidAppear(_ animated: Bool) { 240 | super.viewDidAppear(animated) 241 | struct Counter { static var count = 0 } 242 | Counter.count += 1 243 | print("RightViewController.viewDidAppear(\(animated)), counter: \(Counter.count)") 244 | } 245 | 246 | override func viewWillDisappear(_ animated: Bool) { 247 | super.viewWillDisappear(animated) 248 | struct Counter { static var count = 0 } 249 | Counter.count += 1 250 | print("RightViewController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 251 | } 252 | 253 | override func viewDidDisappear(_ animated: Bool) { 254 | super.viewDidDisappear(animated) 255 | struct Counter { static var count = 0 } 256 | Counter.count += 1 257 | print("RightViewController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 258 | } 259 | 260 | override func viewWillLayoutSubviews() { 261 | super.viewWillLayoutSubviews() 262 | struct Counter { static var count = 0 } 263 | Counter.count += 1 264 | print("RightViewController.viewWillLayoutSubviews(), counter: \(Counter.count)") 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootNavigationController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class RootNavigationController: UINavigationController { 10 | 11 | private var type: DemoType? 12 | 13 | init(type: DemoType) { 14 | super.init(nibName: nil, bundle: nil) 15 | setup(type: type) 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | } 21 | 22 | // For Storyboard Demo 23 | func setup(type: DemoType) { 24 | self.type = type 25 | } 26 | 27 | // MARK: - Lifecycle - 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | struct Counter { static var count = 0 } 32 | Counter.count += 1 33 | print("RootNavigationController.viewDidLoad(), counter: \(Counter.count)") 34 | 35 | setColors() 36 | } 37 | 38 | // MARK: - Rotation - 39 | 40 | override var shouldAutorotate : Bool { 41 | return true 42 | } 43 | 44 | // MARK: - Status Bar - 45 | 46 | override var prefersStatusBarHidden : Bool { 47 | switch type?.demoRow { 48 | case .statusBarHidden, 49 | .statusBarOnlySide: 50 | return true 51 | default: 52 | return false 53 | } 54 | } 55 | 56 | override var preferredStatusBarStyle : UIStatusBarStyle { 57 | return .default 58 | } 59 | 60 | override var preferredStatusBarUpdateAnimation : UIStatusBarAnimation { 61 | return .fade 62 | } 63 | 64 | // MARK: - Theme - 65 | 66 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 67 | super.traitCollectionDidChange(previousTraitCollection) 68 | setColors() 69 | } 70 | 71 | // MARK: - Other - 72 | 73 | func setColors() { 74 | navigationBar.barTintColor = UIColor(white: (isLightTheme() ? 1.0 : 0.0), alpha: 0.9) 75 | navigationBar.titleTextAttributes = [.foregroundColor: (isLightTheme() ? UIColor.black : UIColor.white)] 76 | } 77 | 78 | // MARK: - Logging - 79 | 80 | deinit { 81 | struct Counter { static var count = 0 } 82 | Counter.count += 1 83 | print("RootNavigationController.deinit(), counter: \(Counter.count)") 84 | } 85 | 86 | override func viewWillAppear(_ animated: Bool) { 87 | super.viewWillAppear(animated) 88 | struct Counter { static var count = 0 } 89 | Counter.count += 1 90 | print("RootNavigationController.viewWillAppear(\(animated)), counter: \(Counter.count)") 91 | } 92 | 93 | override func viewDidAppear(_ animated: Bool) { 94 | super.viewDidAppear(animated) 95 | struct Counter { static var count = 0 } 96 | Counter.count += 1 97 | print("RootNavigationController.viewDidAppear(\(animated)), counter: \(Counter.count)") 98 | } 99 | 100 | override func viewWillDisappear(_ animated: Bool) { 101 | super.viewWillDisappear(animated) 102 | struct Counter { static var count = 0 } 103 | Counter.count += 1 104 | print("RootNavigationController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 105 | } 106 | 107 | override func viewDidDisappear(_ animated: Bool) { 108 | super.viewDidDisappear(animated) 109 | struct Counter { static var count = 0 } 110 | Counter.count += 1 111 | print("RootNavigationController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 112 | } 113 | 114 | override func viewWillLayoutSubviews() { 115 | super.viewWillLayoutSubviews() 116 | struct Counter { static var count = 0 } 117 | Counter.count += 1 118 | print("RootNavigationController.viewWillLayoutSubviews(), counter: \(Counter.count)") 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootTabBarController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class RootTabBarController: UITabBarController { 10 | 11 | private var type: DemoType? 12 | 13 | init(type: DemoType) { 14 | super.init(nibName: nil, bundle: nil) 15 | setup(type: type) 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | } 21 | 22 | // For Storyboard Demo 23 | func setup(type: DemoType) { 24 | self.type = type 25 | } 26 | 27 | // MARK: - Lifecycle - 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | struct Counter { static var count = 0 } 32 | Counter.count += 1 33 | print("RootNavigationController.viewDidLoad(), counter: \(Counter.count)") 34 | 35 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .normal) 36 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .selected) 37 | 38 | setColors() 39 | } 40 | 41 | // override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 42 | // super.willTransition(to: newCollection, with: coordinator) 43 | // 44 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .normal) 45 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .selected) 46 | // } 47 | // 48 | // override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 49 | // super.viewWillTransition(to: size, with: coordinator) 50 | // 51 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .normal) 52 | // UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: tabBar.bounds.height)], for: .selected) 53 | // } 54 | 55 | // MARK: - Rotation - 56 | 57 | override var shouldAutorotate : Bool { 58 | return true 59 | } 60 | 61 | // MARK: - Status Bar - 62 | 63 | override var prefersStatusBarHidden : Bool { 64 | return false 65 | } 66 | 67 | override var preferredStatusBarStyle : UIStatusBarStyle { 68 | return .default 69 | } 70 | 71 | override var preferredStatusBarUpdateAnimation : UIStatusBarAnimation { 72 | return .fade 73 | } 74 | 75 | // MARK: - Theme - 76 | 77 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 78 | super.traitCollectionDidChange(previousTraitCollection) 79 | setColors() 80 | } 81 | 82 | // MARK: - Other - 83 | 84 | func setColors() { 85 | // navigationBar.barTintColor = UIColor(white: (isLightTheme() ? 1.0 : 0.0), alpha: 0.9) 86 | // navigationBar.titleTextAttributes = [.foregroundColor: (isLightTheme() ? UIColor.black : UIColor.white)] 87 | } 88 | 89 | // MARK: - Logging - 90 | 91 | deinit { 92 | struct Counter { static var count = 0 } 93 | Counter.count += 1 94 | print("RootNavigationController.deinit(), counter: \(Counter.count)") 95 | } 96 | 97 | override func viewWillAppear(_ animated: Bool) { 98 | super.viewWillAppear(animated) 99 | struct Counter { static var count = 0 } 100 | Counter.count += 1 101 | print("RootNavigationController.viewWillAppear(\(animated)), counter: \(Counter.count)") 102 | } 103 | 104 | override func viewDidAppear(_ animated: Bool) { 105 | super.viewDidAppear(animated) 106 | struct Counter { static var count = 0 } 107 | Counter.count += 1 108 | print("RootNavigationController.viewDidAppear(\(animated)), counter: \(Counter.count)") 109 | } 110 | 111 | override func viewWillDisappear(_ animated: Bool) { 112 | super.viewWillDisappear(animated) 113 | struct Counter { static var count = 0 } 114 | Counter.count += 1 115 | print("RootNavigationController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 116 | } 117 | 118 | override func viewDidDisappear(_ animated: Bool) { 119 | super.viewDidDisappear(animated) 120 | struct Counter { static var count = 0 } 121 | Counter.count += 1 122 | print("RootNavigationController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 123 | } 124 | 125 | override func viewWillLayoutSubviews() { 126 | super.viewWillLayoutSubviews() 127 | struct Counter { static var count = 0 } 128 | Counter.count += 1 129 | print("RootNavigationController.viewWillLayoutSubviews(), counter: \(Counter.count)") 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let titleDefault = "LGSideMenuController" 10 | private let showSideButtonsDefault = true 11 | private let buttonHeight: CGFloat = 44.0 12 | 13 | class RootViewController : UIViewController { 14 | 15 | var demoDescriptionLabel: UILabel? = nil 16 | var demoDescriptionBackgroundView: UIVisualEffectView? = nil 17 | 18 | var showSideButtons: Bool = showSideButtonsDefault 19 | 20 | var imageView = UIImageView() 21 | 22 | let button = UIButton() 23 | let buttonBackground = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) 24 | 25 | init(title: String = titleDefault, 26 | description: String? = nil, 27 | showSideButtons: Bool = showSideButtonsDefault, 28 | imageName: String? = nil) { 29 | super.init(nibName: nil, bundle: nil) 30 | 31 | self.title = title 32 | 33 | if let description = description, !description.isEmpty { 34 | demoDescriptionLabel = UILabel() 35 | demoDescriptionLabel!.text = description 36 | demoDescriptionBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) 37 | } 38 | 39 | self.showSideButtons = showSideButtons 40 | 41 | imageView.image = UIImage(named: imageName ?? getBackgroundImageNameDefault()) 42 | } 43 | 44 | required init?(coder: NSCoder) { 45 | super.init(coder: coder) 46 | } 47 | 48 | // MARK: - Lifecycle - 49 | 50 | override func viewDidLoad() { 51 | super.viewDidLoad() 52 | struct Counter { static var count = 0 } 53 | Counter.count += 1 54 | print("RootViewController.viewDidLoad(), counter: \(Counter.count)") 55 | 56 | view.clipsToBounds = true 57 | 58 | if showSideButtons { 59 | navigationItem.leftBarButtonItem = UIBarButtonItem(image: menuIconImage, style: .plain, target: self, action: #selector(showLeftView(sender:))) 60 | navigationItem.rightBarButtonItem = UIBarButtonItem(image: menuIconImage, style: .plain, target: self, action: #selector(showRightView(sender:))) 61 | } 62 | 63 | imageView.contentMode = .scaleAspectFill 64 | view.addSubview(imageView) 65 | 66 | if let label = demoDescriptionLabel, 67 | let backgroundView = demoDescriptionBackgroundView { 68 | view.addSubview(backgroundView) 69 | 70 | label.font = UIFont.boldSystemFont(ofSize: 12.0) 71 | label.textAlignment = .left 72 | label.numberOfLines = 0 73 | label.lineBreakMode = .byWordWrapping 74 | label.backgroundColor = .clear 75 | backgroundView.contentView.addSubview(label) 76 | } 77 | 78 | view.addSubview(buttonBackground) 79 | 80 | button.setTitle("Show Choose Controller", for: .normal) 81 | button.setTitleColor(UIColor(red: 0.0, green: 0.5, blue: 1.0, alpha: 1.0), for: .highlighted) 82 | button.addTarget(self, action: #selector(showChooseController), for: .touchUpInside) 83 | button.backgroundColor = .clear 84 | buttonBackground.contentView.addSubview(button) 85 | 86 | setColors() 87 | } 88 | 89 | override func viewWillAppear(_ animated: Bool) { 90 | super.viewWillAppear(animated) 91 | struct Counter { static var count = 0 } 92 | Counter.count += 1 93 | print("RootViewController.viewWillAppear(\(animated)), counter: \(Counter.count)") 94 | 95 | if self.sideMenuController?.isLeftViewSwipeGestureEnabled ?? false { 96 | // If you want left view be available by swipe you need to disable navigation controller pop gesture 97 | navigationController!.interactivePopGestureRecognizer!.isEnabled = false 98 | } 99 | } 100 | 101 | override func viewWillLayoutSubviews() { 102 | super.viewWillLayoutSubviews() 103 | struct Counter { static var count = 0 } 104 | Counter.count += 1 105 | print("RootViewController.viewWillLayoutSubviews(), counter: \(Counter.count)") 106 | 107 | let safeAreaLayoutFrame = view.safeAreaLayoutGuide.layoutFrame 108 | 109 | imageView.frame = CGRect(x: 0.0, y: 0.0, width: view.frame.size.width, height: view.frame.size.height) 110 | 111 | if let label = demoDescriptionLabel, 112 | let backgroundView = demoDescriptionBackgroundView { 113 | let labelOffset: CGFloat = 8.0 114 | 115 | let labelSize = label.sizeThatFits(CGSize(width: view.bounds.width - (labelOffset * 2.0), 116 | height: CGFloat.greatestFiniteMagnitude)) 117 | 118 | backgroundView.frame = CGRect(x: 0.0, 119 | y: 0.0, 120 | width: view.bounds.width, 121 | height: labelSize.height + (labelOffset * 2.0) + safeAreaLayoutFrame.minY) 122 | 123 | label.frame = { 124 | let labelOriginY = backgroundView.bounds.height - labelSize.height - labelOffset * 2.0 125 | return CGRect(x: labelOffset, 126 | y: labelOriginY, 127 | width: backgroundView.bounds.width - labelOffset * 2.0, 128 | height: backgroundView.bounds.height - labelOriginY) 129 | }() 130 | } 131 | 132 | buttonBackground.frame = { 133 | let bottomOffset = view.bounds.height - (safeAreaLayoutFrame.minY + safeAreaLayoutFrame.height) 134 | let height = buttonHeight + bottomOffset 135 | return CGRect(x: 0.0, 136 | y: view.frame.size.height - height, 137 | width: view.frame.size.width, 138 | height: height) 139 | }() 140 | 141 | button.frame = CGRect(x: 0.0, y: 0.0, width: buttonBackground.bounds.width, height: buttonHeight) 142 | } 143 | 144 | // MARK: - Theme - 145 | 146 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 147 | super.traitCollectionDidChange(previousTraitCollection) 148 | setColors() 149 | } 150 | 151 | // MARK: - Other - 152 | 153 | @objc 154 | func showLeftView(sender: AnyObject?) { 155 | sideMenuController?.toggleLeftView(animated: true) 156 | } 157 | 158 | @objc 159 | func showRightView(sender: AnyObject?) { 160 | sideMenuController?.toggleRightView(animated: true) 161 | } 162 | 163 | @objc 164 | func showChooseController() { 165 | let storyboard = UIStoryboard(name: "Choose", bundle: nil) 166 | let controller = storyboard.instantiateInitialViewController() as! ChooseNavigationController 167 | let window = UIApplication.shared.delegate!.window!! 168 | 169 | window.rootViewController = controller 170 | 171 | UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: nil, completion: nil) 172 | } 173 | 174 | func setColors() { 175 | button.setTitleColor(isLightTheme() ? .black : .white, for: .normal) 176 | } 177 | 178 | // MARK: - Logging - 179 | 180 | deinit { 181 | struct Counter { static var count = 0 } 182 | Counter.count += 1 183 | print("RootViewController.deinit(), counter: \(Counter.count)") 184 | } 185 | 186 | override func viewDidAppear(_ animated: Bool) { 187 | super.viewDidAppear(animated) 188 | struct Counter { static var count = 0 } 189 | Counter.count += 1 190 | print("RootViewController.viewDidAppear(\(animated)), counter: \(Counter.count)") 191 | } 192 | 193 | override func viewWillDisappear(_ animated: Bool) { 194 | super.viewWillDisappear(animated) 195 | struct Counter { static var count = 0 } 196 | Counter.count += 1 197 | print("RootViewController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 198 | } 199 | 200 | override func viewDidDisappear(_ animated: Bool) { 201 | super.viewDidDisappear(animated) 202 | struct Counter { static var count = 0 } 203 | Counter.count += 1 204 | print("RootViewController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootViewControllerWithScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewControllerWithScrollView.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class RootViewControllerWithScrollView: RootViewController { 10 | 11 | private let scrollView = UIScrollView() 12 | 13 | // MARK: - Lifecycle - 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | // For iOS < 11.0 use this property to avoid artefacts if necessary 19 | // automaticallyAdjustsScrollViewInsets = false 20 | 21 | scrollView.contentInsetAdjustmentBehavior = .never 22 | view.insertSubview(scrollView, at: 0) 23 | 24 | imageView.clipsToBounds = true 25 | imageView.removeFromSuperview() 26 | scrollView.addSubview(imageView) 27 | } 28 | 29 | override func viewDidAppear(_ animated: Bool) { 30 | super.viewDidAppear(animated) 31 | scrollView.contentOffset = CGPoint(x: (view.bounds.width * 5.0 / 2.0) - (view.bounds.width / 2.0), 32 | y: (view.bounds.height * 3.0 / 2.0) - (view.bounds.height / 2.0)) 33 | } 34 | 35 | override func viewWillLayoutSubviews() { 36 | super.viewWillLayoutSubviews() 37 | 38 | scrollView.frame = view.bounds 39 | scrollView.contentInset = .zero 40 | scrollView.scrollIndicatorInsets = { 41 | let topInset = demoDescriptionBackgroundView != nil ? 42 | demoDescriptionBackgroundView!.frame.maxY - view.safeAreaLayoutGuide.layoutFrame.minY : 0.0 43 | return UIEdgeInsets(top: topInset, left: 0.0, bottom: buttonBackground.bounds.height, right: 0.0) 44 | }() 45 | 46 | imageView.frame = CGRect(origin: .zero, 47 | size: CGSize(width: scrollView.frame.width * 5.0, 48 | height: scrollView.frame.height * 3.0)) 49 | scrollView.contentSize = imageView.frame.size 50 | } 51 | 52 | // MARK: - Other - 53 | 54 | override func setColors() { 55 | super.setColors() 56 | scrollView.backgroundColor = .gray 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootViewControllerWithTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewControllerWithTableView.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | private let cellIdentifier = "cell" 10 | 11 | class RootViewControllerWithTableView: RootViewController, UITableViewDelegate, UITableViewDataSource { 12 | 13 | private let tableView = UITableView() 14 | private var rowNumbers = Array(1...100) 15 | 16 | private let tableViewBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) 17 | 18 | // MARK: - Lifecycle - 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | // For iOS < 11.0 use this property to avoid artefacts if necessary 24 | // automaticallyAdjustsScrollViewInsets = false 25 | 26 | view.insertSubview(tableViewBackgroundView, aboveSubview: imageView) 27 | 28 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier) 29 | tableView.delegate = self 30 | tableView.dataSource = self 31 | tableView.contentInsetAdjustmentBehavior = .never 32 | tableView.backgroundColor = .clear 33 | view.insertSubview(tableView, aboveSubview: tableViewBackgroundView) 34 | } 35 | 36 | override func viewDidAppear(_ animated: Bool) { 37 | super.viewDidAppear(animated) 38 | tableView.contentOffset = { 39 | let safeAreaLayoutFrame = view.safeAreaLayoutGuide.layoutFrame 40 | let topOffset = demoDescriptionBackgroundView?.frame.maxY ?? safeAreaLayoutFrame.minY 41 | return CGPoint(x: 0.0, y: -topOffset) 42 | }() 43 | } 44 | 45 | override func viewWillLayoutSubviews() { 46 | super.viewWillLayoutSubviews() 47 | 48 | tableViewBackgroundView.frame = view.bounds 49 | 50 | let safeAreaLayoutFrame = view.safeAreaLayoutGuide.layoutFrame 51 | 52 | tableView.frame = view.bounds 53 | tableView.contentInset = { 54 | let topInset = demoDescriptionBackgroundView?.frame.maxY ?? safeAreaLayoutFrame.minY 55 | return UIEdgeInsets(top: topInset, left: 0.0, bottom: buttonBackground.bounds.height, right: 0.0) 56 | }() 57 | tableView.scrollIndicatorInsets = { 58 | let topInset = demoDescriptionBackgroundView != nil ? 59 | demoDescriptionBackgroundView!.frame.maxY - safeAreaLayoutFrame.minY : 0.0 60 | return UIEdgeInsets(top: topInset, left: 0.0, bottom: buttonBackground.bounds.height, right: 0.0) 61 | }() 62 | } 63 | 64 | // MARK: - UITableViewDataSource - 65 | 66 | func numberOfSections(in tableView: UITableView) -> Int { 67 | return 1 68 | } 69 | 70 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return rowNumbers.count 72 | } 73 | 74 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 75 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) 76 | let rowNumber = rowNumbers[indexPath.row] 77 | 78 | cell.textLabel?.text = "Row number \(rowNumber)" 79 | cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 16.0) 80 | cell.textLabel?.textColor = isLightTheme() ? .black : .white 81 | cell.backgroundColor = .clear 82 | 83 | return cell 84 | } 85 | 86 | // MARK: - UITableViewDelegate - 87 | 88 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 89 | return 44.0 90 | } 91 | 92 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 93 | tableView.deselectRow(at: indexPath, animated: true) 94 | } 95 | 96 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 97 | if (editingStyle == .delete) { 98 | rowNumbers.remove(at: indexPath.row) 99 | tableView.deleteRows(at: [indexPath], with: .automatic) 100 | } 101 | } 102 | 103 | // MARK: - Other - 104 | 105 | override func setColors() { 106 | super.setColors() 107 | tableView.reloadData() 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /Demo/_shared_files/SideMenuController/RootViewController/RootViewControllerWithTextLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewControllerWithTextLabel.swift 3 | // LGSideMenuControllerDemo 4 | // 5 | 6 | import Foundation 7 | import UIKit 8 | 9 | class RootViewControllerWithTextLabel : RootViewController { 10 | 11 | private let textLabel = UILabel() 12 | private let textLabelBackground = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) 13 | 14 | init(title: String, imageName: String? = nil) { 15 | super.init(title: title, showSideButtons: false, imageName: imageName) 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | // MARK: - Lifecycle - 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | struct Counter { static var count = 0 } 27 | Counter.count += 1 28 | print("RootViewController.viewDidLoad(), counter: \(Counter.count)") 29 | 30 | textLabelBackground.layer.masksToBounds = true 31 | view.addSubview(textLabelBackground) 32 | 33 | textLabel.text = title 34 | textLabel.font = UIFont.boldSystemFont(ofSize: 64.0) 35 | textLabel.textAlignment = .center 36 | textLabel.numberOfLines = 1 37 | textLabel.lineBreakMode = .byWordWrapping 38 | textLabel.backgroundColor = .clear 39 | textLabel.adjustsFontSizeToFitWidth = true 40 | textLabel.baselineAdjustment = .alignCenters 41 | view.addSubview(textLabel) 42 | } 43 | 44 | override func viewWillLayoutSubviews() { 45 | super.viewWillLayoutSubviews() 46 | struct Counter { static var count = 0 } 47 | Counter.count += 1 48 | print("RootViewController.viewWillLayoutSubviews(), counter: \(Counter.count)") 49 | 50 | let textLabelOffset: CGFloat = 16.0 51 | textLabel.frame = { 52 | let safeAreaLayoutFrame = view.safeAreaLayoutGuide.layoutFrame 53 | let areaOffsetY = demoDescriptionBackgroundView?.frame.maxY ?? safeAreaLayoutFrame.minY 54 | let areaHeight = view.bounds.height - areaOffsetY - buttonBackground.bounds.height 55 | 56 | let maxWidth = view.bounds.width - (textLabelOffset * 4.0) 57 | var size = textLabel.sizeThatFits(CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)) 58 | size.width = max(size.width, size.height) 59 | size.width = min(size.width, maxWidth) 60 | return CGRect(x: view.bounds.width / 2.0 - size.width / 2.0, 61 | y: areaOffsetY + areaHeight / 2.0 - size.height / 2.0, 62 | width: size.width, 63 | height: size.height) 64 | }() 65 | 66 | if textLabel.bounds.width == textLabel.bounds.height { 67 | textLabelBackground.frame = textLabel.frame.insetBy(dx: -textLabelOffset, dy: -textLabelOffset) 68 | textLabelBackground.layer.cornerRadius = textLabelBackground.bounds.height / 2.0 69 | } 70 | else { 71 | textLabelBackground.frame = textLabel.frame.insetBy(dx: -textLabelOffset * 2.0, dy: -textLabelOffset) 72 | textLabelBackground.layer.cornerRadius = 32.0 73 | } 74 | } 75 | 76 | // MARK: - Other - 77 | 78 | override func setColors() { 79 | super.setColors() 80 | textLabel.textColor = isLightTheme() ? .black : .white 81 | } 82 | 83 | // MARK: - Logging - 84 | 85 | deinit { 86 | struct Counter { static var count = 0 } 87 | Counter.count += 1 88 | print("RootViewController.deinit(), counter: \(Counter.count)") 89 | } 90 | 91 | override func viewWillAppear(_ animated: Bool) { 92 | super.viewWillAppear(animated) 93 | struct Counter { static var count = 0 } 94 | Counter.count += 1 95 | print("RootViewController.viewWillAppear(\(animated)), counter: \(Counter.count)") 96 | } 97 | 98 | override func viewDidAppear(_ animated: Bool) { 99 | super.viewDidAppear(animated) 100 | struct Counter { static var count = 0 } 101 | Counter.count += 1 102 | print("RootViewController.viewDidAppear(\(animated)), counter: \(Counter.count)") 103 | } 104 | 105 | override func viewWillDisappear(_ animated: Bool) { 106 | super.viewWillDisappear(animated) 107 | struct Counter { static var count = 0 } 108 | Counter.count += 1 109 | print("RootViewController.viewWillDisappear(\(animated)), counter: \(Counter.count)") 110 | } 111 | 112 | override func viewDidDisappear(_ animated: Bool) { 113 | super.viewDidDisappear(animated) 114 | struct Counter { static var count = 0 } 115 | Counter.count += 1 116 | print("RootViewController.viewDidDisappear(\(animated)), counter: \(Counter.count)") 117 | } 118 | 119 | } 120 | 121 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework.xcodeproj/xcshareddata/xcschemes/LGSideMenuControllerFramework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework/LGSideMenuControllerFramework.h: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuControllerFramework.h 3 | // LGSideMenuControllerFramework 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | //! Project version number for LGSideMenuController framework. 33 | FOUNDATION_EXPORT double LGSideMenuControllerVersionNumber; 34 | 35 | //! Project version string for LGSideMenuController framework. 36 | FOUNDATION_EXPORT const unsigned char LGSideMenuControllerVersionString[]; 37 | -------------------------------------------------------------------------------- /Framework/LGSideMenuControllerFramework/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module LGSideMenuController { 2 | umbrella header "LGSideMenuControllerFramework.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /LGSideMenuController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'LGSideMenuController' 4 | s.version = '3.1.2' 5 | s.license = { type: 'MIT', file: 'LICENSE' } 6 | s.homepage = 'https://github.com/Friend-LGA/LGSideMenuController' 7 | s.author = { 'Grigorii Lutkov': 'friend.lga@gmail.com' } 8 | s.source = { git: 'https://github.com/Friend-LGA/LGSideMenuController.git', tag: s.version } 9 | s.summary = 'iOS view controller which manages left and right side views' 10 | s.platform = :ios, '9.0' 11 | s.swift_version = '5.0' 12 | s.source_files = 'LGSideMenuController/**/*.swift' 13 | s.framework = 'Foundation', 'CoreGraphics', 'QuartzCore', 'UIKit' 14 | 15 | end 16 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+Callbacks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+Callbacks.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | // MARK: - Root View - 36 | 37 | func didTransformRootViewCallbacks(percentage: CGFloat) { 38 | NotificationCenter.default.post(name: Notification.didTransformRootView, 39 | object: self, 40 | userInfo: [Notification.Key.percentage: percentage]) 41 | 42 | if let callback = self.didTransformRootView { 43 | callback(self, percentage) 44 | } 45 | 46 | if let delegate = self.delegate { 47 | delegate.didTransformRootView(sideMenuController: self, percentage: percentage) 48 | } 49 | } 50 | 51 | // MARK: - Left View - 52 | 53 | func willShowLeftViewCallbacks() { 54 | NotificationCenter.default.post(name: Notification.willShowLeftView, object: self) 55 | 56 | if let willShowLeftView = self.willShowLeftView { 57 | willShowLeftView(self) 58 | } 59 | 60 | if let delegate = self.delegate { 61 | delegate.willShowLeftView(sideMenuController: self) 62 | } 63 | } 64 | 65 | func didShowLeftViewCallbacks() { 66 | NotificationCenter.default.post(name: Notification.didShowLeftView, object: self) 67 | 68 | if let didShowLeftView = self.didShowLeftView { 69 | didShowLeftView(self) 70 | } 71 | 72 | if let delegate = self.delegate { 73 | delegate.didShowLeftView(sideMenuController: self) 74 | } 75 | } 76 | 77 | func willHideLeftViewCallbacks() { 78 | NotificationCenter.default.post(name: Notification.willHideLeftView, object: self) 79 | 80 | if let willHideLeftView = self.willHideLeftView { 81 | willHideLeftView(self) 82 | } 83 | 84 | if let delegate = self.delegate { 85 | delegate.willHideLeftView(sideMenuController: self) 86 | } 87 | } 88 | 89 | func didHideLeftViewCallbacks() { 90 | NotificationCenter.default.post(name: Notification.didHideLeftView, object: self) 91 | 92 | if let didHideLeftView = self.didHideLeftView { 93 | didHideLeftView(self) 94 | } 95 | 96 | if let delegate = self.delegate { 97 | delegate.didHideLeftView(sideMenuController: self) 98 | } 99 | } 100 | 101 | func showAnimationsForLeftViewCallbacks(duration: TimeInterval, timingFunction: CAMediaTimingFunction) { 102 | NotificationCenter.default.post(name: Notification.showAnimationsForLeftView, 103 | object: self, 104 | userInfo: [Notification.Key.duration: duration, 105 | Notification.Key.timigFunction: timingFunction]) 106 | 107 | if let callback = self.showAnimationsForLeftView { 108 | callback(self, duration, timingFunction) 109 | } 110 | 111 | if let delegate = self.delegate { 112 | delegate.showAnimationsForLeftView(sideMenuController: self, 113 | duration: duration, 114 | timingFunction: timingFunction) 115 | } 116 | } 117 | 118 | func hideAnimationsForLeftViewCallbacks(duration: TimeInterval, timingFunction: CAMediaTimingFunction) { 119 | NotificationCenter.default.post(name: Notification.hideAnimationsForLeftView, 120 | object: self, 121 | userInfo: [Notification.Key.duration: duration, 122 | Notification.Key.timigFunction: timingFunction]) 123 | 124 | if let callback = self.hideAnimationsForLeftView { 125 | callback(self, duration, timingFunction) 126 | } 127 | 128 | if let delegate = self.delegate { 129 | delegate.hideAnimationsForLeftView(sideMenuController: self, 130 | duration: duration, 131 | timingFunction: timingFunction) 132 | } 133 | } 134 | 135 | func didTransformLeftViewCallbacks(percentage: CGFloat) { 136 | NotificationCenter.default.post(name: Notification.didTransformLeftView, 137 | object: self, 138 | userInfo: [Notification.Key.percentage: percentage]) 139 | 140 | if let callback = self.didTransformLeftView { 141 | callback(self, percentage) 142 | } 143 | 144 | if let delegate = self.delegate { 145 | delegate.didTransformLeftView(sideMenuController: self, percentage: percentage) 146 | } 147 | } 148 | 149 | // MARK: - Right View - 150 | 151 | func willShowRightViewCallbacks() { 152 | NotificationCenter.default.post(name: Notification.willShowRightView, object: self) 153 | 154 | if let willShowRightView = self.willShowRightView { 155 | willShowRightView(self) 156 | } 157 | 158 | if let delegate = self.delegate { 159 | delegate.willShowRightView(sideMenuController: self) 160 | } 161 | } 162 | 163 | func didShowRightViewCallbacks() { 164 | NotificationCenter.default.post(name: Notification.didShowRightView, object: self) 165 | 166 | if let didShowRightView = self.didShowRightView { 167 | didShowRightView(self) 168 | } 169 | 170 | if let delegate = self.delegate { 171 | delegate.didShowRightView(sideMenuController: self) 172 | } 173 | } 174 | 175 | func willHideRightViewCallbacks() { 176 | NotificationCenter.default.post(name: Notification.willHideRightView, object: self) 177 | 178 | if let willHideRightView = self.willHideRightView { 179 | willHideRightView(self) 180 | } 181 | 182 | if let delegate = self.delegate { 183 | delegate.willHideRightView(sideMenuController: self) 184 | } 185 | } 186 | 187 | func didHideRightViewCallbacks() { 188 | NotificationCenter.default.post(name: Notification.didHideRightView, object: self) 189 | 190 | if let didHideRightView = self.didHideRightView { 191 | didHideRightView(self) 192 | } 193 | 194 | if let delegate = self.delegate { 195 | delegate.didHideRightView(sideMenuController: self) 196 | } 197 | } 198 | 199 | func showAnimationsForRightViewCallbacks(duration: TimeInterval, timingFunction: CAMediaTimingFunction) { 200 | NotificationCenter.default.post(name: Notification.showAnimationsForRightView, 201 | object: self, 202 | userInfo: [Notification.Key.duration: duration, 203 | Notification.Key.timigFunction: timingFunction]) 204 | 205 | if let callback = self.showAnimationsForRightView { 206 | callback(self, duration, timingFunction) 207 | } 208 | 209 | if let delegate = self.delegate { 210 | delegate.showAnimationsForRightView(sideMenuController: self, 211 | duration: duration, 212 | timingFunction: timingFunction) 213 | } 214 | } 215 | 216 | func hideAnimationsForRightViewCallbacks(duration: TimeInterval, timingFunction: CAMediaTimingFunction) { 217 | NotificationCenter.default.post(name: Notification.hideAnimationsForRightView, 218 | object: self, 219 | userInfo: [Notification.Key.duration: duration, 220 | Notification.Key.timigFunction: timingFunction]) 221 | 222 | if let callback = self.hideAnimationsForRightView { 223 | callback(self, duration, timingFunction) 224 | } 225 | 226 | if let delegate = self.delegate { 227 | delegate.hideAnimationsForRightView(sideMenuController: self, 228 | duration: duration, 229 | timingFunction: timingFunction) 230 | } 231 | } 232 | 233 | func didTransformRightViewCallbacks(percentage: CGFloat) { 234 | NotificationCenter.default.post(name: Notification.didTransformRightView, 235 | object: self, 236 | userInfo: [Notification.Key.percentage: percentage]) 237 | 238 | if let callback = self.didTransformRightView { 239 | callback(self, percentage) 240 | } 241 | 242 | if let delegate = self.delegate { 243 | delegate.didTransformRightView(sideMenuController: self, percentage: percentage) 244 | } 245 | } 246 | 247 | } 248 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+Helpers.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func disableRootViewLayouting() { 36 | guard self.isRootViewLayoutingEnabled == true, 37 | let wrapperView = self.rootViewWrapperView else { return } 38 | 39 | wrapperView.canLayoutSubviews = false 40 | self.isRootViewLayoutingEnabled = false 41 | } 42 | 43 | func enableRootViewLayouting() { 44 | guard self.isRootViewLayoutingEnabled == false, 45 | let wrapperView = self.rootViewWrapperView else { return } 46 | 47 | wrapperView.canLayoutSubviews = true 48 | self.isRootViewLayoutingEnabled = true 49 | } 50 | 51 | func disableRootViewControllerLayouting() { 52 | guard self.isRootViewControllerLayoutingEnabled == true, 53 | let viewController = self.rootViewController else { return } 54 | 55 | viewController.willMove(toParent: nil) 56 | viewController.removeFromParent() 57 | self.isRootViewControllerLayoutingEnabled = false 58 | } 59 | 60 | func enableRootViewControllerLayouting() { 61 | guard self.isRootViewControllerLayoutingEnabled == false, 62 | let viewController = self.rootViewController else { return } 63 | 64 | self.addChild(viewController) 65 | viewController.didMove(toParent: self) 66 | self.isRootViewControllerLayoutingEnabled = true 67 | } 68 | 69 | var leftViewWidthTotal: CGFloat { 70 | self.leftViewWidth + self.leftViewLayerBorderWidth 71 | } 72 | 73 | var rightViewWidthTotal: CGFloat { 74 | self.rightViewWidth + self.rightViewLayerBorderWidth 75 | } 76 | 77 | var rootViewOffsetTotalForLeftView: CGPoint { 78 | var result = self.rootViewOffsetWhenHiddenForLeftView 79 | if self.leftViewPresentationStyle.shouldRootViewMove { 80 | result.x += self.leftViewWidthTotal + self.rootViewLayerBorderWidthForLeftView 81 | } 82 | return result 83 | } 84 | 85 | var rootViewOffsetTotalForLeftViewWhenAlwaysVisible: CGFloat { 86 | return 87 | self.rootViewOffsetWhenHiddenForLeftView.x + 88 | self.leftViewWidthTotal + 89 | self.rootViewLayerBorderWidthForLeftView 90 | } 91 | 92 | var rootViewOffsetTotalForRightView: CGPoint { 93 | var result = CGPoint(x: -self.rootViewOffsetWhenHiddenForRightView.x, 94 | y: self.rootViewOffsetWhenHiddenForRightView.y) 95 | if self.rightViewPresentationStyle.shouldRootViewMove { 96 | result.x += self.rightViewWidthTotal + self.rootViewLayerBorderWidthForRightView 97 | } 98 | return result 99 | } 100 | 101 | var rootViewOffsetTotalForRightViewWhenAlwaysVisible: CGFloat { 102 | return 103 | -self.rootViewOffsetWhenHiddenForRightView.x + 104 | self.rightViewWidthTotal + 105 | self.rootViewLayerBorderWidthForRightView 106 | } 107 | 108 | // MARK: - Cancel Animations - 109 | 110 | func cancelRootViewAnimations() { 111 | guard let containerView = self.rootContainerView, 112 | let wrapperView = self.rootViewWrapperView, 113 | let coverView = self.rootViewCoverView else { return } 114 | 115 | CATransaction.begin() 116 | containerView.layer.removeAllAnimations() 117 | wrapperView.layer.removeAllAnimations() 118 | coverView.layer.removeAllAnimations() 119 | CATransaction.commit() 120 | } 121 | 122 | func cancelLeftViewAnimations() { 123 | guard let containerView = self.leftContainerView, 124 | let backgroundDecorationView = self.leftViewBackgroundDecorationView, 125 | let wrapperView = self.leftViewWrapperView, 126 | let coverView = self.leftViewCoverView else { return } 127 | 128 | CATransaction.begin() 129 | containerView.layer.removeAllAnimations() 130 | backgroundDecorationView.layer.removeAllAnimations() 131 | wrapperView.layer.removeAllAnimations() 132 | coverView.layer.removeAllAnimations() 133 | CATransaction.commit() 134 | } 135 | 136 | func cancelRightViewAnimations() { 137 | guard let containerView = self.rightContainerView, 138 | let backgroundDecorationView = self.rightViewBackgroundDecorationView, 139 | let wrapperView = self.rightViewWrapperView, 140 | let coverView = self.rightViewCoverView else { return } 141 | 142 | CATransaction.begin() 143 | containerView.layer.removeAllAnimations() 144 | backgroundDecorationView.layer.removeAllAnimations() 145 | wrapperView.layer.removeAllAnimations() 146 | coverView.layer.removeAllAnimations() 147 | CATransaction.commit() 148 | } 149 | 150 | // MARK: - Status Bar - 151 | 152 | var isViewLocatedUnderStatusBar: Bool { 153 | guard let keyWindow = LGSideMenuHelper.getKeyWindow() else { return false } 154 | let statusBarOrigin = keyWindow.convert(LGSideMenuHelper.getStatusBarFrame().origin, to: self.view) 155 | return statusBarOrigin == .zero 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+Layouting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+Layouting.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | extension LGSideMenuController { 34 | 35 | open override func viewWillLayoutSubviews() { 36 | super.viewWillLayoutSubviews() 37 | let size = self.view.bounds.size 38 | if self.isNeedsUpdateLayoutsAndStyles || self.savedSize != size { 39 | self.savedSize = size 40 | self.updateLayoutsAndStyles() 41 | } 42 | else if self.isNeedsUpdateRootViewLayoutsAndStyles { 43 | self.updateRootViewLayoutsAndStyles() 44 | } 45 | else if self.isNeedsUpdateLeftViewLayoutsAndStyles { 46 | self.updateLeftViewLayoutsAndStyles() 47 | } 48 | else if self.isNeedsUpdateRightViewLayoutsAndStyles { 49 | self.updateRightViewLayoutsAndStyles() 50 | } 51 | } 52 | 53 | /// Invalidates the current layout and triggers a layout update during the next update cycle. 54 | open func setNeedsUpdateLayoutsAndStyles() { 55 | self.isNeedsUpdateLayoutsAndStyles = true 56 | if self.isViewLoaded { 57 | self.view.setNeedsLayout() 58 | } 59 | } 60 | 61 | /// Invalidates the current layout and triggers a layout update during the next update cycle. 62 | open func setNeedsUpdateRootViewLayoutsAndStyles() { 63 | self.isNeedsUpdateRootViewLayoutsAndStyles = true 64 | if self.isViewLoaded { 65 | self.view.setNeedsLayout() 66 | } 67 | } 68 | 69 | /// Invalidates the current layout and triggers a layout update during the next update cycle. 70 | open func setNeedsUpdateLeftViewLayoutsAndStyles() { 71 | self.isNeedsUpdateLeftViewLayoutsAndStyles = true 72 | if self.isViewLoaded { 73 | self.view.setNeedsLayout() 74 | } 75 | } 76 | 77 | /// Invalidates the current layout and triggers a layout update during the next update cycle. 78 | open func setNeedsUpdateRightViewLayoutsAndStyles() { 79 | self.isNeedsUpdateRightViewLayoutsAndStyles = true 80 | if self.isViewLoaded { 81 | self.view.setNeedsLayout() 82 | } 83 | } 84 | 85 | /// Forces update layouts and styles for all views 86 | open func updateLayoutsAndStyles() { 87 | self.isNeedsUpdateLayoutsAndStyles = false 88 | self.isNeedsUpdateRootViewLayoutsAndStyles = false 89 | self.isNeedsUpdateLeftViewLayoutsAndStyles = false 90 | self.isNeedsUpdateRightViewLayoutsAndStyles = false 91 | 92 | self.validateViewsInit() 93 | self.validateViewsHierarchy() 94 | self.validateViewsFrames() 95 | self.validateViewsStyles() 96 | self.validateViewsTransforms() 97 | self.validateViewsVisibility() 98 | } 99 | 100 | /// Forces update layouts and styles for root views 101 | open func updateRootViewLayoutsAndStyles() { 102 | self.isNeedsUpdateRootViewLayoutsAndStyles = false 103 | 104 | self.validateRootViewsInit() 105 | self.validateViewsHierarchy() // Whole hierarchy should be validated 106 | self.validateRootViewsFrames() 107 | self.validateRootViewsStyles() 108 | self.validateRootViewsTransforms() 109 | self.validateRootViewsVisibility() 110 | } 111 | 112 | /// Forces update layouts and styles for left views 113 | open func updateLeftViewLayoutsAndStyles() { 114 | self.isNeedsUpdateLeftViewLayoutsAndStyles = false 115 | 116 | self.validateLeftViewsInit() 117 | self.validateViewsHierarchy() // Whole hierarchy should be validated 118 | self.validateLeftViewsFrames() 119 | self.validateLeftViewsStyles() 120 | self.validateLeftViewsTransforms() 121 | self.validateLeftViewsVisibility() 122 | } 123 | 124 | /// Forces update layouts and styles for right views 125 | open func updateRightViewLayoutsAndStyles() { 126 | self.isNeedsUpdateRightViewLayoutsAndStyles = false 127 | 128 | self.validateRightViewsInit() 129 | self.validateViewsHierarchy() // Whole hierarchy should be validated 130 | self.validateRightViewsFrames() 131 | self.validateRightViewsStyles() 132 | self.validateRightViewsTransforms() 133 | self.validateRightViewsVisibility() 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+Rotating.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+Rotating.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | extension LGSideMenuController { 34 | 35 | open override var shouldAutorotate: Bool { 36 | if let rootViewController = self.rootViewController { 37 | return rootViewController.shouldAutorotate 38 | } 39 | return super.shouldAutorotate 40 | } 41 | 42 | open override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 43 | super.willTransition(to: newCollection, with: coordinator) 44 | 45 | if !self.isRootViewShowing { 46 | self.isRotationInvalidatedLayout = true 47 | } 48 | } 49 | 50 | open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 51 | super.viewWillTransition(to: size, with: coordinator) 52 | 53 | // We need to do this, because current orientation is already changed to a new one 54 | guard let previousOrientation = LGSideMenuHelper.getOppositeInterfaceOrientation() else { return } 55 | 56 | self.cancelRootViewAnimations() 57 | self.cancelLeftViewAnimations() 58 | self.cancelRightViewAnimations() 59 | 60 | if (self.leftView != nil && self.isLeftViewAlwaysVisible(orientation: previousOrientation)) || 61 | (self.rightView != nil && self.isRightViewAlwaysVisible(orientation: previousOrientation)) { 62 | self.shouldUpdateVisibility = false 63 | } 64 | 65 | if self.state == .leftViewWillShow { 66 | if self.isLeftViewAlwaysVisible { 67 | self.showLeftViewDone() 68 | } 69 | } 70 | else if self.state == .leftViewWillHide { 71 | self.hideLeftViewDone(updateStatusBar: true) 72 | } 73 | 74 | if self.state == .rightViewWillShow { 75 | if self.isRightViewAlwaysVisible { 76 | self.showRightViewDone() 77 | } 78 | } 79 | else if self.state == .rightViewWillHide { 80 | self.hideRightViewDone(updateStatusBar: true) 81 | } 82 | 83 | coordinator.animate(alongsideTransition: { [weak self] (context: UIViewControllerTransitionCoordinatorContext) in 84 | guard let self = self else { return } 85 | 86 | if self.isLeftViewAlwaysVisible && !self.isLeftViewHidden { 87 | self.hideLeftViewPrepare() 88 | self.hideLeftViewActions(animated: true, duration: context.transitionDuration) 89 | } 90 | 91 | if self.isRightViewAlwaysVisible && !self.isRightViewHidden { 92 | self.hideRightViewPrepare() 93 | self.hideRightViewActions(animated: true, duration: context.transitionDuration) 94 | } 95 | }, completion: { [weak self] (context: UIViewControllerTransitionCoordinatorContext) in 96 | guard let self = self else { return } 97 | 98 | if !self.shouldUpdateVisibility { 99 | self.shouldUpdateVisibility = true 100 | self.validateLeftViewsVisibility() 101 | self.validateRightViewsVisibility() 102 | } 103 | 104 | self.validateViewsUserInteraction() 105 | }) 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+States.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+States.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | extension LGSideMenuController { 34 | 35 | /// Is root view fully opened 36 | open var isRootViewShowing: Bool { 37 | return self.state == .rootViewIsShowing 38 | } 39 | 40 | /// Is left view fully opened 41 | open var isLeftViewShowing: Bool { 42 | return self.state == .leftViewIsShowing 43 | } 44 | 45 | /// Is right view fully opened 46 | open var isRightViewShowing: Bool { 47 | return self.state == .rightViewIsShowing 48 | } 49 | 50 | /// Is either left or right view fully opened 51 | open var isSideViewShowing: Bool { 52 | return self.isLeftViewShowing || self.isRightViewShowing 53 | } 54 | 55 | /// Is left view showing or will show or will hide right now 56 | open var isLeftViewVisible: Bool { 57 | return self.state.isLeftViewVisible 58 | } 59 | 60 | /// Is right view showing or will show or will hide right now 61 | open var isRightViewVisible: Bool { 62 | return self.state.isRightViewVisible 63 | } 64 | 65 | /// Is either left or right view showing or will show or will hide right now 66 | open var isSideViewVisible: Bool { 67 | return self.isLeftViewVisible || self.isRightViewVisible 68 | } 69 | 70 | /// Is left view fully closed 71 | open var isRootViewHidden: Bool { 72 | return self.state.isRootViewHidden 73 | } 74 | 75 | /// Is left view fully closed 76 | open var isLeftViewHidden: Bool { 77 | return self.state.isLeftViewHidden 78 | } 79 | 80 | /// Is right view fully closed 81 | open var isRightViewHidden: Bool { 82 | return self.state.isRightViewHidden 83 | } 84 | 85 | /// Is either left or right view fully closed 86 | open var isSideViewHidden: Bool { 87 | return self.isLeftViewHidden || self.isRightViewHidden 88 | } 89 | 90 | /// Is left view currently will show or hide 91 | open var isLeftViewVisibilityChanging: Bool { 92 | return self.state == .leftViewWillShow || self.state == .leftViewWillHide 93 | } 94 | 95 | /// Is right view currently will show or hide 96 | open var isRightViewVisibilityChanging: Bool { 97 | return self.state == .rightViewWillShow || self.state == .rightViewWillHide 98 | } 99 | 100 | /// Is either left or right view currently will show or hide 101 | open var isSideViewVisibilityChanging: Bool { 102 | return self.isLeftViewVisibilityChanging || self.isRightViewVisibilityChanging 103 | } 104 | 105 | /// Is left view currently fully open or close 106 | open var isLeftViewVisibilityStable: Bool { 107 | return self.state != .leftViewWillShow && self.state != .leftViewWillHide 108 | } 109 | 110 | /// Is right view currently fully open or close 111 | open var isRightViewVisibilityStable: Bool { 112 | return self.state != .rightViewWillShow && self.state != .rightViewWillHide 113 | } 114 | 115 | /// Is either left or right view currently fully open or close 116 | open var isSideViewVisibilityStable: Bool { 117 | return self.isLeftViewVisibilityStable || self.isRightViewVisibilityStable 118 | } 119 | 120 | /// Is left view suppose to be "always visible" for current orientation 121 | open var isLeftViewAlwaysVisible: Bool { 122 | return self.leftViewAlwaysVisibleOptions.isVisible(sizeClass: self.traitCollection.horizontalSizeClass) 123 | } 124 | 125 | /// Is right view suppose to be "always visible" for current orientation 126 | open var isRightViewAlwaysVisible: Bool { 127 | return self.rightViewAlwaysVisibleOptions.isVisible(sizeClass: self.traitCollection.horizontalSizeClass) 128 | } 129 | 130 | /// Is left view suppose to be "always visible" for specified orientation 131 | open func isLeftViewAlwaysVisible(orientation: UIInterfaceOrientation) -> Bool { 132 | return self.leftViewAlwaysVisibleOptions.isVisible(orientation: orientation, 133 | sizeClass: self.traitCollection.horizontalSizeClass) 134 | } 135 | 136 | /// Is right view suppose to be "always visible" for specified orientation 137 | open func isRightViewAlwaysVisible(orientation: UIInterfaceOrientation) -> Bool { 138 | return self.rightViewAlwaysVisibleOptions.isVisible(orientation: orientation, 139 | sizeClass: self.traitCollection.horizontalSizeClass) 140 | } 141 | 142 | /// Is any of side views suppose to be "always visible" for current orientation 143 | open var isSideViewAlwaysVisible: Bool { 144 | return self.isLeftViewAlwaysVisible || self.isRightViewAlwaysVisible 145 | } 146 | 147 | /// Is left view showing or always showing or will show or will hide right now 148 | open var isLeftViewVisibleToUser: Bool { 149 | return self.isLeftViewVisible || self.isLeftViewAlwaysVisible 150 | } 151 | 152 | /// Is right view showing or always showing or will show or will hide right now 153 | open var isRightViewVisibleToUser: Bool { 154 | return self.isRightViewVisible || self.isRightViewAlwaysVisible 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+StatusBarHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+StatusBarHandler.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | extension LGSideMenuController { 34 | 35 | open override var prefersStatusBarHidden: Bool { 36 | if self.rootView != nil && (self.state == .rootViewIsShowing || self.state == .leftViewWillHide || self.state == .rightViewWillHide) { 37 | return self.isRootViewStatusBarHidden 38 | } 39 | else if self.leftView != nil && self.isLeftViewVisible && !self.isLeftViewAlwaysVisible { 40 | return self.isLeftViewStatusBarHidden 41 | } 42 | else if self.rightView != nil && self.isRightViewVisible && !self.isRightViewAlwaysVisible { 43 | return self.isRightViewStatusBarHidden 44 | } 45 | 46 | return super.prefersStatusBarHidden 47 | } 48 | 49 | open override var preferredStatusBarStyle: UIStatusBarStyle { 50 | if self.rootView != nil && (self.state == .rootViewIsShowing || self.state == .leftViewWillHide || self.state == .rightViewWillHide) { 51 | return self.rootViewStatusBarStyle 52 | } 53 | else if self.leftView != nil && self.isLeftViewVisible && !self.isLeftViewAlwaysVisible { 54 | return self.leftViewStatusBarStyle 55 | } 56 | else if self.rightView != nil && self.isRightViewVisible && !self.isRightViewAlwaysVisible { 57 | return self.rightViewStatusBarStyle 58 | } 59 | 60 | return super.preferredStatusBarStyle 61 | } 62 | 63 | open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 64 | if self.rootView != nil && self.state == .rootViewIsShowing { 65 | return self.rootViewStatusBarUpdateAnimation 66 | } 67 | else if self.leftView != nil && self.isLeftViewVisible && !self.isLeftViewAlwaysVisible { 68 | return self.leftViewStatusBarUpdateAnimation 69 | } 70 | else if self.rightView != nil && self.isRightViewVisible && !self.isRightViewAlwaysVisible { 71 | return self.rightViewStatusBarUpdateAnimation 72 | } 73 | 74 | return super.preferredStatusBarUpdateAnimation 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/LGSideMenuController+ViewsRemoving.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ViewsRemoving.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | // Note: 36 | // We don't nullify any of rootView, rootViewController, leftView, leftViewController, rightView, rightViewController 37 | // because we don't manage their lifecycle from here, they should be assigned from the outside. 38 | 39 | func removeViewController(_ viewController: UIViewController?) { 40 | guard let viewController = viewController else { return } 41 | LGSideMenuHelper.setSideMenuController(nil, to: viewController) 42 | viewController.willMove(toParent: nil) 43 | viewController.removeFromParent() 44 | } 45 | 46 | // MARK: - Root View - 47 | 48 | func removeRootViews() { 49 | if let containerView = self.rootContainerView { 50 | containerView.removeFromSuperview() 51 | self.rootContainerView = nil 52 | } 53 | 54 | if let containerClipToBorderView = self.rootContainerClipToBorderView { 55 | containerClipToBorderView.removeFromSuperview() 56 | self.rootContainerClipToBorderView = nil 57 | } 58 | 59 | if let backgroundDecorationView = self.rootViewBackgroundDecorationView { 60 | backgroundDecorationView.removeFromSuperview() 61 | self.rootViewBackgroundDecorationView = nil 62 | } 63 | 64 | if let backgroundShadowView = self.rootViewBackgroundShadowView { 65 | backgroundShadowView.removeFromSuperview() 66 | self.rootViewBackgroundShadowView = nil 67 | } 68 | 69 | if let wrapperView = self.rootViewWrapperView { 70 | wrapperView.removeFromSuperview() 71 | self.rootViewWrapperView = nil 72 | } 73 | 74 | if let coverView = self.rootViewCoverView { 75 | coverView.removeFromSuperview() 76 | self.rootViewCoverView = nil 77 | } 78 | } 79 | 80 | // MARK: - Left View - 81 | 82 | func removeLeftViews() { 83 | if let containerView = self.leftContainerView { 84 | containerView.removeFromSuperview() 85 | self.leftContainerView = nil 86 | } 87 | 88 | if let containerClipToBorderView = self.leftContainerClipToBorderView { 89 | containerClipToBorderView.removeFromSuperview() 90 | self.leftContainerClipToBorderView = nil 91 | } 92 | 93 | if let backgroundDecorationView = self.leftViewBackgroundDecorationView { 94 | backgroundDecorationView.removeFromSuperview() 95 | self.leftViewBackgroundDecorationView = nil 96 | } 97 | 98 | if let backgroundShadowView = self.leftViewBackgroundShadowView { 99 | backgroundShadowView.removeFromSuperview() 100 | self.leftViewBackgroundShadowView = nil 101 | } 102 | 103 | if let backgroundImageView = self.leftViewBackgroundImageView { 104 | backgroundImageView.removeFromSuperview() 105 | self.leftViewBackgroundImageView = nil 106 | } 107 | 108 | if let backgroundView = self.leftViewBackgroundView { 109 | backgroundView.removeFromSuperview() 110 | // We don't nullify it because it is custom property 111 | } 112 | 113 | if let backgroundEffectView = self.leftViewBackgroundEffectView { 114 | backgroundEffectView.removeFromSuperview() 115 | self.leftViewBackgroundEffectView = nil 116 | } 117 | 118 | if let backgroundWrapperView = self.leftViewBackgroundWrapperView { 119 | backgroundWrapperView.removeFromSuperview() 120 | self.leftViewBackgroundWrapperView = nil 121 | } 122 | 123 | if let wrapperView = self.leftViewWrapperView { 124 | wrapperView.removeFromSuperview() 125 | self.leftViewWrapperView = nil 126 | } 127 | 128 | if let coverView = self.leftViewCoverView { 129 | coverView.removeFromSuperview() 130 | self.leftViewCoverView = nil 131 | } 132 | 133 | if let statusBarBackgroundView = self.leftViewStatusBarBackgroundView { 134 | statusBarBackgroundView.removeFromSuperview() 135 | self.leftViewStatusBarBackgroundView = nil 136 | } 137 | 138 | if let statusBarBackgroundEffectView = self.leftViewStatusBarBackgroundEffectView { 139 | statusBarBackgroundEffectView.removeFromSuperview() 140 | self.leftViewStatusBarBackgroundEffectView = nil 141 | } 142 | } 143 | 144 | // MARK: - Right View - 145 | 146 | func removeRightViews() { 147 | if let containerView = self.rightContainerView { 148 | containerView.removeFromSuperview() 149 | self.rightContainerView = nil 150 | } 151 | 152 | if let containerClipToBorderView = self.rightContainerClipToBorderView { 153 | containerClipToBorderView.removeFromSuperview() 154 | self.rightContainerClipToBorderView = nil 155 | } 156 | 157 | if let backgroundDecorationView = self.rightViewBackgroundDecorationView { 158 | backgroundDecorationView.removeFromSuperview() 159 | self.rightViewBackgroundDecorationView = nil 160 | } 161 | 162 | if let backgroundShadowView = self.rightViewBackgroundShadowView { 163 | backgroundShadowView.removeFromSuperview() 164 | self.rightViewBackgroundShadowView = nil 165 | } 166 | 167 | if let backgroundImageView = self.rightViewBackgroundImageView { 168 | backgroundImageView.removeFromSuperview() 169 | self.rightViewBackgroundImageView = nil 170 | } 171 | 172 | if let backgroundView = self.rightViewBackgroundView { 173 | backgroundView.removeFromSuperview() 174 | // We don't nullify it because it is custom property 175 | } 176 | 177 | if let backgroundEffectView = self.rightViewBackgroundEffectView { 178 | backgroundEffectView.removeFromSuperview() 179 | self.rightViewBackgroundEffectView = nil 180 | } 181 | 182 | if let backgroundWrapperView = self.rightViewBackgroundWrapperView { 183 | backgroundWrapperView.removeFromSuperview() 184 | self.rightViewBackgroundWrapperView = nil 185 | } 186 | 187 | if let wrapperView = self.rightViewWrapperView { 188 | wrapperView.removeFromSuperview() 189 | self.rightViewWrapperView = nil 190 | } 191 | 192 | if let coverView = self.rightViewCoverView { 193 | coverView.removeFromSuperview() 194 | self.rightViewCoverView = nil 195 | } 196 | 197 | if let statusBarBackgroundView = self.rightViewStatusBarBackgroundView { 198 | statusBarBackgroundView.removeFromSuperview() 199 | self.rightViewStatusBarBackgroundView = nil 200 | } 201 | 202 | if let statusBarBackgroundEffectView = self.rightViewStatusBarBackgroundEffectView { 203 | statusBarBackgroundEffectView.removeFromSuperview() 204 | self.rightViewStatusBarBackgroundEffectView = nil 205 | } 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/Validators/LGSideMenuController+ValidatingUserInteraction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ValidatingUserInteraction.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func validateViewsUserInteraction() { 36 | self.validateRootViewsUserInteraction() 37 | self.validateLeftViewsUserInteraction() 38 | self.validateRightViewsUserInteraction() 39 | } 40 | 41 | func validateRootViewsUserInteraction() { 42 | guard let rootViewWrapperView = self.rootViewWrapperView else { return } 43 | rootViewWrapperView.isUserInteractionEnabled = self.isRootViewShowing 44 | } 45 | 46 | func validateLeftViewsUserInteraction() { 47 | guard let leftViewWrapperView = self.leftViewWrapperView else { return } 48 | leftViewWrapperView.isUserInteractionEnabled = 49 | (self.isLeftViewShowing || self.isLeftViewAlwaysVisible) && self.isRightViewHidden 50 | } 51 | 52 | func validateRightViewsUserInteraction() { 53 | guard let rightViewWrapperView = self.rightViewWrapperView else { return } 54 | rightViewWrapperView.isUserInteractionEnabled = 55 | (self.isRightViewShowing || self.isRightViewAlwaysVisible) && self.isLeftViewHidden 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/Validators/LGSideMenuController+ValidatingViewsHierarchy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ValidatingViewsHierarchy.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func validateViewsHierarchy() { 36 | self.validateRootViewsHierarchy() 37 | self.validateLeftViewsHierarchy() 38 | self.validateRightViewsHierarchy() 39 | } 40 | 41 | func validateRootViewsHierarchy() { 42 | guard let rootView = self.rootView, 43 | let containerView = self.rootContainerView, 44 | let containerClipToBorderView = self.rootContainerClipToBorderView, 45 | let backgroundDecorationView = self.rootViewBackgroundDecorationView, 46 | let backgroundShadowView = self.rootViewBackgroundShadowView, 47 | let wrapperView = self.rootViewWrapperView, 48 | let coverView = self.rootViewCoverView else { return } 49 | 50 | if isRootViewControllerLayoutingEnabled, 51 | let rootViewController = self.rootViewController, 52 | rootViewController.parent != self { 53 | self.addChild(rootViewController) 54 | rootViewController.didMove(toParent: self) 55 | } 56 | 57 | self.view.insertSubview(containerView, at: 0) 58 | 59 | containerView.insertSubview(backgroundDecorationView, at: 0) 60 | containerView.insertSubview(containerClipToBorderView, at: 1) 61 | 62 | backgroundDecorationView.insertSubview(backgroundShadowView, at: 0) 63 | 64 | containerClipToBorderView.insertSubview(wrapperView, at: 0) 65 | containerClipToBorderView.insertSubview(coverView, at: 1) 66 | 67 | wrapperView.insertSubview(rootView, at: 0) 68 | } 69 | 70 | func validateLeftViewsHierarchy() { 71 | guard let rootContainerView = self.rootContainerView, 72 | let leftView = self.leftView, 73 | let containerView = self.leftContainerView, 74 | let containerClipToBorderView = self.leftContainerClipToBorderView, 75 | let backgroundDecorationView = self.leftViewBackgroundDecorationView, 76 | let backgroundShadowView = self.leftViewBackgroundShadowView, 77 | let backgroundWrapperView = self.leftViewBackgroundWrapperView, 78 | let backgroundEffectView = self.leftViewBackgroundEffectView, 79 | let wrapperView = self.leftViewWrapperView, 80 | let coverView = self.leftViewCoverView, 81 | let statusBarBackgroundView = self.leftViewStatusBarBackgroundView, 82 | let statusBarBackgroundEffectView = self.leftViewStatusBarBackgroundEffectView else { return } 83 | 84 | if let leftViewController = self.leftViewController, leftViewController.parent != self { 85 | self.addChild(leftViewController) 86 | leftViewController.didMove(toParent: self) 87 | } 88 | 89 | if self.leftViewPresentationStyle.isAbove { 90 | self.view.insertSubview(containerView, aboveSubview: rootContainerView) 91 | } 92 | else { 93 | self.view.insertSubview(containerView, belowSubview: rootContainerView) 94 | } 95 | 96 | containerView.insertSubview(backgroundDecorationView, at: 0) 97 | containerView.insertSubview(containerClipToBorderView, at: 1) 98 | 99 | backgroundDecorationView.insertSubview(backgroundShadowView, at: 0) 100 | backgroundDecorationView.insertSubview(backgroundEffectView, at: 1) 101 | backgroundDecorationView.insertSubview(backgroundWrapperView, at: 2) 102 | 103 | containerClipToBorderView.insertSubview(wrapperView, at: 0) 104 | containerClipToBorderView.insertSubview(coverView, at: 1) 105 | containerClipToBorderView.insertSubview(statusBarBackgroundView, at: 2) 106 | 107 | if let backgroundImageView = self.leftViewBackgroundImageView { 108 | backgroundWrapperView.insertSubview(backgroundImageView, at: 0) 109 | } 110 | else if let backgroundView = self.leftViewBackgroundView { 111 | backgroundWrapperView.insertSubview(backgroundView, at: 0) 112 | } 113 | 114 | wrapperView.insertSubview(leftView, at: 0) 115 | 116 | statusBarBackgroundView.insertSubview(statusBarBackgroundEffectView, at: 0) 117 | } 118 | 119 | func validateRightViewsHierarchy() { 120 | guard let rootContainerView = self.rootContainerView, 121 | let rightView = self.rightView, 122 | let containerView = self.rightContainerView, 123 | let containerClipToBorderView = self.rightContainerClipToBorderView, 124 | let backgroundDecorationView = self.rightViewBackgroundDecorationView, 125 | let backgroundShadowView = self.rightViewBackgroundShadowView, 126 | let backgroundWrapperView = self.rightViewBackgroundWrapperView, 127 | let backgroundEffectView = self.rightViewBackgroundEffectView, 128 | let wrapperView = self.rightViewWrapperView, 129 | let coverView = self.rightViewCoverView, 130 | let statusBarBackgroundView = self.rightViewStatusBarBackgroundView, 131 | let statusBarBackgroundEffectView = self.rightViewStatusBarBackgroundEffectView else { return } 132 | 133 | if let rightViewController = self.rightViewController, rightViewController.parent != self { 134 | self.addChild(rightViewController) 135 | rightViewController.didMove(toParent: self) 136 | } 137 | 138 | if self.rightViewPresentationStyle.isAbove { 139 | self.view.insertSubview(containerView, aboveSubview: rootContainerView) 140 | } 141 | else { 142 | self.view.insertSubview(containerView, belowSubview: rootContainerView) 143 | } 144 | 145 | containerView.insertSubview(backgroundDecorationView, at: 0) 146 | containerView.insertSubview(containerClipToBorderView, at: 1) 147 | 148 | backgroundDecorationView.insertSubview(backgroundShadowView, at: 0) 149 | backgroundDecorationView.insertSubview(backgroundEffectView, at: 1) 150 | backgroundDecorationView.insertSubview(backgroundWrapperView, at: 2) 151 | 152 | containerClipToBorderView.insertSubview(wrapperView, at: 0) 153 | containerClipToBorderView.insertSubview(coverView, at: 1) 154 | containerClipToBorderView.insertSubview(statusBarBackgroundView, at: 2) 155 | 156 | if let backgroundImageView = self.rightViewBackgroundImageView { 157 | backgroundWrapperView.insertSubview(backgroundImageView, at: 0) 158 | } 159 | else if let backgroundView = self.rightViewBackgroundView { 160 | backgroundWrapperView.insertSubview(backgroundView, at: 0) 161 | } 162 | 163 | wrapperView.insertSubview(rightView, at: 0) 164 | 165 | statusBarBackgroundView.insertSubview(statusBarBackgroundEffectView, at: 0) 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/Validators/LGSideMenuController+ValidatingViewsInit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ValidatingViewsInit.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func validateViewsInit() { 36 | self.validateRootViewsInit() 37 | self.validateLeftViewsInit() 38 | self.validateRightViewsInit() 39 | } 40 | 41 | func validateRootViewsInit() { 42 | guard self.rootView != nil else { return } 43 | 44 | if self.rootContainerView == nil { 45 | self.rootContainerView = UIView() 46 | defaultRootViewSetup(self.rootContainerView, true) 47 | } 48 | 49 | if self.rootContainerClipToBorderView == nil { 50 | self.rootContainerClipToBorderView = UIView() 51 | self.rootContainerClipToBorderView?.clipsToBounds = true 52 | defaultRootViewSetup(self.rootContainerClipToBorderView, true) 53 | } 54 | 55 | if self.rootViewBackgroundDecorationView == nil { 56 | self.rootViewBackgroundDecorationView = LGSideMenuBackgroundDecorationView() 57 | defaultRootViewSetup(self.rootViewBackgroundDecorationView) 58 | } 59 | 60 | if self.rootViewBackgroundShadowView == nil { 61 | self.rootViewBackgroundShadowView = LGSideMenuBackgroundShadowView() 62 | defaultRootViewSetup(self.rootViewBackgroundShadowView) 63 | } 64 | 65 | if self.rootViewWrapperView == nil { 66 | self.rootViewWrapperView = LGSideMenuWrapperView() 67 | self.rootViewWrapperView?.clipsToBounds = true 68 | self.rootViewWrapperView?.canLayoutSubviews = self.isRootViewLayoutingEnabled 69 | defaultRootViewSetup(self.rootViewWrapperView, true) 70 | } 71 | 72 | if self.rootViewCoverView == nil { 73 | self.rootViewCoverView = UIVisualEffectView() 74 | defaultRootViewSetup(self.rootViewCoverView) 75 | } 76 | } 77 | 78 | func validateLeftViewsInit() { 79 | guard self.leftView != nil else { return } 80 | 81 | if self.leftContainerView == nil { 82 | self.leftContainerView = UIView() 83 | self.leftContainerView?.clipsToBounds = true 84 | defaultLeftViewSetup(self.leftContainerView, true) 85 | } 86 | 87 | if self.leftContainerClipToBorderView == nil { 88 | self.leftContainerClipToBorderView = UIView() 89 | self.leftContainerClipToBorderView?.clipsToBounds = true 90 | defaultLeftViewSetup(self.leftContainerClipToBorderView, true) 91 | } 92 | 93 | if self.leftViewBackgroundDecorationView == nil { 94 | self.leftViewBackgroundDecorationView = LGSideMenuBackgroundDecorationView() 95 | defaultLeftViewSetup(self.leftViewBackgroundDecorationView) 96 | } 97 | 98 | if self.leftViewBackgroundShadowView == nil { 99 | self.leftViewBackgroundShadowView = LGSideMenuBackgroundShadowView() 100 | defaultLeftViewSetup(self.leftViewBackgroundShadowView) 101 | } 102 | 103 | if self.leftViewBackgroundEffectView == nil { 104 | self.leftViewBackgroundEffectView = UIVisualEffectView() 105 | defaultLeftViewSetup(self.leftViewBackgroundEffectView) 106 | } 107 | 108 | if self.leftViewBackgroundWrapperView == nil { 109 | self.leftViewBackgroundWrapperView = UIView() 110 | self.leftViewBackgroundWrapperView?.clipsToBounds = true 111 | defaultLeftViewSetup(self.leftViewBackgroundWrapperView) 112 | } 113 | 114 | if self.leftViewBackgroundImage != nil && self.leftViewBackgroundImageView == nil { 115 | self.leftViewBackgroundImageView = UIImageView() 116 | self.leftViewBackgroundImageView?.contentMode = .scaleAspectFill 117 | defaultLeftViewSetup(self.leftViewBackgroundImageView) 118 | } 119 | 120 | if self.leftViewWrapperView == nil { 121 | self.leftViewWrapperView = LGSideMenuWrapperView() 122 | self.leftViewWrapperView?.clipsToBounds = true 123 | defaultLeftViewSetup(self.leftViewWrapperView, true) 124 | } 125 | 126 | if self.leftViewCoverView == nil { 127 | self.leftViewCoverView = UIVisualEffectView() 128 | defaultLeftViewSetup(self.leftViewCoverView) 129 | } 130 | 131 | if self.leftViewStatusBarBackgroundView == nil { 132 | self.leftViewStatusBarBackgroundView = LGSideMenuStatusBarBackgroundView() 133 | defaultLeftViewSetup(self.leftViewStatusBarBackgroundView) 134 | } 135 | 136 | if self.leftViewStatusBarBackgroundEffectView == nil { 137 | self.leftViewStatusBarBackgroundEffectView = UIVisualEffectView() 138 | defaultLeftViewSetup(self.leftViewStatusBarBackgroundEffectView) 139 | } 140 | } 141 | 142 | func validateRightViewsInit() { 143 | guard self.rightView != nil else { return } 144 | 145 | if self.rightContainerView == nil { 146 | self.rightContainerView = UIView() 147 | self.rightContainerView?.clipsToBounds = true 148 | defaultRightViewSetup(self.rightContainerView, true) 149 | } 150 | 151 | if self.rightContainerClipToBorderView == nil { 152 | self.rightContainerClipToBorderView = UIView() 153 | self.rightContainerClipToBorderView?.clipsToBounds = true 154 | defaultRightViewSetup(self.rightContainerClipToBorderView, true) 155 | } 156 | 157 | if self.rightViewBackgroundDecorationView == nil { 158 | self.rightViewBackgroundDecorationView = LGSideMenuBackgroundDecorationView() 159 | defaultRightViewSetup(self.rightViewBackgroundDecorationView) 160 | } 161 | 162 | if self.rightViewBackgroundShadowView == nil { 163 | self.rightViewBackgroundShadowView = LGSideMenuBackgroundShadowView() 164 | defaultRightViewSetup(self.rightViewBackgroundShadowView) 165 | } 166 | 167 | if self.rightViewBackgroundEffectView == nil { 168 | self.rightViewBackgroundEffectView = UIVisualEffectView() 169 | defaultRightViewSetup(self.rightViewBackgroundEffectView) 170 | } 171 | 172 | if self.rightViewBackgroundWrapperView == nil { 173 | self.rightViewBackgroundWrapperView = UIView() 174 | self.rightViewBackgroundWrapperView?.clipsToBounds = true 175 | defaultRightViewSetup(self.rightViewBackgroundWrapperView) 176 | } 177 | 178 | if self.rightViewBackgroundImage != nil && self.rightViewBackgroundImageView == nil { 179 | self.rightViewBackgroundImageView = UIImageView() 180 | self.rightViewBackgroundImageView?.contentMode = .scaleAspectFill 181 | defaultRightViewSetup(self.rightViewBackgroundImageView) 182 | } 183 | 184 | if self.rightViewWrapperView == nil { 185 | self.rightViewWrapperView = LGSideMenuWrapperView() 186 | self.rightViewWrapperView?.clipsToBounds = true 187 | defaultRightViewSetup(self.rightViewWrapperView, true) 188 | } 189 | 190 | if self.rightViewCoverView == nil { 191 | self.rightViewCoverView = UIVisualEffectView() 192 | defaultRightViewSetup(self.rightViewCoverView) 193 | } 194 | 195 | if self.rightViewStatusBarBackgroundView == nil { 196 | self.rightViewStatusBarBackgroundView = LGSideMenuStatusBarBackgroundView() 197 | defaultRightViewSetup(self.rightViewStatusBarBackgroundView) 198 | } 199 | 200 | if self.rightViewStatusBarBackgroundEffectView == nil { 201 | self.rightViewStatusBarBackgroundEffectView = UIVisualEffectView() 202 | defaultRightViewSetup(self.rightViewStatusBarBackgroundEffectView) 203 | } 204 | } 205 | 206 | private func defaultRootViewSetup(_ view: UIView?, _ isUserInteractionEnabled: Bool = false) { 207 | view?.backgroundColor = .clear 208 | view?.isUserInteractionEnabled = isUserInteractionEnabled 209 | } 210 | 211 | private func defaultLeftViewSetup(_ view: UIView?, _ isUserInteractionEnabled: Bool = false) { 212 | view?.backgroundColor = .clear 213 | view?.isUserInteractionEnabled = isUserInteractionEnabled 214 | view?.layer.anchorPoint = CGPoint(x: 0.0, y: 0.5) 215 | } 216 | 217 | private func defaultRightViewSetup(_ view: UIView?, _ isUserInteractionEnabled: Bool = false) { 218 | view?.backgroundColor = .clear 219 | view?.isUserInteractionEnabled = isUserInteractionEnabled 220 | view?.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5) 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/Validators/LGSideMenuController+ValidatingViewsStyles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ValidatingViewsStyles.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func validateViewsStyles() { 36 | self.validateRootViewsStyles() 37 | self.validateLeftViewsStyles() 38 | self.validateRightViewsStyles() 39 | } 40 | 41 | func validateRootViewsStyles() { 42 | guard let backgroundDecorationView = self.rootViewBackgroundDecorationView, 43 | let backgroundShadowView = self.rootViewBackgroundShadowView, 44 | let coverView = self.rootViewCoverView else { return } 45 | 46 | backgroundDecorationView.fillColor = self.rootViewBackgroundColor 47 | 48 | if self.leftView != nil && self.isLeftViewVisibleToUser { 49 | backgroundDecorationView.strokeColor = self.rootViewLayerBorderColorForLeftView 50 | backgroundDecorationView.strokeWidth = self.rootViewLayerBorderWidthForLeftView 51 | 52 | backgroundShadowView.shadowColor = self.rootViewLayerShadowColorForLeftView 53 | backgroundShadowView.shadowBlur = self.rootViewLayerShadowRadiusForLeftView 54 | } 55 | else if self.rightView != nil && self.isRightViewVisibleToUser { 56 | backgroundDecorationView.strokeColor = self.rootViewLayerBorderColorForRightView 57 | backgroundDecorationView.strokeWidth = self.rootViewLayerBorderWidthForRightView 58 | 59 | backgroundShadowView.shadowColor = self.rootViewLayerShadowColorForRightView 60 | backgroundShadowView.shadowBlur = self.rootViewLayerShadowRadiusForRightView 61 | } 62 | else { 63 | backgroundDecorationView.strokeColor = .clear 64 | backgroundDecorationView.strokeWidth = 0.0 65 | 66 | backgroundShadowView.shadowColor = .clear 67 | backgroundShadowView.shadowBlur = 0.0 68 | } 69 | 70 | if self.leftView != nil && self.isLeftViewVisible && !self.isLeftViewAlwaysVisible { 71 | coverView.backgroundColor = self.rootViewCoverColorForLeftView 72 | coverView.effect = self.rootViewCoverBlurEffectForLeftView 73 | } 74 | else if self.rightView != nil && self.isRightViewVisible && !self.isRightViewAlwaysVisible { 75 | coverView.backgroundColor = self.rootViewCoverColorForRightView 76 | coverView.effect = self.rootViewCoverBlurEffectForRightView 77 | } 78 | else { 79 | coverView.backgroundColor = nil 80 | coverView.effect = nil 81 | } 82 | } 83 | 84 | func validateLeftViewsStyles() { 85 | guard let backgroundDecorationView = self.leftViewBackgroundDecorationView, 86 | let backgroundShadowView = self.leftViewBackgroundShadowView, 87 | let backgroundEffectView = self.leftViewBackgroundEffectView, 88 | let coverView = self.leftViewCoverView, 89 | let statusBarBackgroundView = self.leftViewStatusBarBackgroundView, 90 | let statusBarBackgroundEffectView = self.leftViewStatusBarBackgroundEffectView else { return } 91 | 92 | backgroundDecorationView.fillColor = self.leftViewBackgroundColor 93 | backgroundDecorationView.strokeColor = self.leftViewLayerBorderColor 94 | backgroundDecorationView.strokeWidth = self.leftViewLayerBorderWidth 95 | backgroundDecorationView.alpha = self.leftViewBackgroundAlpha 96 | 97 | backgroundShadowView.shadowColor = self.leftViewLayerShadowColor 98 | backgroundShadowView.shadowBlur = self.leftViewLayerShadowRadius 99 | 100 | backgroundEffectView.effect = self.leftViewBackgroundBlurEffect 101 | 102 | if let backgroundImageView = self.leftViewBackgroundImageView { 103 | backgroundImageView.image = self.leftViewBackgroundImage 104 | } 105 | 106 | if self.isLeftViewAlwaysVisible { 107 | coverView.backgroundColor = self.leftViewCoverColorWhenAlwaysVisible 108 | coverView.effect = self.leftViewCoverBlurEffectWhenAlwaysVisible 109 | } 110 | else { 111 | coverView.backgroundColor = self.leftViewCoverColor 112 | coverView.effect = self.leftViewCoverBlurEffect 113 | } 114 | 115 | statusBarBackgroundView.fillColor = self.leftViewStatusBarBackgroundColor 116 | statusBarBackgroundView.shadowColor = self.leftViewStatusBarBackgroundShadowColor 117 | statusBarBackgroundView.shadowBlur = self.leftViewStatusBarBackgroundShadowRadius 118 | 119 | statusBarBackgroundEffectView.effect = self.leftViewStatusBarBackgroundBlurEffect 120 | } 121 | 122 | func validateRightViewsStyles() { 123 | guard let backgroundDecorationView = self.rightViewBackgroundDecorationView, 124 | let backgroundShadowView = self.rightViewBackgroundShadowView, 125 | let backgroundEffectView = self.rightViewBackgroundEffectView, 126 | let coverView = self.rightViewCoverView, 127 | let statusBarBackgroundView = self.rightViewStatusBarBackgroundView, 128 | let statusBarBackgroundEffectView = self.rightViewStatusBarBackgroundEffectView else { return } 129 | 130 | backgroundDecorationView.fillColor = self.rightViewBackgroundColor 131 | backgroundDecorationView.strokeColor = self.rightViewLayerBorderColor 132 | backgroundDecorationView.strokeWidth = self.rightViewLayerBorderWidth 133 | backgroundDecorationView.alpha = self.rightViewBackgroundAlpha 134 | 135 | backgroundShadowView.shadowColor = self.rightViewLayerShadowColor 136 | backgroundShadowView.shadowBlur = self.rightViewLayerShadowRadius 137 | 138 | backgroundEffectView.effect = self.rightViewBackgroundBlurEffect 139 | 140 | if let backgroundImageView = self.rightViewBackgroundImageView { 141 | backgroundImageView.image = self.rightViewBackgroundImage 142 | } 143 | 144 | if self.isRightViewAlwaysVisible { 145 | coverView.backgroundColor = self.rightViewCoverColorWhenAlwaysVisible 146 | coverView.effect = self.rightViewCoverBlurEffectWhenAlwaysVisible 147 | } 148 | else { 149 | coverView.backgroundColor = self.rightViewCoverColor 150 | coverView.effect = self.rightViewCoverBlurEffect 151 | } 152 | 153 | statusBarBackgroundView.fillColor = self.rightViewStatusBarBackgroundColor 154 | statusBarBackgroundView.shadowColor = self.rightViewStatusBarBackgroundShadowColor 155 | statusBarBackgroundView.shadowBlur = self.rightViewStatusBarBackgroundShadowRadius 156 | 157 | statusBarBackgroundEffectView.effect = self.rightViewStatusBarBackgroundBlurEffect 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /LGSideMenuController/Extensions/Validators/LGSideMenuController+ValidatingViewsVisibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuController+ValidatingViewsVisibility.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | internal extension LGSideMenuController { 34 | 35 | func validateViewsVisibility() { 36 | self.validateRootViewsVisibility() 37 | self.validateLeftViewsVisibility() 38 | self.validateRightViewsVisibility() 39 | } 40 | 41 | func validateRootViewsVisibility() { 42 | guard self.shouldUpdateVisibility == true, 43 | let backgroundDecorationView = self.rootViewBackgroundDecorationView, 44 | let coverView = self.rootViewCoverView else { return } 45 | 46 | backgroundDecorationView.isHidden = self.isRootViewShowing && !self.isLeftViewAlwaysVisible && !self.isRightViewAlwaysVisible 47 | coverView.isHidden = self.isRootViewShowing 48 | } 49 | 50 | func validateLeftViewsVisibility() { 51 | guard self.shouldUpdateVisibility == true, 52 | let containerView = self.leftContainerView, 53 | let coverView = self.leftViewCoverView, 54 | let statusBarBackgroundView = self.leftViewStatusBarBackgroundView else { return } 55 | 56 | containerView.isHidden = !self.isLeftViewVisibleToUser 57 | coverView.isHidden = self.isLeftViewShowing || (self.isLeftViewAlwaysVisible && !self.isRightViewVisible) 58 | 59 | statusBarBackgroundView.isHidden = 60 | self.isLeftViewStatusBarBackgroundHidden || 61 | self.isLeftViewStatusBarHidden || 62 | self.isNavigationBarVisible() || 63 | !self.isViewLocatedUnderStatusBar 64 | } 65 | 66 | func validateRightViewsVisibility() { 67 | guard self.shouldUpdateVisibility == true, 68 | let containerView = self.rightContainerView, 69 | let coverView = self.rightViewCoverView, 70 | let statusBarBackgroundView = self.rightViewStatusBarBackgroundView else { return } 71 | 72 | containerView.isHidden = !self.isRightViewVisibleToUser 73 | coverView.isHidden = self.isRightViewShowing || (self.isRightViewAlwaysVisible && !self.isLeftViewVisible) 74 | 75 | statusBarBackgroundView.isHidden = 76 | self.isRightViewStatusBarBackgroundHidden || 77 | self.isRightViewStatusBarHidden || 78 | self.isNavigationBarVisible() || 79 | !self.isViewLocatedUnderStatusBar 80 | } 81 | 82 | private func isNavigationBarVisible() -> Bool { 83 | guard let navigationController = navigationController else { return false } 84 | return !navigationController.isNavigationBarHidden 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuBackgroundDecorationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuBackgroundDecorationView.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import CoreGraphics 32 | import UIKit 33 | 34 | public final class LGSideMenuBackgroundDecorationView: UIView { 35 | 36 | public internal(set) var strokeColor: UIColor = .clear { 37 | didSet { 38 | setNeedsDisplay() 39 | } 40 | } 41 | 42 | public internal(set) var strokeWidth: CGFloat = .zero { 43 | didSet { 44 | setNeedsDisplay() 45 | } 46 | } 47 | 48 | public internal(set) var fillColor: UIColor = .clear { 49 | didSet { 50 | setNeedsDisplay() 51 | } 52 | } 53 | 54 | public init() { 55 | super.init(frame: .zero) 56 | backgroundColor = .clear 57 | } 58 | 59 | required public init?(coder: NSCoder) { 60 | fatalError("init(coder:) has not been implemented") 61 | } 62 | 63 | public override func draw(_ rect: CGRect) { 64 | guard let context = UIGraphicsGetCurrentContext() else { return } 65 | 66 | context.clear(rect) 67 | 68 | let path = UIBezierPath(rect: rect) 69 | path.close() 70 | 71 | // To have inner stroke we need to fill rect and erase smaller rect from inside of it 72 | if self.strokeColor != .clear && self.strokeWidth > 0 { 73 | context.beginPath() 74 | context.addPath(path.cgPath) 75 | context.setFillColor(strokeColor.cgColor) 76 | context.fillPath() 77 | 78 | let strokePath = getStrokedPath(rect: rect) 79 | strokePath.close() 80 | 81 | context.beginPath() 82 | context.addPath(strokePath.cgPath) 83 | context.setFillColor(UIColor.clear.cgColor) 84 | context.setBlendMode(.clear) 85 | context.fillPath() 86 | context.setBlendMode(.normal) 87 | } 88 | 89 | // Fill smaller rect 90 | if self.fillColor != .clear { 91 | let strokePath = getStrokedPath(rect: rect) 92 | strokePath.close() 93 | 94 | context.beginPath() 95 | context.addPath(strokePath.cgPath) 96 | context.setFillColor(fillColor.cgColor) 97 | context.fillPath() 98 | } 99 | } 100 | 101 | private func getStrokedPath(rect: CGRect) -> UIBezierPath { 102 | return UIBezierPath(rect: rect.insetBy(dx: self.strokeWidth, dy: self.strokeWidth)) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuBackgroundShadowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuBackgroundShadowView.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import CoreGraphics 32 | import UIKit 33 | 34 | public final class LGSideMenuBackgroundShadowView: UIView { 35 | 36 | public internal(set) var shadowColor: UIColor = .clear { 37 | didSet { 38 | setNeedsDisplay() 39 | } 40 | } 41 | 42 | public internal(set) var shadowBlur: CGFloat = .zero { 43 | didSet { 44 | setNeedsDisplay() 45 | } 46 | } 47 | 48 | public init() { 49 | super.init(frame: .zero) 50 | backgroundColor = .clear 51 | } 52 | 53 | required public init?(coder: NSCoder) { 54 | fatalError("init(coder:) has not been implemented") 55 | } 56 | 57 | public override func draw(_ rect: CGRect) { 58 | guard let context = UIGraphicsGetCurrentContext() else { return } 59 | 60 | let drawRect = rect.insetBy(dx: self.shadowBlur, dy: self.shadowBlur) 61 | 62 | let path = UIBezierPath(rect: drawRect) 63 | path.close() 64 | 65 | context.clear(rect) 66 | context.beginPath() 67 | context.addPath(path.cgPath) 68 | 69 | // Fill it black to draw proper shadow, then erase black internals and keep only shadow 70 | if shadowColor != .clear && self.shadowBlur > 0 { 71 | context.setShadow(offset: .zero, blur: self.shadowBlur, color: shadowColor.cgColor) 72 | context.setFillColor(UIColor.black.cgColor) 73 | context.fillPath() 74 | context.setShadow(offset: .zero, blur: .zero, color: nil) 75 | 76 | context.beginPath() 77 | context.addPath(path.cgPath) 78 | context.setFillColor(UIColor.clear.cgColor) 79 | context.setBlendMode(.clear) 80 | context.fillPath() 81 | context.setBlendMode(.normal) 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuDelegate.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import QuartzCore 32 | import UIKit 33 | 34 | /// Delegate protocol to observe behaviour of LGSideMenuController 35 | public protocol LGSideMenuDelegate { 36 | 37 | func willShowLeftView(sideMenuController: LGSideMenuController) 38 | func didShowLeftView(sideMenuController: LGSideMenuController) 39 | 40 | func willHideLeftView(sideMenuController: LGSideMenuController) 41 | func didHideLeftView(sideMenuController: LGSideMenuController) 42 | 43 | func willShowRightView(sideMenuController: LGSideMenuController) 44 | func didShowRightView(sideMenuController: LGSideMenuController) 45 | 46 | func willHideRightView(sideMenuController: LGSideMenuController) 47 | func didHideRightView(sideMenuController: LGSideMenuController) 48 | 49 | /// This method is executed inside animation block for showing left view. 50 | /// Use it to add some custom animations. 51 | func showAnimationsForLeftView(sideMenuController: LGSideMenuController, 52 | duration: TimeInterval, 53 | timingFunction: CAMediaTimingFunction) 54 | 55 | /// This method is executed inside animation block for hiding left view. 56 | /// Use it to add some custom animations. 57 | func hideAnimationsForLeftView(sideMenuController: LGSideMenuController, 58 | duration: TimeInterval, 59 | timingFunction: CAMediaTimingFunction) 60 | 61 | /// This method is executed inside animation block for showing right view. 62 | /// Use it to add some custom animations. 63 | func showAnimationsForRightView(sideMenuController: LGSideMenuController, 64 | duration: TimeInterval, 65 | timingFunction: CAMediaTimingFunction) 66 | 67 | /// This method is executed inside animation block for hiding right view. 68 | /// Use it to add some custom animations. 69 | func hideAnimationsForRightView(sideMenuController: LGSideMenuController, 70 | duration: TimeInterval, 71 | timingFunction: CAMediaTimingFunction) 72 | 73 | /// This method is executed on every transformation of root view during showing/hiding of side views 74 | /// You can retrieve percentage between `0.0` and `1.0` from userInfo dictionary, where 75 | /// - `0.0` - view is fully shown 76 | /// - `1.0` - view is fully hidden 77 | func didTransformRootView(sideMenuController: LGSideMenuController, percentage: CGFloat) 78 | 79 | /// This method is executed on every transformation of left view during showing/hiding 80 | /// You can retrieve percentage between `0.0` and `1.0` from userInfo dictionary, where 81 | /// - `0.0` - view is fully hidden 82 | /// - `1.0` - view is fully shown 83 | func didTransformLeftView(sideMenuController: LGSideMenuController, percentage: CGFloat) 84 | 85 | /// This method is executed on every transformation of right view during showing/hiding 86 | /// You can retrieve percentage between `0.0` and `1.0` from userInfo dictionary, where 87 | /// - `0.0` - view is fully hidden 88 | /// - `1.0` - view is fully shown 89 | func didTransformRightView(sideMenuController: LGSideMenuController, percentage: CGFloat) 90 | } 91 | 92 | // As swift doesn't support optional methods, 93 | // we use this extension with default empty implementations for delegate methods 94 | public extension LGSideMenuDelegate { 95 | 96 | func willShowLeftView(sideMenuController: LGSideMenuController) {} 97 | func didShowLeftView(sideMenuController: LGSideMenuController) {} 98 | 99 | func willHideLeftView(sideMenuController: LGSideMenuController) {} 100 | func didHideLeftView(sideMenuController: LGSideMenuController) {} 101 | 102 | func willShowRightView(sideMenuController: LGSideMenuController) {} 103 | func didShowRightView(sideMenuController: LGSideMenuController) {} 104 | 105 | func willHideRightView(sideMenuController: LGSideMenuController) {} 106 | func didHideRightView(sideMenuController: LGSideMenuController) {} 107 | 108 | func showAnimationsForLeftView(sideMenuController: LGSideMenuController, 109 | duration: TimeInterval, 110 | timingFunction: CAMediaTimingFunction) {} 111 | 112 | func hideAnimationsForLeftView(sideMenuController: LGSideMenuController, 113 | duration: TimeInterval, 114 | timingFunction: CAMediaTimingFunction) {} 115 | 116 | func showAnimationsForRightView(sideMenuController: LGSideMenuController, 117 | duration: TimeInterval, 118 | timingFunction: CAMediaTimingFunction) {} 119 | 120 | func hideAnimationsForRightView(sideMenuController: LGSideMenuController, 121 | duration: TimeInterval, 122 | timingFunction: CAMediaTimingFunction) {} 123 | 124 | func rootViewIsTransforming(sideMenuController: LGSideMenuController, percentage: CGFloat) {} 125 | func leftViewIsTransforming(sideMenuController: LGSideMenuController, percentage: CGFloat) {} 126 | func rightViewIsTransforming(sideMenuController: LGSideMenuController, percentage: CGFloat) {} 127 | 128 | } 129 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuHelper.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import ObjectiveC 32 | import QuartzCore 33 | import UIKit 34 | 35 | internal struct LGSideMenuHelper { 36 | private struct Keys { 37 | static var sideMenuController = "sideMenuController" 38 | } 39 | 40 | static func animate(duration: TimeInterval, timingFunction: CAMediaTimingFunction, animations: @escaping () -> Void, completion: @escaping () -> Void) { 41 | UIView.beginAnimations(nil, context: nil) 42 | UIView.setAnimationDuration(duration) 43 | CATransaction.begin() 44 | CATransaction.setCompletionBlock(completion) 45 | CATransaction.setAnimationTimingFunction(timingFunction) 46 | animations() 47 | CATransaction.commit() 48 | UIView.commitAnimations() 49 | } 50 | 51 | static func statusBarAppearanceUpdate(viewController: UIViewController, duration: TimeInterval, animations: (() -> Void)?) { 52 | if viewController.preferredStatusBarUpdateAnimation == .none || duration == .zero { 53 | if let animations = animations { 54 | animations() 55 | } 56 | viewController.setNeedsStatusBarAppearanceUpdate() 57 | } 58 | else { 59 | UIView.animate(withDuration: duration, animations: { 60 | if let animations = animations { 61 | animations() 62 | } 63 | viewController.setNeedsStatusBarAppearanceUpdate() 64 | }) 65 | } 66 | } 67 | 68 | static func isPhone() -> Bool { 69 | return UIDevice.current.userInterfaceIdiom == .phone 70 | } 71 | 72 | static func isPad() -> Bool { 73 | return UIDevice.current.userInterfaceIdiom == .pad 74 | } 75 | 76 | static func getKeyWindow() -> UIWindow? { 77 | if #available(iOS 13.0, *) { 78 | return UIApplication.shared.windows.first(where: { $0.isKeyWindow }) 79 | } else { 80 | return UIApplication.shared.keyWindow 81 | } 82 | } 83 | 84 | static func getStatusBarFrame() -> CGRect { 85 | if #available(iOS 13.0, *) { 86 | return getKeyWindow()?.windowScene?.statusBarManager?.statusBarFrame ?? .zero 87 | } else { 88 | return UIApplication.shared.statusBarFrame 89 | } 90 | } 91 | 92 | static func getInterfaceOrientation() -> UIInterfaceOrientation? { 93 | if #available(iOS 13.0, *) { 94 | return getKeyWindow()?.windowScene?.interfaceOrientation 95 | } else { 96 | return UIApplication.shared.statusBarOrientation 97 | } 98 | } 99 | 100 | static func getOppositeInterfaceOrientation() -> UIInterfaceOrientation? { 101 | guard let orientation = getInterfaceOrientation() else { return nil } 102 | 103 | if (orientation.isLandscape) { 104 | return .portrait 105 | } else { 106 | return .landscapeLeft 107 | } 108 | } 109 | 110 | static func isPortrait() -> Bool { 111 | return getInterfaceOrientation()?.isPortrait ?? true 112 | } 113 | 114 | static func isLandscape() -> Bool { 115 | return getInterfaceOrientation()?.isLandscape ?? false 116 | } 117 | 118 | static func setSideMenuController(_ sideMenuController: LGSideMenuController?, to viewController: UIViewController) { 119 | objc_setAssociatedObject(viewController, &Keys.sideMenuController, sideMenuController, .OBJC_ASSOCIATION_ASSIGN) 120 | } 121 | 122 | static func getSideMenuController(from viewController: UIViewController) -> LGSideMenuController? { 123 | return objc_getAssociatedObject(viewController, &Keys.sideMenuController) as? LGSideMenuController 124 | } 125 | 126 | static func canPerformSegue(_ viewController: UIViewController, withIdentifier identifier: String) -> Bool { 127 | guard let identifiers = viewController.value(forKey: "storyboardSegueTemplates") as? [NSObject] else { return false } 128 | return identifiers.contains { (object: NSObject) -> Bool in 129 | if let id = object.value(forKey: "_identifier") as? String { 130 | return id == identifier 131 | } else { 132 | return false 133 | } 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuSegue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuSegue.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | public final class LGSideMenuSegue: UIStoryboardSegue { 34 | 35 | public struct Identifier { 36 | public static let root = "root" 37 | public static let left = "left" 38 | public static let right = "right" 39 | } 40 | 41 | public override func perform() { 42 | guard let sideMenuController = self.source as? LGSideMenuController else { 43 | assert(false, "LGSideMenuSegue must have source as LGSideMenuController") 44 | return 45 | } 46 | 47 | switch identifier { 48 | case Identifier.root: 49 | sideMenuController.rootViewController = destination 50 | case Identifier.left: 51 | sideMenuController.leftViewController = destination 52 | case Identifier.right: 53 | sideMenuController.rightViewController = destination 54 | default: 55 | assert(false, "LGSideMenuSegue must have identifier either \"root\", \"left\" or \"right\"") 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuStatusBarBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuStatusBarBackgroundView.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import CoreGraphics 32 | import UIKit 33 | 34 | public final class LGSideMenuStatusBarBackgroundView: UIView { 35 | 36 | public internal(set) var fillColor: UIColor = .clear { 37 | didSet { 38 | setNeedsDisplay() 39 | } 40 | } 41 | 42 | public internal(set) var shadowColor: UIColor = .clear { 43 | didSet { 44 | setNeedsDisplay() 45 | } 46 | } 47 | 48 | public internal(set) var shadowBlur: CGFloat = .zero { 49 | didSet { 50 | setNeedsDisplay() 51 | } 52 | } 53 | 54 | public init() { 55 | super.init(frame: CGRect.zero) 56 | self.backgroundColor = .clear 57 | } 58 | 59 | required public init?(coder: NSCoder) { 60 | fatalError("init(coder:) has not been implemented") 61 | } 62 | 63 | public override func draw(_ rect: CGRect) { 64 | guard let context = UIGraphicsGetCurrentContext() else { return } 65 | 66 | // dx: 0.0, because shadow is much lighter at the corner 67 | let drawRect = rect.insetBy(dx: 0.0, dy: self.shadowBlur) 68 | 69 | let path = UIBezierPath(rect: drawRect) 70 | path.close() 71 | 72 | context.clear(rect) 73 | context.beginPath() 74 | context.addPath(path.cgPath) 75 | 76 | // Fill it black to draw proper shadow, then erase black internals and keep only shadow 77 | if shadowColor != .clear && self.shadowBlur > 0 { 78 | context.setShadow(offset: .zero, blur: self.shadowBlur, color: shadowColor.cgColor) 79 | context.setFillColor(UIColor.black.cgColor) 80 | context.fillPath() 81 | context.setShadow(offset: .zero, blur: .zero, color: nil) 82 | 83 | context.beginPath() 84 | context.addPath(path.cgPath) 85 | context.setFillColor(UIColor.clear.cgColor) 86 | context.setBlendMode(.clear) 87 | context.fillPath() 88 | context.setBlendMode(.normal) 89 | } 90 | 91 | // Fill it with proper color 92 | if self.fillColor != .clear { 93 | context.beginPath() 94 | context.addPath(path.cgPath) 95 | context.setFillColor(fillColor.cgColor) 96 | context.fillPath() 97 | } 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /LGSideMenuController/LGSideMenuWrapperView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LGSideMenuWrapperView.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | public final class LGSideMenuWrapperView: UIView { 34 | 35 | public var canLayoutSubviews = true 36 | 37 | public override func setNeedsLayout() { 38 | guard canLayoutSubviews else { return } 39 | super.setNeedsLayout() 40 | } 41 | 42 | public override func layoutIfNeeded() { 43 | guard canLayoutSubviews else { return } 44 | super.layoutIfNeeded() 45 | } 46 | 47 | public override func layoutSubviews() { 48 | guard canLayoutSubviews else { return } 49 | super.layoutSubviews() 50 | } 51 | 52 | public override func layoutSublayers(of layer: CALayer) { 53 | guard canLayoutSubviews else { return } 54 | super.layoutSublayers(of: layer) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /LGSideMenuController/UIViewController+LGSideMenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+LGSideMenuController.swift 3 | // LGSideMenuController 4 | // 5 | // 6 | // The MIT License (MIT) 7 | // 8 | // Copyright © 2015 Grigorii Lutkov 9 | // (https://github.com/Friend-LGA/LGSideMenuController) 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | // 29 | 30 | import Foundation 31 | import UIKit 32 | 33 | extension UIViewController { 34 | 35 | /// If the view controller or one of its ancestors is a child of a LGSideMenuController, this property contains the owning LGSideMenuController. 36 | /// This property is nil if the view controller is not embedded inside a LGSideMenuController. 37 | weak open var sideMenuController: LGSideMenuController? { 38 | if let controller = self as? LGSideMenuController { 39 | return controller 40 | } 41 | if let controller = LGSideMenuHelper.getSideMenuController(from: self) { 42 | return controller 43 | } 44 | if let controller = self.parent?.sideMenuController { 45 | return controller 46 | } 47 | return nil 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Grigorii Lutkov 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // Copyright © 2015 Grigorii Lutkov 7 | // (https://github.com/Friend-LGA/LGSideMenuController) 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import PackageDescription 29 | 30 | let package = Package( 31 | name: "LGSideMenuController", 32 | platforms: [ 33 | .iOS(.v9) 34 | ], products: [ 35 | .library(name: "LGSideMenuController", 36 | targets: ["LGSideMenuController"]) 37 | ], 38 | targets: [ 39 | .target(name: "LGSideMenuController", 40 | path: "LGSideMenuController") 41 | ] 42 | ) 43 | --------------------------------------------------------------------------------