diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dded8f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +# Change Log +All notable changes to the library will be documented in this file. + +## [Unreleased] +### Added +- UITabBarController and UINavigationController menu items support +- Rotation support +- Public methods documentation in code +- CHANGELOG file + +### Changed +- Renamed `showMenu()`/`hideMenu()` methods to `showSideMenu()`/`hideSideMenu()` +- Reworked [Sample](./Sample) + +### Fixed +- Customization of spring animation parameters: [Issue #17] +- Displaying horizontal images in [Sample](./Sample) + +**Migration notes** + +- To mark UIViewController as item of SideMenu you should adopt `SideMenuItemContent` protocol instead of inheritance. +To show menu you should call `showSideMenu()` method from this protocol. +```swift +import InteractiveSideMenu + +class KittyViewController: UIViewController, SideMenuItemContent { + + @IBAction func openMenu(_ sender: UIButton) { + showSideMenu() + } +} +``` +- To customize animation you should now update ```transitionOptions``` property in ```MenuContainerViewColtroller``` class. +```swift +override func viewDidLoad() { + super.viewDidLoad() + let screenSize: CGRect = UIScreen.main.bounds + self.transitionOptions = TransitionOptions(duration: 0.4, visibleContentWidth: screenSize.width / 6) + ... +} +``` + +- Now you have possibility to update customization settings using ```viewWillTransition(to:with:)``` mehod. +```swift +override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + var options = TransitionOptions() + options.duration = size.width < size.height ? 0.4 : 0.6 + options.visibleContentWidth = size.width / 6 + self.transitionOptions = options +} +``` + +## 1.0 - 2017-01-23 +### Added +- Left SideMenu with possibility to customize menu animation and content +- Sample demonstrating using SideMenu library +- README file + +[Unreleased]: https://github.com/handsomecode/InteractiveSideMenu/compare/master...feature/nav_and_tab_controllers_support +[Issue #17]: https://github.com/handsomecode/InteractiveSideMenu/issues/17 diff --git a/InteractiveSideMenu.podspec b/InteractiveSideMenu.podspec index e6be7c7..20336a7 100644 --- a/InteractiveSideMenu.podspec +++ b/InteractiveSideMenu.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "InteractiveSideMenu" - s.version = "1.0" + s.version = "2.0" s.summary = "Interactive Side Menu in Swift" s.homepage = "https://github.com/handsomecode/InteractiveSideMenu" s.license = "Apache 2.0 license" @@ -13,4 +13,4 @@ Pod::Spec.new do |s| s.source_files = "Sources/*.swift" s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3' } -end \ No newline at end of file +end diff --git a/InteractiveSideMenu.xcodeproj/project.pbxproj b/InteractiveSideMenu.xcodeproj/project.pbxproj index 737e26c..25be181 100644 --- a/InteractiveSideMenu.xcodeproj/project.pbxproj +++ b/InteractiveSideMenu.xcodeproj/project.pbxproj @@ -102,7 +102,7 @@ 881EF07A1E30FFE40035DEB4 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = Handsome; TargetAttributes = { 881EF0821E30FFE40035DEB4 = { @@ -258,12 +258,15 @@ buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = is.handsome.InteractiveSideMenu; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -279,11 +282,13 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = is.handsome.InteractiveSideMenu; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/InteractiveSideMenu.xcodeproj/xcshareddata/xcschemes/InteractiveSideMenu.xcscheme b/InteractiveSideMenu.xcodeproj/xcshareddata/xcschemes/InteractiveSideMenu.xcscheme index 6311ba6..714c2f6 100644 --- a/InteractiveSideMenu.xcodeproj/xcshareddata/xcschemes/InteractiveSideMenu.xcscheme +++ b/InteractiveSideMenu.xcodeproj/xcshareddata/xcschemes/InteractiveSideMenu.xcscheme @@ -1,6 +1,6 @@ TransitionOptionsBuilder? { - return TransitionOptionsBuilder() { builder in - builder.duration = 0.5 - builder.contentScale = 1 - } +override func viewDidLoad() { + super.viewDidLoad() + let screenSize: CGRect = UIScreen.main.bounds + self.transitionOptions = TransitionOptions(duration: 0.4, visibleContentWidth: screenSize.width / 6) + ... } - ``` +``` + +Also, you have possibility to update customization settings, e.g. set another options for landscape orientation. To do it, override ```viewWillTransition(to:with:)``` mehod and add desired parameters to ```transitionOptions``` property. +```swift +override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + var options = TransitionOptions() + options.duration = size.width < size.height ? 0.4 : 0.6 + options.visibleContentWidth = size.width / 6 + self.transitionOptions = options +} +``` + +Transition options could be used to set different parameters for Compact and Regular sizes as well. Implement ViewController's ```traitCollectionDidChange(_: )``` method to add these settings. See [Sample](./Sample) for more details. diff --git a/Sample/Sample.xcodeproj/project.pbxproj b/Sample/Sample.xcodeproj/project.pbxproj index 32375b7..4a5ceef 100644 --- a/Sample/Sample.xcodeproj/project.pbxproj +++ b/Sample/Sample.xcodeproj/project.pbxproj @@ -11,10 +11,12 @@ 881EF0AD1E3102E30035DEB4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 881EF0AB1E3102E30035DEB4 /* Main.storyboard */; }; 881EF0AF1E3102E30035DEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 881EF0AE1E3102E30035DEB4 /* Assets.xcassets */; }; 881EF0B21E3102E30035DEB4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 881EF0B01E3102E30035DEB4 /* LaunchScreen.storyboard */; }; - 881EF0BD1E31037D0035DEB4 /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF0B91E31037D0035DEB4 /* FirstViewController.swift */; }; + 881EF0BD1E31037D0035DEB4 /* KittyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF0B91E31037D0035DEB4 /* KittyViewController.swift */; }; 881EF0BE1E31037D0035DEB4 /* HostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF0BA1E31037D0035DEB4 /* HostViewController.swift */; }; 881EF0BF1E31037D0035DEB4 /* NavigationMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF0BB1E31037D0035DEB4 /* NavigationMenuViewController.swift */; }; - 881EF0C01E31037D0035DEB4 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881EF0BC1E31037D0035DEB4 /* SecondViewController.swift */; }; + 9A15C5481EDD62BC00C4FD75 /* InteractiveSideMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 881EF0C71E31049C0035DEB4 /* InteractiveSideMenu.framework */; }; + 9A15C5491EDD62BC00C4FD75 /* InteractiveSideMenu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 881EF0C71E31049C0035DEB4 /* InteractiveSideMenu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9A6755031EA8E30C00F0C71D /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A6755021EA8E30C00F0C71D /* TabBarViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -32,8 +34,29 @@ remoteGlobalIDString = 881EF0821E30FFE40035DEB4; remoteInfo = InteractiveSideMenu; }; + 9A15C54A1EDD62BC00C4FD75 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 881EF0C21E31049C0035DEB4 /* InteractiveSideMenu.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 881EF0821E30FFE40035DEB4; + remoteInfo = InteractiveSideMenu; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 9A15C54C1EDD62BC00C4FD75 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 9A15C5491EDD62BC00C4FD75 /* InteractiveSideMenu.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 881EF0A41E3102E30035DEB4 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 881EF0A71E3102E30035DEB4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -41,11 +64,11 @@ 881EF0AE1E3102E30035DEB4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 881EF0B11E3102E30035DEB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 881EF0B31E3102E30035DEB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 881EF0B91E31037D0035DEB4 /* FirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; + 881EF0B91E31037D0035DEB4 /* KittyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KittyViewController.swift; sourceTree = ""; }; 881EF0BA1E31037D0035DEB4 /* HostViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = HostViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 881EF0BB1E31037D0035DEB4 /* NavigationMenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationMenuViewController.swift; sourceTree = ""; }; - 881EF0BC1E31037D0035DEB4 /* SecondViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 881EF0C21E31049C0035DEB4 /* InteractiveSideMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = InteractiveSideMenu.xcodeproj; path = ../InteractiveSideMenu.xcodeproj; sourceTree = ""; }; + 9A6755021EA8E30C00F0C71D /* TabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9A15C5481EDD62BC00C4FD75 /* InteractiveSideMenu.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -94,8 +118,8 @@ 881EF0C11E3103A80035DEB4 /* MenuContentScreens */ = { isa = PBXGroup; children = ( - 881EF0B91E31037D0035DEB4 /* FirstViewController.swift */, - 881EF0BC1E31037D0035DEB4 /* SecondViewController.swift */, + 881EF0B91E31037D0035DEB4 /* KittyViewController.swift */, + 9A6755021EA8E30C00F0C71D /* TabBarViewController.swift */, ); name = MenuContentScreens; sourceTree = ""; @@ -118,11 +142,13 @@ 881EF0A01E3102E30035DEB4 /* Sources */, 881EF0A11E3102E30035DEB4 /* Frameworks */, 881EF0A21E3102E30035DEB4 /* Resources */, + 9A15C54C1EDD62BC00C4FD75 /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( 881EF0C91E3104AD0035DEB4 /* PBXTargetDependency */, + 9A15C54B1EDD62BC00C4FD75 /* PBXTargetDependency */, ); name = Sample; productName = Sample; @@ -197,10 +223,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 881EF0C01E31037D0035DEB4 /* SecondViewController.swift in Sources */, - 881EF0BD1E31037D0035DEB4 /* FirstViewController.swift in Sources */, + 881EF0BD1E31037D0035DEB4 /* KittyViewController.swift in Sources */, 881EF0BE1E31037D0035DEB4 /* HostViewController.swift in Sources */, 881EF0BF1E31037D0035DEB4 /* NavigationMenuViewController.swift in Sources */, + 9A6755031EA8E30C00F0C71D /* TabBarViewController.swift in Sources */, 881EF0A81E3102E30035DEB4 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -213,6 +239,11 @@ name = InteractiveSideMenu; targetProxy = 881EF0C81E3104AD0035DEB4 /* PBXContainerItemProxy */; }; + 9A15C54B1EDD62BC00C4FD75 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = InteractiveSideMenu; + targetProxy = 9A15C54A1EDD62BC00C4FD75 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -329,8 +360,10 @@ 881EF0B71E3102E30035DEB4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = is.handsome.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -341,8 +374,10 @@ 881EF0B81E3102E30035DEB4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = is.handsome.Sample; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json index b8236c6..d98c3c4 100644 --- a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,44 +1,148 @@ { "images" : [ { - "idiom" : "iphone", "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { + "size" : "29x29", "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { + "size" : "57x57", "idiom" : "iphone", - "size" : "60x60", + "filename" : "Icon-App-57x57@1x.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon-App-57x57@2x.png", "scale" : "2x" }, { + "size" : "60x60", "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50x50@1x.png", + "scale" : "1x" + }, + { + "size" : "50x50", + "idiom" : "ipad", + "filename" : "Icon-Small-50x50@2x.png", + "scale" : "2x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-App-72x72@1x.png", + "scale" : "1x" + }, + { + "size" : "72x72", + "idiom" : "ipad", + "filename" : "Icon-App-72x72@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" } ], "info" : { diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..db5801f Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..3074fc9 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..b43f4c3 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..9a49d7e Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..00f5c33 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..20d0710 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..3074fc9 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..ffbde26 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..85e1f3e Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..66a3245 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..cded045 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..85e1f3e Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..d55ee5e Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..682401b Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..89358ce Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..861d2a6 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..c620b48 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..cb40a41 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png new file mode 100644 index 0000000..fbd6677 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png differ diff --git a/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png new file mode 100644 index 0000000..53aa8bf Binary files /dev/null and b/Sample/Sample/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png differ diff --git a/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Black Cat-50.png b/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Black Cat-50.png new file mode 100644 index 0000000..b63a702 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Black Cat-50.png differ diff --git a/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Contents.json b/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Contents.json new file mode 100644 index 0000000..1fca31f --- /dev/null +++ b/Sample/Sample/Assets.xcassets/ic_tabbar_playful.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "Black Cat-50.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Cat-50.png b/Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Cat-50.png new file mode 100644 index 0000000..a0f7992 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Cat-50.png differ diff --git a/Sample/Sample/Assets.xcassets/surprised cat.imageset/Contents.json b/Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Contents.json similarity index 89% rename from Sample/Sample/Assets.xcassets/surprised cat.imageset/Contents.json rename to Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Contents.json index 591503a..8c9722e 100644 --- a/Sample/Sample/Assets.xcassets/surprised cat.imageset/Contents.json +++ b/Sample/Sample/Assets.xcassets/ic_tabbar_serious.imageset/Contents.json @@ -2,7 +2,6 @@ "images" : [ { "idiom" : "universal", - "filename" : "c11.jpg", "scale" : "1x" }, { @@ -11,6 +10,7 @@ }, { "idiom" : "universal", + "filename" : "Cat-50.png", "scale" : "3x" } ], diff --git a/Sample/Sample/Assets.xcassets/smiling cat.imageset/Contents.json b/Sample/Sample/Assets.xcassets/kitty.imageset/Contents.json similarity index 89% rename from Sample/Sample/Assets.xcassets/smiling cat.imageset/Contents.json rename to Sample/Sample/Assets.xcassets/kitty.imageset/Contents.json index 68812a3..553a53d 100644 --- a/Sample/Sample/Assets.xcassets/smiling cat.imageset/Contents.json +++ b/Sample/Sample/Assets.xcassets/kitty.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "c7.jpg", + "filename" : "kitty.jpg", "scale" : "1x" }, { diff --git a/Sample/Sample/Assets.xcassets/kitty.imageset/kitty.jpg b/Sample/Sample/Assets.xcassets/kitty.imageset/kitty.jpg new file mode 100644 index 0000000..5b912ea Binary files /dev/null and b/Sample/Sample/Assets.xcassets/kitty.imageset/kitty.jpg differ diff --git a/Sample/Sample/Assets.xcassets/playful cat.imageset/Contents.json b/Sample/Sample/Assets.xcassets/playful cat.imageset/Contents.json new file mode 100644 index 0000000..b9c255a --- /dev/null +++ b/Sample/Sample/Assets.xcassets/playful cat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cute-little-cat-Wallpaper-1.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sample/Sample/Assets.xcassets/playful cat.imageset/cute-little-cat-Wallpaper-1.jpg b/Sample/Sample/Assets.xcassets/playful cat.imageset/cute-little-cat-Wallpaper-1.jpg new file mode 100644 index 0000000..ebc6580 Binary files /dev/null and b/Sample/Sample/Assets.xcassets/playful cat.imageset/cute-little-cat-Wallpaper-1.jpg differ diff --git a/Sample/Sample/Assets.xcassets/serious cat.imageset/Contents.json b/Sample/Sample/Assets.xcassets/serious cat.imageset/Contents.json new file mode 100644 index 0000000..4a5715a --- /dev/null +++ b/Sample/Sample/Assets.xcassets/serious cat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "fcbf16daa5d8865.jpg", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sample/Sample/Assets.xcassets/serious cat.imageset/fcbf16daa5d8865.jpg b/Sample/Sample/Assets.xcassets/serious cat.imageset/fcbf16daa5d8865.jpg new file mode 100644 index 0000000..2fa126e Binary files /dev/null and b/Sample/Sample/Assets.xcassets/serious cat.imageset/fcbf16daa5d8865.jpg differ diff --git a/Sample/Sample/Assets.xcassets/smiling cat.imageset/c7.jpg b/Sample/Sample/Assets.xcassets/smiling cat.imageset/c7.jpg deleted file mode 100644 index add7fb0..0000000 Binary files a/Sample/Sample/Assets.xcassets/smiling cat.imageset/c7.jpg and /dev/null differ diff --git a/Sample/Sample/Assets.xcassets/surprised cat.imageset/c11.jpg b/Sample/Sample/Assets.xcassets/surprised cat.imageset/c11.jpg deleted file mode 100644 index ed81469..0000000 Binary files a/Sample/Sample/Assets.xcassets/surprised cat.imageset/c11.jpg and /dev/null differ diff --git a/Sample/Sample/Base.lproj/Main.storyboard b/Sample/Sample/Base.lproj/Main.storyboard index 10ccc4d..44ba7b6 100644 --- a/Sample/Sample/Base.lproj/Main.storyboard +++ b/Sample/Sample/Base.lproj/Main.storyboard @@ -1,19 +1,19 @@ - - + + - + - + - + @@ -22,7 +22,7 @@ - + @@ -42,8 +42,8 @@ - + @@ -51,47 +51,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -108,15 +67,19 @@ - + - + + + + + @@ -153,10 +116,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + diff --git a/Sample/Sample/HostViewController.swift b/Sample/Sample/HostViewController.swift index d4c0039..b34fc6d 100644 --- a/Sample/Sample/HostViewController.swift +++ b/Sample/Sample/HostViewController.swift @@ -19,33 +19,67 @@ import UIKit import InteractiveSideMenu +/* + HostViewController is container view controller, contains menu controller and the list of relevant view controllers. + + Responsible for creating and selecting menu items content controlers. + Has opportunity to show/hide side menu. + */ class HostViewController: MenuContainerViewController { override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } - + + override var prefersStatusBarHidden: Bool { + return false + } + override func viewDidLoad() { super.viewDidLoad() - - menuViewController = self.storyboard!.instantiateViewController(withIdentifier: "NavigationMenu") as! MenuViewController - - contentViewControllers = contentControllers() - - selectContentViewController(contentViewControllers.first!) + + let screenSize: CGRect = UIScreen.main.bounds + self.transitionOptions = TransitionOptions(duration: 0.4, visibleContentWidth: screenSize.width / 6) + + // Instantiate menu view controller by identifier + self.menuViewController = self.storyboard!.instantiateViewController(withIdentifier: "NavigationMenu") as! MenuViewController + + // Gather content items controllers + self.contentViewControllers = contentControllers() + + // Select initial content controller. It's needed even if the first view controller should be selected. + self.selectContentViewController(contentViewControllers.first!) } - - override func menuTransitionOptionsBuilder() -> TransitionOptionsBuilder? { - return TransitionOptionsBuilder() { builder in - builder.duration = 0.5 - builder.contentScale = 1 - } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + /* + Options to customize menu transition animation. + */ + var options = TransitionOptions() + + // Animation duration + options.duration = size.width < size.height ? 0.4 : 0.6 + + // Part of item content remaining visible on right when menu is shown + options.visibleContentWidth = size.width / 6 + self.transitionOptions = options } - - private func contentControllers() -> [MenuItemContentViewController] { - var contentList = [MenuItemContentViewController]() - contentList.append(self.storyboard?.instantiateViewController(withIdentifier: "First") as! MenuItemContentViewController) - contentList.append(self.storyboard?.instantiateViewController(withIdentifier: "Second") as! MenuItemContentViewController) + + private func contentControllers() -> [UIViewController] { + let controllersIdentifiers = ["Kitty", "TabBar"] + var contentList = [UIViewController]() + + /* + Instantiate items controllers from storyboard. + */ + for identifier in controllersIdentifiers { + if let viewController = self.storyboard?.instantiateViewController(withIdentifier: identifier) { + contentList.append(viewController) + } + } + return contentList } } diff --git a/Sample/Sample/Info.plist b/Sample/Sample/Info.plist index 93b96dc..d219a6a 100644 --- a/Sample/Sample/Info.plist +++ b/Sample/Sample/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion en + CFBundleDisplayName + SideMenu CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -31,6 +33,8 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight diff --git a/Sample/Sample/FirstViewController.swift b/Sample/Sample/KittyViewController.swift similarity index 64% rename from Sample/Sample/FirstViewController.swift rename to Sample/Sample/KittyViewController.swift index 9f30605..605fe76 100644 --- a/Sample/Sample/FirstViewController.swift +++ b/Sample/Sample/KittyViewController.swift @@ -1,5 +1,5 @@ // -// FirstViewController.swift +// KittyViewController.swift // // Copyright 2017 Handsome LLC // @@ -19,9 +19,14 @@ import UIKit import InteractiveSideMenu -class FirstViewController: MenuItemContentViewController { - - @IBAction func didOpenMenu(_ sender: UIButton) { - showMenu() +/* + KittyViewController is a controller relevant one of the side menu items. To indicate this it adopts `SideMenuItemContent` protocol. + */ +class KittyViewController: UIViewController, SideMenuItemContent { + + /* Show side menu on menu button click + */ + @IBAction func openMenu(_ sender: UIButton) { + showSideMenu() } } diff --git a/Sample/Sample/NavigationMenuViewController.swift b/Sample/Sample/NavigationMenuViewController.swift index 7987068..7431eb0 100644 --- a/Sample/Sample/NavigationMenuViewController.swift +++ b/Sample/Sample/NavigationMenuViewController.swift @@ -19,45 +19,55 @@ import UIKit import InteractiveSideMenu +/* + Menu controller is responsible for creating its content and showing/hiding menu using 'menuContainerViewController' property. + */ class NavigationMenuViewController: MenuViewController { - let kItemsCount = 2 - let kCellReuseIdentifier = "UITableViewCell" - let cats = ["Smiling Cat", "Surprised Cat"] + let kCellReuseIdentifier = "MenuCell" + let menuItems = ["Kitty", "TabBar cats"] @IBOutlet weak var tableView: UITableView! + + override var prefersStatusBarHidden: Bool { + return false + } override func viewDidLoad() { super.viewDidLoad() - - tableView.delegate = self - tableView.dataSource = self + + // Select the initial row tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: false, scrollPosition: UITableViewScrollPosition.none) } } - +/* + Extention of `NavigationMenuViewController` class, implements table view delegates methods. + */ extension NavigationMenuViewController: UITableViewDelegate, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return kItemsCount + return menuItems.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: kCellReuseIdentifier, for: indexPath) - cell.textLabel?.text = cats[indexPath.row] + cell.textLabel?.text = menuItems[indexPath.row] return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - DispatchQueue.main.async { - guard let menuContainerViewController = self.menuContainerViewController else { return } - menuContainerViewController.selectContentViewController(menuContainerViewController.contentViewControllers[indexPath.row]) - menuContainerViewController.hideMenu() + + guard let menuContainerViewController = self.menuContainerViewController else { + return } + + menuContainerViewController.selectContentViewController(menuContainerViewController.contentViewControllers[indexPath.row]) + menuContainerViewController.hideSideMenu() } } diff --git a/Sample/Sample/SecondViewController.swift b/Sample/Sample/SecondViewController.swift deleted file mode 100644 index 3aacc2f..0000000 --- a/Sample/Sample/SecondViewController.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SecondViewController.swift -// -// Copyright 2017 Handsome LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import UIKit -import InteractiveSideMenu - -class SecondViewController: MenuItemContentViewController { - - @IBAction func didOpenMenu(_ sender: UIButton) { - showMenu() - } -} diff --git a/Sample/Sample/TabBarViewController.swift b/Sample/Sample/TabBarViewController.swift new file mode 100644 index 0000000..83eac53 --- /dev/null +++ b/Sample/Sample/TabBarViewController.swift @@ -0,0 +1,63 @@ +// +// TabBarViewController.swift +// +// Copyright 2017 Handsome LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import InteractiveSideMenu + +/* + TabBarViewController is a controller relevant one of the side menu items. To indicate this it adopts `SideMenuItemContent` protocol. + */ +class TabBarViewController: UITabBarController, SideMenuItemContent { + +} + +/* + The first controller of tab bar. + */ +class FirstViewController: UIViewController { + + /* + Show menu on click if connected tab bar controller adopts proper protocol. + */ + @IBAction func openMenu(_ sender: UIButton) { + + if let menuItemViewController = self.tabBarController as? SideMenuItemContent { + menuItemViewController.showSideMenu() + } + } +} + +/* + The second controller of tab bar. + */ +class SecondViewController: UIViewController { + + /* + Show menu on click if connected tab bar controller adopts proper protocol. + */ + @IBAction func openMenu(_ sender: UIButton) { + + if let menuItemViewController = self.tabBarController as? SideMenuItemContent { + menuItemViewController.showSideMenu() + } + } + +} + + + diff --git a/Sources/MenuAnimator.swift b/Sources/MenuAnimator.swift index 237e830..4bd3671 100644 --- a/Sources/MenuAnimator.swift +++ b/Sources/MenuAnimator.swift @@ -18,133 +18,200 @@ import UIKit -public class TransitionOptionsBuilder { - +/** + Options that define menu transition settings. + Class lets user customize their transitioning animation: duration, content scale, visible content width, animationOptions, using spring animation or not and params if yes. + */ +public struct TransitionOptions { + + /** + The duration of showing/hiding menu animation. Default value is 0.5. + */ public var duration: TimeInterval = 0.5 { willSet(newDuration) { if(newDuration < 0) { - fatalError("Invalid duration value (\(newDuration)). It must be non negative") + fatalError("Invalid `duration` value (\(newDuration)). It must be non negative") } } } - + + /** + The scale factor of content menu item when the menu is opened. Default value is 0.88. + */ public var contentScale: CGFloat = 0.88 { willSet(newContentScale) { if(newContentScale < 0) { - fatalError("Invalid contentScale value (\(newContentScale)). It must be non negative") + fatalError("Invalid `contentScale` value (\(newContentScale)). It must be non negative") } } } - + + /// The width of visible part of content menu item when the menu is shown. Default value is 56 points. public var visibleContentWidth: CGFloat = 56.0 - public var useFinishingSpringOption = true - public var useCancelingSpringOption = true - public var finishingSpringOption = SpringOption(presentSpringParams: SpringParams(dampingRatio: 0.7, velocity: 0.3), - dismissSpringParams: SpringParams(dampingRatio: 0.8, velocity: 0.3)) - public var cancelingSpringOption = SpringOption(presentSpringParams: SpringParams(dampingRatio: 0.7, velocity: 0.0), + + /// Defines if spring animation will be used on menu transition finishing. Default value is true. + public var useFinishingSpringSettings = true + + /// Defines if spring animation will be used on menu transition cancelling (when user let draggable view to go back to the begining position). Default value is true. + public var useCancellingSpringSettings = true + + /// Spring animation settings if `useFinishingSpringSettings` is set to true. + public var finishingSpringSettings = SpringSettings(presentSpringParams: SpringParams(dampingRatio: 0.7, velocity: 0.3), + dismissSpringParams: SpringParams(dampingRatio: + 0.8, velocity: 0.3)) + /// Spring animation settings if `useCancellingSpringSettings` is set to true. + public var cancellingSpringSettings = SpringSettings(presentSpringParams: SpringParams(dampingRatio: 0.7, velocity: 0.0), dismissSpringParams: SpringParams(dampingRatio: 0.7, velocity: 0.0)) + + /// Regular view animation options. Default value is `curveEaseInOut`. public var animationOptions: UIViewAnimationOptions = .curveEaseInOut - - public init(building: (TransitionOptionsBuilder) -> Void) { - building(self) + + public init() { } - - class func defaultOptionsBuilder() -> TransitionOptionsBuilder { - return TransitionOptionsBuilder(){_ in } + + public init(duration: TimeInterval) { + self.duration = duration } -} + public init(contentScale: CGFloat) { + self.contentScale = contentScale + } -public struct SpringParams { - let dampingRatio: CGFloat - let velocity: CGFloat -} + public init(visibleContentWidth: CGFloat) { + self.visibleContentWidth = visibleContentWidth + } + public init(duration: TimeInterval, contentScale: CGFloat) { + self.duration = duration + self.contentScale = contentScale + } + + public init(duration: TimeInterval, visibleContentWidth: CGFloat) { + self.duration = duration + self.visibleContentWidth = visibleContentWidth + } -public struct SpringOption { + public init(contentScale: CGFloat, visibleContentWidth: CGFloat) { + self.contentScale = contentScale + self.visibleContentWidth = visibleContentWidth + } + + public init(duration: TimeInterval, contentScale: CGFloat, visibleContentWidth: CGFloat) { + self.duration = duration + self.contentScale = contentScale + self.visibleContentWidth = visibleContentWidth + } +} + +/** + Settings of spring animation for presenting and dismissing actions. + */ +public struct SpringSettings { let presentSpringParams: SpringParams let dismissSpringParams: SpringParams + + public init(presentSpringParams: SpringParams, dismissSpringParams: SpringParams) { + self.presentSpringParams = presentSpringParams + self.dismissSpringParams = dismissSpringParams + } } +/** + Basic spring params. + */ +public struct SpringParams { + + /// The damping ratio from 0 to 1 for the spring animation as it approaches its quiescent state. + let dampingRatio: CGFloat + + /// The initial spring velocity. For smooth start to the animation, match this value to the view’s velocity. + let velocity: CGFloat + public init(dampingRatio: CGFloat, velocity: CGFloat) { + self.dampingRatio = dampingRatio + self.velocity = velocity + } +} + +/** + Delegate of menu transitioning actions. + */ class MenuTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate { var interactiveTransition: MenuInteractiveTransition! func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + if interactiveTransition == nil { + fatalError("Invalid `interactiveTransition` value. This property should not be nil") + } interactiveTransition.present = true return interactiveTransition } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + if interactiveTransition == nil { + fatalError("Invalid `interactiveTransition` value. This property should not be nil") + } interactiveTransition.present = false return interactiveTransition } func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + if interactiveTransition == nil { + fatalError("Invalid `interactiveTransition` value. This property should not be nil") + } return interactiveTransition.interactionInProgress ? interactiveTransition : nil } func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + if interactiveTransition == nil { + fatalError("Invalid `interactiveTransition` value. This property should not be nil") + } return interactiveTransition.interactionInProgress ? interactiveTransition : nil } } +/** + The side menu interactive transitioning implementation. + */ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransitioning, UIViewControllerAnimatedTransitioning { typealias Action = () -> () //MARK: - Properties + // var present: Bool = false var interactionInProgress: Bool = false - - private var transitionDuration: TimeInterval! - private var scaleDiff: CGFloat! - private var visibleContentWidth: CGFloat! - private var useFinishingSpringOption: Bool! - private var useCancelingSpringOption: Bool! - private var finishingSpringOption: SpringOption! - private var cancelingSpringOption: SpringOption! - private var animationOptions: UIViewAnimationOptions! - - private var presentAction: Action! - private var dismissAction: Action! + + var options = TransitionOptions() + private let presentAction: Action + private let dismissAction: Action private var transitionShouldStarted = false private var transitionStarted = false - private var transitionContext: UIViewControllerContextTransitioning! - private var contentSnapshotView: UIView! + private var transitionContext: UIViewControllerContextTransitioning? + private var contentSnapshotView: UIView? - private var tapRecognizer: UITapGestureRecognizer! - private var panRecognizer: UIPanGestureRecognizer! + private var tapRecognizer: UITapGestureRecognizer? + private var panRecognizer: UIPanGestureRecognizer? - required init(presentAction: @escaping Action, dismissAction: @escaping Action, transitionOptionsBuilder: TransitionOptionsBuilder? = nil) { - super.init() - + required init(presentAction: @escaping Action, dismissAction: @escaping Action) { + self.presentAction = presentAction self.dismissAction = dismissAction - - let options = transitionOptionsBuilder ?? TransitionOptionsBuilder.defaultOptionsBuilder() - - self.transitionDuration = options.duration - self.scaleDiff = 1 - options.contentScale - self.visibleContentWidth = options.visibleContentWidth - self.useFinishingSpringOption = options.useFinishingSpringOption - self.useCancelingSpringOption = options.useCancelingSpringOption - self.finishingSpringOption = options.finishingSpringOption - self.cancelingSpringOption = options.cancelingSpringOption - self.animationOptions = options.animationOptions + super.init() } //MARK: - Delegate methods + // func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { startTransition(transitionContext: transitionContext) } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return transitionDuration + return options.duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { @@ -165,11 +232,18 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition handlePan(recognizer: recognizer) } + //MARK: - Private methods + // private func createSnapshotView(from: UIView) -> UIView { - let snapshotView = from.snapshotView(afterScreenUpdates: true)! - snapshotView.frame = from.frame + guard let snapshotView = from.snapshotView(afterScreenUpdates: true) else { + print("Invalid snapshot view. Default color will be used") + let placeholderView = UIView() + placeholderView.frame = from.frame + placeholderView.backgroundColor = from.backgroundColor + addShadow(toView: placeholderView) + return placeholderView + } addShadow(toView: snapshotView) - return snapshotView } @@ -188,8 +262,12 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition self.transitionContext = transitionContext - let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! - let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { + fatalError("Invalid fromViewController key. Can't start transition") + } + guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { + fatalError("Invalid toViewController key. Can't start transition") + } let containerView = transitionContext.containerView let screenWidth = containerView.frame.size.width @@ -197,23 +275,33 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition if present { containerView.insertSubview(toViewController.view, belowSubview: fromViewController.view) - self.tapRecognizer = UITapGestureRecognizer(target: toViewController as! MenuViewController, - action: #selector(MenuViewController.handleTap(recognizer:))) - - self.panRecognizer = UIPanGestureRecognizer(target: self, + if self.tapRecognizer == nil { + + guard toViewController is MenuViewController else { + fatalError("Invalid toViewController type. It must be MenuViewController") + } + self.tapRecognizer = UITapGestureRecognizer(target: toViewController, + action: #selector(MenuViewController.handleTap(recognizer:))) + self.panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(MenuInteractiveTransition.handlePanDismission(recognizer:))) + } contentSnapshotView = createSnapshotView(from: fromViewController.view) + + guard let contentSnapshotView = self.contentSnapshotView else { + fatalError("Invalid `contentSnapshotView` value. This property should not be nil") + } + containerView.addSubview(contentSnapshotView) fromViewController.view.isHidden = true } else { containerView.addSubview(toViewController.view) - toViewController.view.transform = CGAffineTransform(scaleX: 1 - scaleDiff, y: 1 - scaleDiff) + toViewController.view.transform = CGAffineTransform(scaleX: options.contentScale, y: options.contentScale) addShadow(toView: toViewController.view) - let newOrigin = CGPoint(x: screenWidth - visibleContentWidth, y: toViewController.view.frame.origin.y) + let newOrigin = CGPoint(x: screenWidth - options.visibleContentWidth, y: toViewController.view.frame.origin.y) let rect = CGRect(origin: newOrigin, size: toViewController.view.frame.size) toViewController.view.frame = rect @@ -224,14 +312,21 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition } private func updateTransition(percentComplete: CGFloat) { + guard let transitionContext = self.transitionContext else { + fatalError("Invalid `transitionContext` value. This property should not be nil") + } let containerView = transitionContext.containerView let screenWidth = containerView.frame.size.width - let totalWidth = screenWidth - visibleContentWidth + let totalWidth = screenWidth - options.visibleContentWidth + + guard let contentSnapshotView = self.contentSnapshotView else { + fatalError("Invalid `contentSnapshotView` value. This property should not be nil") + } if present { - let newScale = 1 - (scaleDiff * percentComplete) + let newScale = 1 - (1 - options.contentScale) * percentComplete let newX = totalWidth * percentComplete contentSnapshotView.transform = CGAffineTransform(scaleX: newScale, y: newScale) @@ -243,10 +338,12 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition } else { contentSnapshotView.isHidden = true - let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { + fatalError("Invalid toViewController key. Can't update transition") + } let newX = totalWidth * (1 - percentComplete) - let newScale = 1 - scaleDiff + (scaleDiff * percentComplete) + let newScale = options.contentScale + (1 - options.contentScale) * percentComplete toViewController.view.transform = CGAffineTransform(scaleX: newScale, y: newScale) let newOrigin = CGPoint(x: newX, y: toViewController.view.frame.origin.y) @@ -262,17 +359,31 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition let animation : () -> Void = { [weak self] in self?.updateTransition(percentComplete: 1.0) } let completion : (Bool) -> Void = { [weak self] _ in if let transition = self { - let fromViewController = transition.transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! - let toViewController = transition.transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! - + guard let transitionContext = transition.transitionContext else { + fatalError("Invalid `transition.transitionContext` value. This property should not be nil") + } + guard let contentSnapshotView = transition.contentSnapshotView else { + fatalError("Invalid `transition.contentSnapshotView` value. This property should not be nil") + } + guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { + fatalError("Invalid fromViewController key. Can't finish transition") + } + guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { + fatalError("Invalid toViewController key. Can't finish transition") + } + if transition.present { fromViewController.view.isHidden = false - - transition.contentSnapshotView.removeFromSuperview() - transition.contentSnapshotView.addGestureRecognizer(transition.panRecognizer) - transition.contentSnapshotView.addGestureRecognizer(transition.tapRecognizer) - - toViewController.view.addSubview(transition.contentSnapshotView) + contentSnapshotView.removeFromSuperview() + + if let panRecognizer = transition.panRecognizer { + contentSnapshotView.addGestureRecognizer(panRecognizer) + } + if let tapRecognizer = transition.tapRecognizer { + contentSnapshotView.addGestureRecognizer(tapRecognizer) + } + + toViewController.view.addSubview(contentSnapshotView) } else { toViewController.view.isHidden = false transition.removeShadow(fromView: toViewController.view) @@ -281,26 +392,22 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition toViewController.view.isUserInteractionEnabled = true fromViewController.view.isUserInteractionEnabled = true - transition.transitionContext.completeTransition(true) + transitionContext.completeTransition(true) } } - guard let useFinishingSpringOption = self.useFinishingSpringOption else { - return - } - - if useFinishingSpringOption { - UIView.animate(withDuration: transitionDuration - transitionDuration * Double(currentPercentComplete), + if options.useFinishingSpringSettings { + UIView.animate(withDuration: options.duration - options.duration * Double(currentPercentComplete), delay: 0, - usingSpringWithDamping: present ? finishingSpringOption.presentSpringParams.dampingRatio : finishingSpringOption.dismissSpringParams.dampingRatio, - initialSpringVelocity: present ? finishingSpringOption.presentSpringParams.velocity : finishingSpringOption.dismissSpringParams.velocity, - options: animationOptions, + usingSpringWithDamping: present ? options.finishingSpringSettings.presentSpringParams.dampingRatio : options.finishingSpringSettings.dismissSpringParams.dampingRatio, + initialSpringVelocity: present ? options.finishingSpringSettings.presentSpringParams.velocity : options.finishingSpringSettings.dismissSpringParams.velocity, + options: options.animationOptions, animations: animation, completion: completion) } else { - UIView.animate(withDuration: transitionDuration - transitionDuration * Double(currentPercentComplete), + UIView.animate(withDuration: options.duration - options.duration * Double(currentPercentComplete), delay: 0, - options: animationOptions, + options: options.animationOptions, animations: animation, completion: completion) } @@ -312,55 +419,65 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition let animation : () -> Void = { [weak self] in self?.updateTransition(percentComplete: 0) } let completion : (Bool) -> Void = { [weak self] _ in if let transition = self { - let fromViewController = transition.transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)! - let toViewController = transition.transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)! + + guard let transitionContext = transition.transitionContext else { + fatalError("Invalid `transition.transitionContext` value. This property should not be nil") + } + guard let contentSnapshotView = transition.contentSnapshotView else { + fatalError("Invalid `transition.contentSnapshotView` value. This property should not be nil") + } + guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { + fatalError("Invalid fromViewController key. Can't cancel transition") + } + guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { + fatalError("Invalid toViewController key. Can't cancel transition") + } if transition.present { fromViewController.view.isHidden = false } else { toViewController.view.removeFromSuperview() - transition.contentSnapshotView.isHidden = false + contentSnapshotView.isHidden = false fromViewController.view.isUserInteractionEnabled = true } toViewController.view.isUserInteractionEnabled = true fromViewController.view.isUserInteractionEnabled = true - transition.transitionContext.completeTransition(false) + transitionContext.completeTransition(false) } } - guard let useCancelingSpringOption = self.useCancelingSpringOption else { - return - } - - if useCancelingSpringOption { - UIView.animate(withDuration: transitionDuration - transitionDuration * Double(currentPercentComplete), + if options.useCancellingSpringSettings { + UIView.animate(withDuration: options.duration - options.duration * Double(currentPercentComplete), delay: 0, - usingSpringWithDamping: present ? cancelingSpringOption.presentSpringParams.dampingRatio : cancelingSpringOption.dismissSpringParams.dampingRatio, - initialSpringVelocity: present ? cancelingSpringOption.presentSpringParams.velocity : cancelingSpringOption.dismissSpringParams.velocity, - options: animationOptions, + usingSpringWithDamping: present ? options.cancellingSpringSettings.presentSpringParams.dampingRatio : options.cancellingSpringSettings.dismissSpringParams.dampingRatio, + initialSpringVelocity: present ? options.cancellingSpringSettings.presentSpringParams.velocity : options.cancellingSpringSettings.dismissSpringParams.velocity, + options: options.animationOptions, animations: animation, completion: completion) } else { - UIView.animate(withDuration: transitionDuration - transitionDuration * Double(currentPercentComplete), + UIView.animate(withDuration: options.duration - options.duration * Double(currentPercentComplete), delay: 0, - options: animationOptions, + options: options.animationOptions, animations: animation, completion: completion) } } private func handlePan(recognizer: UIPanGestureRecognizer) { - let translation = recognizer.translation(in: recognizer.view!.superview!) - let dx = translation.x / recognizer.view!.bounds.width + guard let recognizerView = recognizer.view else { + fatalError("Invalid recognizer view value") + } + let translation = recognizer.translation(in: recognizerView) + let dx = translation.x / recognizerView.bounds.width let progress: CGFloat = abs(dx) - var velocity = recognizer.velocity(in: recognizer.view!.superview!).x + var velocity = recognizer.velocity(in: recognizerView).x if !present { velocity = -velocity } - + switch recognizer.state { case .began: interactionInProgress = true @@ -376,12 +493,18 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition case .changed: if transitionStarted && (present && dx > 0 || !present && dx < 0) { + guard let transitionContext = transitionContext else { + fatalError("Invalid `transitionContext` value. This property should not be nil") + } updateTransition(percentComplete: progress) transitionContext.updateInteractiveTransition(progress) } case .cancelled, .ended: if transitionStarted { + guard let transitionContext = transitionContext else { + fatalError("Invalid `transitionContext` value. This property should not be nil") + } if progress > 0.4 && velocity >= 0 || progress > 0.01 && velocity > 100 { finishTransition(currentPercentComplete: progress) transitionContext.finishInteractiveTransition() @@ -393,9 +516,12 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition } else if transitionShouldStarted && !transitionStarted { if transitionStarted { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + guard let transitionContext = self.transitionContext else { + fatalError("Invalid `transitionContext` value. This property should not be nil") + } if self.transitionStarted { self.cancelTransition(currentPercentComplete: progress) - self.transitionContext.cancelInteractiveTransition() + transitionContext.cancelInteractiveTransition() } } } @@ -406,6 +532,6 @@ class MenuInteractiveTransition: NSObject, UIViewControllerInteractiveTransition default: break - } } + } } diff --git a/Sources/MenuContainerViewController.swift b/Sources/MenuContainerViewController.swift index 3b60e82..f153394 100644 --- a/Sources/MenuContainerViewController.swift +++ b/Sources/MenuContainerViewController.swift @@ -18,75 +18,135 @@ import UIKit +/** + Container for menu view controller. + */ open class MenuContainerViewController: UIViewController { - + + /** + The view controller for side menu. + */ public var menuViewController: MenuViewController! { didSet { + if menuViewController == nil { + fatalError("Invalid `menuViewController` value. It should not be nil") + } menuViewController.menuContainerViewController = self menuViewController.transitioningDelegate = self.navigationMenuTransitionDelegate menuViewController.navigationMenuTransitionDelegate = self.navigationMenuTransitionDelegate } } - public var contentViewControllers: [MenuItemContentViewController]! - - private weak var currentContentViewController: MenuItemContentViewController? - private var navigationMenuTransitionDelegate: MenuTransitioningDelegate! - - open func menuTransitionOptionsBuilder() -> TransitionOptionsBuilder? { - return nil + /** + The options defining side menu transitioning. + Could be set at any time of controller lifecycle. + */ + public var transitionOptions: TransitionOptions { + get { + return navigationMenuTransitionDelegate?.interactiveTransition?.options ?? TransitionOptions() + } + set { + navigationMenuTransitionDelegate?.interactiveTransition?.options = newValue + } } - + + /** + The list of all content view controllers corresponding to side menu items. + */ + public var contentViewControllers: [UIViewController]! + + /** + Shows left side menu. + */ + public func showSideMenu() { + presentNavigationMenu() + } + + /** + Hides left side menu. + Controller from the right side will be visible. + */ + public func hideSideMenu() { + self.dismiss(animated: true, completion: nil) + } + + /** + Embeds menu item content view controller. + + - parameter selectedContentVC: view controller to be embedded + */ + public func selectContentViewController(_ selectedContentVC: UIViewController) { + if let currentContentVC = self.currentContentViewController { + if currentContentVC != selectedContentVC { + currentContentVC.view.removeFromSuperview() + currentContentVC.removeFromParentViewController() + setCurrentView(selectedContentVC) + } + } else { + setCurrentView(selectedContentVC) + } + } + + // MARK: - Controller lifecycle + // override open func viewDidLoad() { super.viewDidLoad() - + navigationMenuTransitionDelegate = MenuTransitioningDelegate() navigationMenuTransitionDelegate.interactiveTransition = MenuInteractiveTransition( presentAction: { [weak self] in self?.presentNavigationMenu() }, - dismissAction: { [weak self] in self?.dismiss(animated: true, completion: nil) }, - transitionOptionsBuilder: menuTransitionOptionsBuilder() + dismissAction: { [weak self] in self?.dismiss(animated: true, completion: nil) } ) - + let screenEdgePanRecognizer = UIScreenEdgePanGestureRecognizer( target: navigationMenuTransitionDelegate.interactiveTransition, action: #selector(MenuInteractiveTransition.handlePanPresentation(recognizer:)) ) - + screenEdgePanRecognizer.edges = .left self.view.addGestureRecognizer(screenEdgePanRecognizer) } - - public func showMenu() { - presentNavigationMenu() - } - - public func hideMenu() { - self.dismiss(animated: true, completion: nil) - } - - public func selectContentViewController(_ selectedContentVC: MenuItemContentViewController) { - if let currentContentVC = self.currentContentViewController { - if currentContentVC != selectedContentVC { - currentContentVC.view.removeFromSuperview() - currentContentVC.removeFromParentViewController() - - self.currentContentViewController = selectedContentVC - - setCurrentView() + + override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + let viewBounds = CGRect(x:0, y:0, width:size.width, height:size.height) + let viewCenter = CGPoint(x:size.width/2, y:size.height/2) + coordinator.animate(alongsideTransition: { _ in + if self.menuViewController == nil { + fatalError("Invalid `menuViewController` value. It should not be nil") } - } else { - self.currentContentViewController = selectedContentVC - setCurrentView() - } + self.menuViewController.view.bounds = viewBounds + self.menuViewController.view.center = viewCenter + self.view.bounds = viewBounds + self.view.center = viewCenter + self.hideSideMenu() + }, completion: nil) } - - private func setCurrentView() { - self.addChildViewController(currentContentViewController!) - self.view.addSubview(currentContentViewController!.view) - self.view.addSubviewWithFullSizeConstraints(view: currentContentViewController!.view) + + // MARK: - Private + // + private weak var currentContentViewController: UIViewController? + private var navigationMenuTransitionDelegate: MenuTransitioningDelegate! + + /** + Adds proper content view controller as a child. + + - parameter selectedContentVC: view controller to be added + */ + private func setCurrentView(_ selectedContentVC: UIViewController) { + self.addChildViewController(selectedContentVC) + self.view.addSubviewWithFullSizeConstraints(view: selectedContentVC.view) + self.currentContentViewController = selectedContentVC } - + + /** + Presents left side menu. + */ private func presentNavigationMenu() { + if menuViewController == nil { + fatalError("Invalid `menuViewController` value. It should not be nil") + } self.present(menuViewController, animated: true, completion: nil) } } diff --git a/Sources/MenuItemContentViewController.swift b/Sources/MenuItemContentViewController.swift index 517629b..bccea74 100644 --- a/Sources/MenuItemContentViewController.swift +++ b/Sources/MenuItemContentViewController.swift @@ -18,13 +18,25 @@ import UIKit -open class MenuItemContentViewController: UIViewController { - - private var menuContainerViewController: MenuContainerViewController { - return self.parent as! MenuContainerViewController - } - - public func showMenu() { - menuContainerViewController.showMenu() +/** + The protocol to indicate item of side menu. Every menu item should adopt this protocol. + */ +public protocol SideMenuItemContent { + + /** + Shows left side menu. + */ + func showSideMenu() +} + +/** + The extention of SideMenuItemContent protocol implementing showSideMenu() method for UIViewCntroller class. + */ +extension SideMenuItemContent where Self: UIViewController { + + public func showSideMenu() { + if let menuContainerViewController = self.parent as? MenuContainerViewController { + menuContainerViewController.showSideMenu() + } } } diff --git a/Sources/MenuViewController.swift b/Sources/MenuViewController.swift index 8920dd6..3ef2231 100644 --- a/Sources/MenuViewController.swift +++ b/Sources/MenuViewController.swift @@ -21,10 +21,10 @@ import UIKit open class MenuViewController: UIViewController { public weak var menuContainerViewController: MenuContainerViewController? - var navigationMenuTransitionDelegate: MenuTransitioningDelegate! + var navigationMenuTransitionDelegate: MenuTransitioningDelegate? func handleTap(recognizer: UIGestureRecognizer){ - menuContainerViewController?.hideMenu() + menuContainerViewController?.hideSideMenu() } }