├── .gitignore ├── Default-568h@2x.png ├── JASidePanels.xcodeproj ├── .gitignore ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── JASidePanels ├── Demo │ ├── JAAppDelegate.h │ ├── JAAppDelegate.m │ ├── JACenterViewController.h │ ├── JACenterViewController.m │ ├── JADebugViewController.h │ ├── JADebugViewController.m │ ├── JALeftViewController.h │ ├── JALeftViewController.m │ ├── JARightViewController.h │ ├── JARightViewController.m │ ├── JASidePanels-Info.plist │ ├── JASidePanels-Prefix.pch │ └── main.m ├── Source │ ├── JASidePanelController.h │ ├── JASidePanelController.m │ ├── UIViewController+JASidePanel.h │ └── UIViewController+JASidePanel.m └── en.lproj │ └── InfoPlist.strings └── README.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotosleep/JASidePanels/5913385464f1aec77c20783dbfd4985cfd3f89d6/Default-568h@2x.png -------------------------------------------------------------------------------- /JASidePanels.xcodeproj/.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | -------------------------------------------------------------------------------- /JASidePanels.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5C49572716C555F700250332 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5C49572616C555F700250332 /* Default-568h@2x.png */; }; 11 | 95322A731517AEF40069C8D7 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 95322A701517AEEB0069C8D7 /* QuartzCore.framework */; }; 12 | 9561ACB2151286EE005889C6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9561ACB1151286EE005889C6 /* UIKit.framework */; }; 13 | 9561ACB4151286EE005889C6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9561ACB3151286EE005889C6 /* Foundation.framework */; }; 14 | 9561ACB6151286EE005889C6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9561ACB5151286EE005889C6 /* CoreGraphics.framework */; }; 15 | 9561ACBC151286EE005889C6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9561ACBA151286EE005889C6 /* InfoPlist.strings */; }; 16 | 9561AD0F15130EE2005889C6 /* JASidePanelController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD0E15130EE2005889C6 /* JASidePanelController.m */; }; 17 | 9561AD1915130F10005889C6 /* JAAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD1215130F10005889C6 /* JAAppDelegate.m */; }; 18 | 9561AD1A15130F10005889C6 /* JACenterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD1415130F10005889C6 /* JACenterViewController.m */; }; 19 | 9561AD1B15130F10005889C6 /* JALeftViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD1615130F10005889C6 /* JALeftViewController.m */; }; 20 | 9561AD1C15130F10005889C6 /* JARightViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD1815130F10005889C6 /* JARightViewController.m */; }; 21 | 9561AD2115130F2A005889C6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9561AD1F15130F2A005889C6 /* main.m */; }; 22 | 9568132B16373ACB007B3602 /* JADebugViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9568132A16373ACB007B3602 /* JADebugViewController.m */; }; 23 | 95BCC45A15F081130009F1DA /* UIViewController+JASidePanel.m in Sources */ = {isa = PBXBuildFile; fileRef = 95BCC45915F081130009F1DA /* UIViewController+JASidePanel.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 5C49572616C555F700250332 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 28 | 95322A701517AEEB0069C8D7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 29 | 9561ACAD151286EE005889C6 /* JASidePanels.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JASidePanels.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 9561ACB1151286EE005889C6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | 9561ACB3151286EE005889C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | 9561ACB5151286EE005889C6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 33 | 9561ACBB151286EE005889C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 34 | 9561AD0D15130EE2005889C6 /* JASidePanelController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JASidePanelController.h; path = Source/JASidePanelController.h; sourceTree = ""; }; 35 | 9561AD0E15130EE2005889C6 /* JASidePanelController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JASidePanelController.m; path = Source/JASidePanelController.m; sourceTree = ""; }; 36 | 9561AD1115130F10005889C6 /* JAAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JAAppDelegate.h; path = Demo/JAAppDelegate.h; sourceTree = ""; }; 37 | 9561AD1215130F10005889C6 /* JAAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JAAppDelegate.m; path = Demo/JAAppDelegate.m; sourceTree = ""; }; 38 | 9561AD1315130F10005889C6 /* JACenterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JACenterViewController.h; path = Demo/JACenterViewController.h; sourceTree = ""; }; 39 | 9561AD1415130F10005889C6 /* JACenterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JACenterViewController.m; path = Demo/JACenterViewController.m; sourceTree = ""; }; 40 | 9561AD1515130F10005889C6 /* JALeftViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JALeftViewController.h; path = Demo/JALeftViewController.h; sourceTree = ""; }; 41 | 9561AD1615130F10005889C6 /* JALeftViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JALeftViewController.m; path = Demo/JALeftViewController.m; sourceTree = ""; }; 42 | 9561AD1715130F10005889C6 /* JARightViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JARightViewController.h; path = Demo/JARightViewController.h; sourceTree = ""; }; 43 | 9561AD1815130F10005889C6 /* JARightViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JARightViewController.m; path = Demo/JARightViewController.m; sourceTree = ""; }; 44 | 9561AD1D15130F2A005889C6 /* JASidePanels-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "JASidePanels-Info.plist"; path = "Demo/JASidePanels-Info.plist"; sourceTree = ""; }; 45 | 9561AD1E15130F2A005889C6 /* JASidePanels-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "JASidePanels-Prefix.pch"; path = "Demo/JASidePanels-Prefix.pch"; sourceTree = ""; }; 46 | 9561AD1F15130F2A005889C6 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Demo/main.m; sourceTree = ""; }; 47 | 9568132916373ACB007B3602 /* JADebugViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JADebugViewController.h; path = Demo/JADebugViewController.h; sourceTree = ""; }; 48 | 9568132A16373ACB007B3602 /* JADebugViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JADebugViewController.m; path = Demo/JADebugViewController.m; sourceTree = ""; }; 49 | 95BCC45815F081130009F1DA /* UIViewController+JASidePanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+JASidePanel.h"; path = "Source/UIViewController+JASidePanel.h"; sourceTree = ""; }; 50 | 95BCC45915F081130009F1DA /* UIViewController+JASidePanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+JASidePanel.m"; path = "Source/UIViewController+JASidePanel.m"; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 9561ACAA151286EE005889C6 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 9561ACB2151286EE005889C6 /* UIKit.framework in Frameworks */, 59 | 9561ACB4151286EE005889C6 /* Foundation.framework in Frameworks */, 60 | 9561ACB6151286EE005889C6 /* CoreGraphics.framework in Frameworks */, 61 | 95322A731517AEF40069C8D7 /* QuartzCore.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 9561ACA2151286EE005889C6 = { 69 | isa = PBXGroup; 70 | children = ( 71 | 5C49572616C555F700250332 /* Default-568h@2x.png */, 72 | 9561ACB7151286EE005889C6 /* JASidePanels */, 73 | 9561ACB0151286EE005889C6 /* Frameworks */, 74 | 9561ACAE151286EE005889C6 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 9561ACAE151286EE005889C6 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 9561ACAD151286EE005889C6 /* JASidePanels.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 9561ACB0151286EE005889C6 /* Frameworks */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 95322A701517AEEB0069C8D7 /* QuartzCore.framework */, 90 | 9561ACB1151286EE005889C6 /* UIKit.framework */, 91 | 9561ACB3151286EE005889C6 /* Foundation.framework */, 92 | 9561ACB5151286EE005889C6 /* CoreGraphics.framework */, 93 | ); 94 | name = Frameworks; 95 | sourceTree = ""; 96 | }; 97 | 9561ACB7151286EE005889C6 /* JASidePanels */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9561AD0C15130E96005889C6 /* Source */, 101 | 9561AD1015130EFC005889C6 /* Demo */, 102 | ); 103 | path = JASidePanels; 104 | sourceTree = ""; 105 | }; 106 | 9561ACB8151286EE005889C6 /* Supporting Files */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 9561AD1D15130F2A005889C6 /* JASidePanels-Info.plist */, 110 | 9561AD1E15130F2A005889C6 /* JASidePanels-Prefix.pch */, 111 | 9561AD1F15130F2A005889C6 /* main.m */, 112 | 9561ACBA151286EE005889C6 /* InfoPlist.strings */, 113 | ); 114 | name = "Supporting Files"; 115 | sourceTree = ""; 116 | }; 117 | 9561AD0C15130E96005889C6 /* Source */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 9561AD0D15130EE2005889C6 /* JASidePanelController.h */, 121 | 9561AD0E15130EE2005889C6 /* JASidePanelController.m */, 122 | 95BCC45815F081130009F1DA /* UIViewController+JASidePanel.h */, 123 | 95BCC45915F081130009F1DA /* UIViewController+JASidePanel.m */, 124 | ); 125 | name = Source; 126 | sourceTree = ""; 127 | }; 128 | 9561AD1015130EFC005889C6 /* Demo */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 9561ACB8151286EE005889C6 /* Supporting Files */, 132 | 9561AD1115130F10005889C6 /* JAAppDelegate.h */, 133 | 9561AD1215130F10005889C6 /* JAAppDelegate.m */, 134 | 9568132916373ACB007B3602 /* JADebugViewController.h */, 135 | 9568132A16373ACB007B3602 /* JADebugViewController.m */, 136 | 9561AD1315130F10005889C6 /* JACenterViewController.h */, 137 | 9561AD1415130F10005889C6 /* JACenterViewController.m */, 138 | 9561AD1515130F10005889C6 /* JALeftViewController.h */, 139 | 9561AD1615130F10005889C6 /* JALeftViewController.m */, 140 | 9561AD1715130F10005889C6 /* JARightViewController.h */, 141 | 9561AD1815130F10005889C6 /* JARightViewController.m */, 142 | ); 143 | name = Demo; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 9561ACAC151286EE005889C6 /* JASidePanels */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 9561ACCE151286EE005889C6 /* Build configuration list for PBXNativeTarget "JASidePanels" */; 152 | buildPhases = ( 153 | 9561ACA9151286EE005889C6 /* Sources */, 154 | 9561ACAA151286EE005889C6 /* Frameworks */, 155 | 9561ACAB151286EE005889C6 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = JASidePanels; 162 | productName = JASidePanels; 163 | productReference = 9561ACAD151286EE005889C6 /* JASidePanels.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 9561ACA4151286EE005889C6 /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | CLASSPREFIX = JA; 173 | LastUpgradeCheck = 0460; 174 | }; 175 | buildConfigurationList = 9561ACA7151286EE005889C6 /* Build configuration list for PBXProject "JASidePanels" */; 176 | compatibilityVersion = "Xcode 3.2"; 177 | developmentRegion = English; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | ); 182 | mainGroup = 9561ACA2151286EE005889C6; 183 | productRefGroup = 9561ACAE151286EE005889C6 /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | 9561ACAC151286EE005889C6 /* JASidePanels */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 9561ACAB151286EE005889C6 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 9561ACBC151286EE005889C6 /* InfoPlist.strings in Resources */, 198 | 5C49572716C555F700250332 /* Default-568h@2x.png in Resources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXResourcesBuildPhase section */ 203 | 204 | /* Begin PBXSourcesBuildPhase section */ 205 | 9561ACA9151286EE005889C6 /* Sources */ = { 206 | isa = PBXSourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 9561AD0F15130EE2005889C6 /* JASidePanelController.m in Sources */, 210 | 9561AD1915130F10005889C6 /* JAAppDelegate.m in Sources */, 211 | 9561AD1A15130F10005889C6 /* JACenterViewController.m in Sources */, 212 | 9561AD1B15130F10005889C6 /* JALeftViewController.m in Sources */, 213 | 9561AD1C15130F10005889C6 /* JARightViewController.m in Sources */, 214 | 9561AD2115130F2A005889C6 /* main.m in Sources */, 215 | 95BCC45A15F081130009F1DA /* UIViewController+JASidePanel.m in Sources */, 216 | 9568132B16373ACB007B3602 /* JADebugViewController.m in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | 9561ACBA151286EE005889C6 /* InfoPlist.strings */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 9561ACBB151286EE005889C6 /* en */, 227 | ); 228 | name = InfoPlist.strings; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 9561ACCC151286EE005889C6 /* Debug */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_ENUM_CONVERSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 244 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 245 | COPY_PHASE_STRIP = NO; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_DYNAMIC_NO_PIC = NO; 248 | GCC_OPTIMIZATION_LEVEL = 0; 249 | GCC_PREPROCESSOR_DEFINITIONS = ( 250 | "DEBUG=1", 251 | "$(inherited)", 252 | ); 253 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 254 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 256 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 257 | GCC_WARN_UNUSED_VARIABLE = YES; 258 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 259 | SDKROOT = iphoneos; 260 | TARGETED_DEVICE_FAMILY = "1,2"; 261 | }; 262 | name = Debug; 263 | }; 264 | 9561ACCD151286EE005889C6 /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 275 | COPY_PHASE_STRIP = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 280 | GCC_WARN_UNUSED_VARIABLE = YES; 281 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 282 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 283 | SDKROOT = iphoneos; 284 | TARGETED_DEVICE_FAMILY = "1,2"; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | 9561ACCF151286EE005889C6 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 293 | GCC_PREFIX_HEADER = "JASidePanels/Demo/JASidePanels-Prefix.pch"; 294 | INFOPLIST_FILE = "JASidePanels/Demo/JASidePanels-Info.plist"; 295 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | WRAPPER_EXTENSION = app; 298 | }; 299 | name = Debug; 300 | }; 301 | 9561ACD0151286EE005889C6 /* Release */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 305 | GCC_PREFIX_HEADER = "JASidePanels/Demo/JASidePanels-Prefix.pch"; 306 | INFOPLIST_FILE = "JASidePanels/Demo/JASidePanels-Info.plist"; 307 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | WRAPPER_EXTENSION = app; 310 | }; 311 | name = Release; 312 | }; 313 | /* End XCBuildConfiguration section */ 314 | 315 | /* Begin XCConfigurationList section */ 316 | 9561ACA7151286EE005889C6 /* Build configuration list for PBXProject "JASidePanels" */ = { 317 | isa = XCConfigurationList; 318 | buildConfigurations = ( 319 | 9561ACCC151286EE005889C6 /* Debug */, 320 | 9561ACCD151286EE005889C6 /* Release */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = Release; 324 | }; 325 | 9561ACCE151286EE005889C6 /* Build configuration list for PBXNativeTarget "JASidePanels" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 9561ACCF151286EE005889C6 /* Debug */, 329 | 9561ACD0151286EE005889C6 /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | /* End XCConfigurationList section */ 335 | }; 336 | rootObject = 9561ACA4151286EE005889C6 /* Project object */; 337 | } 338 | -------------------------------------------------------------------------------- /JASidePanels.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JAAppDelegate.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import 28 | 29 | @class JASidePanelController; 30 | 31 | @interface JAAppDelegate : UIResponder 32 | 33 | @property (strong, nonatomic) UIWindow *window; 34 | 35 | @property (strong, nonatomic) JASidePanelController *viewController; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JAAppDelegate.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import "JAAppDelegate.h" 28 | 29 | #import "JASidePanelController.h" 30 | #import "JACenterViewController.h" 31 | #import "JALeftViewController.h" 32 | #import "JARightViewController.h" 33 | 34 | @implementation JAAppDelegate 35 | 36 | @synthesize window = _window; 37 | @synthesize viewController = _viewController; 38 | 39 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 40 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 41 | 42 | self.viewController = [[JASidePanelController alloc] init]; 43 | self.viewController.shouldDelegateAutorotateToVisiblePanel = NO; 44 | 45 | self.viewController.leftPanel = [[JALeftViewController alloc] init]; 46 | self.viewController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[JACenterViewController alloc] init]]; 47 | self.viewController.rightPanel = [[JARightViewController alloc] init]; 48 | 49 | self.window.rootViewController = self.viewController; 50 | [self.window makeKeyAndVisible]; 51 | return YES; 52 | } 53 | 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JACenterViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import "JADebugViewController.h" 28 | 29 | @interface JACenterViewController : JADebugViewController 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JACenterViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import "JACenterViewController.h" 27 | 28 | @interface JACenterViewController () 29 | 30 | @end 31 | 32 | @implementation JACenterViewController 33 | 34 | - (id)init { 35 | if (self = [super init]) { 36 | self.title = @"Center Panel"; 37 | } 38 | return self; 39 | } 40 | 41 | - (void)viewDidLoad { 42 | [super viewDidLoad]; 43 | 44 | CGFloat red = (CGFloat)arc4random() / 0x100000000; 45 | CGFloat green = (CGFloat)arc4random() / 0x100000000; 46 | CGFloat blue = (CGFloat)arc4random() / 0x100000000; 47 | self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0f]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JADebugViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // JADebugViewController.h 3 | // JASidePanels 4 | // 5 | // Created by Jesse Andersen on 10/23/12. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface JADebugViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JADebugViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // JADebugViewController.m 3 | // JASidePanels 4 | // 5 | // Created by Jesse Andersen on 10/23/12. 6 | // 7 | // 8 | 9 | #import "JADebugViewController.h" 10 | 11 | @interface JADebugViewController () 12 | 13 | @end 14 | 15 | @implementation JADebugViewController 16 | 17 | - (void)viewWillAppear:(BOOL)animated { 18 | [super viewWillAppear:animated]; 19 | NSLog(@"%@ viewWillAppear", self); 20 | } 21 | 22 | - (void)viewDidAppear:(BOOL)animated { 23 | [super viewDidAppear:animated]; 24 | NSLog(@"%@ viewDidAppear", self); 25 | } 26 | 27 | - (void)viewWillDisappear:(BOOL)animated { 28 | [super viewWillDisappear:animated]; 29 | NSLog(@"%@ viewWillDisappear", self); 30 | } 31 | 32 | - (void)viewDidDisappear:(BOOL)animated { 33 | [super viewDidDisappear:animated]; 34 | NSLog(@"%@ viewDidDisappear", self); 35 | } 36 | 37 | - (void)willMoveToParentViewController:(UIViewController *)parent { 38 | [super willMoveToParentViewController:parent]; 39 | NSLog(@"%@ willMoveToParentViewController %@", self, parent); 40 | } 41 | 42 | - (void)didMoveToParentViewController:(UIViewController *)parent { 43 | [super didMoveToParentViewController:parent]; 44 | NSLog(@"%@ didMoveToParentViewController %@", self, parent); 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JALeftViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import "JADebugViewController.h" 28 | 29 | @interface JALeftViewController : JADebugViewController 30 | 31 | @property (nonatomic, weak, readonly) UILabel *label; 32 | @property (nonatomic, weak, readonly) UIButton *hide; 33 | @property (nonatomic, weak, readonly) UIButton *show; 34 | @property (nonatomic, weak, readonly) UIButton *removeRightPanel; 35 | @property (nonatomic, weak, readonly) UIButton *addRightPanel; 36 | @property (nonatomic, weak, readonly) UIButton *changeCenterPanel; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JALeftViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import "JALeftViewController.h" 28 | #import "JASidePanelController.h" 29 | 30 | #import "UIViewController+JASidePanel.h" 31 | #import "JARightViewController.h" 32 | #import "JACenterViewController.h" 33 | 34 | @interface JALeftViewController () 35 | 36 | @property (nonatomic, weak) UILabel *label; 37 | @property (nonatomic, weak) UIButton *hide; 38 | @property (nonatomic, weak) UIButton *show; 39 | @property (nonatomic, weak) UIButton *removeRightPanel; 40 | @property (nonatomic, weak) UIButton *addRightPanel; 41 | @property (nonatomic, weak) UIButton *changeCenterPanel; 42 | 43 | @end 44 | 45 | @implementation JALeftViewController 46 | 47 | - (void)viewDidLoad { 48 | [super viewDidLoad]; 49 | self.view.backgroundColor = [UIColor blueColor]; 50 | 51 | UILabel *label = [[UILabel alloc] init]; 52 | label.font = [UIFont boldSystemFontOfSize:20.0f]; 53 | label.textColor = [UIColor whiteColor]; 54 | label.backgroundColor = [UIColor clearColor]; 55 | label.text = @"Left Panel"; 56 | [label sizeToFit]; 57 | label.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; 58 | [self.view addSubview:label]; 59 | self.label = label; 60 | 61 | UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 62 | button.frame = CGRectMake(20.0f, 70.0f, 200.0f, 40.0f); 63 | button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 64 | [button setTitle:@"Hide Center" forState:UIControlStateNormal]; 65 | [button addTarget:self action:@selector(_hideTapped:) forControlEvents:UIControlEventTouchUpInside]; 66 | [self.view addSubview:button]; 67 | self.hide = button; 68 | 69 | button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 70 | button.frame = self.hide.frame; 71 | button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 72 | [button setTitle:@"Show Center" forState:UIControlStateNormal]; 73 | [button addTarget:self action:@selector(_showTapped:) forControlEvents:UIControlEventTouchUpInside]; 74 | button.hidden = YES; 75 | [self.view addSubview:button]; 76 | self.show = button; 77 | 78 | button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 79 | button.frame = CGRectMake(20.0f, 120.0f, 200.0f, 40.0f); 80 | button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 81 | [button setTitle:@"Remove Right Panel" forState:UIControlStateNormal]; 82 | [button addTarget:self action:@selector(_removeRightPanelTapped:) forControlEvents:UIControlEventTouchUpInside]; 83 | [self.view addSubview:button]; 84 | self.removeRightPanel = button; 85 | 86 | button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 87 | button.frame = self.removeRightPanel.frame; 88 | button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 89 | [button setTitle:@"Add Right Panel" forState:UIControlStateNormal]; 90 | [button addTarget:self action:@selector(_addRightPanelTapped:) forControlEvents:UIControlEventTouchUpInside]; 91 | button.hidden = YES; 92 | [self.view addSubview:button]; 93 | self.addRightPanel = button; 94 | 95 | button = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 96 | button.frame = CGRectMake(20.0f, 170.0f, 200.0f, 40.0f); 97 | button.autoresizingMask = UIViewAutoresizingFlexibleRightMargin; 98 | [button setTitle:@"Change Center Panel" forState:UIControlStateNormal]; 99 | [button addTarget:self action:@selector(_changeCenterPanelTapped:) forControlEvents:UIControlEventTouchUpInside]; 100 | [self.view addSubview:button]; 101 | self.changeCenterPanel = button; 102 | } 103 | 104 | - (void)viewWillAppear:(BOOL)animated { 105 | [super viewWillAppear:animated]; 106 | self.label.center = CGPointMake(floorf(self.sidePanelController.leftVisibleWidth/2.0f), 25.0f); 107 | } 108 | 109 | #pragma mark - Button Actions 110 | 111 | - (void)_hideTapped:(id)sender { 112 | [self.sidePanelController setCenterPanelHidden:YES animated:YES duration:0.2f]; 113 | self.hide.hidden = YES; 114 | self.show.hidden = NO; 115 | } 116 | 117 | - (void)_showTapped:(id)sender { 118 | [self.sidePanelController setCenterPanelHidden:NO animated:YES duration:0.2f]; 119 | self.hide.hidden = NO; 120 | self.show.hidden = YES; 121 | } 122 | 123 | - (void)_removeRightPanelTapped:(id)sender { 124 | self.sidePanelController.rightPanel = nil; 125 | self.removeRightPanel.hidden = YES; 126 | self.addRightPanel.hidden = NO; 127 | } 128 | 129 | - (void)_addRightPanelTapped:(id)sender { 130 | self.sidePanelController.rightPanel = [[JARightViewController alloc] init]; 131 | self.removeRightPanel.hidden = NO; 132 | self.addRightPanel.hidden = YES; 133 | } 134 | 135 | - (void)_changeCenterPanelTapped:(id)sender { 136 | self.sidePanelController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[JACenterViewController alloc] init]]; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JARightViewController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import 28 | 29 | #import "JALeftViewController.h" 30 | 31 | @interface JARightViewController : JALeftViewController 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JARightViewController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | 27 | #import "JARightViewController.h" 28 | #import "JASidePanelController.h" 29 | 30 | #import "UIViewController+JASidePanel.h" 31 | 32 | @interface JARightViewController () 33 | 34 | @end 35 | 36 | @implementation JARightViewController 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | self.view.backgroundColor = [UIColor redColor]; 41 | self.label.text = @"Right Panel"; 42 | [self.label sizeToFit]; 43 | self.hide.frame = CGRectMake(self.view.bounds.size.width - 220.0f, 70.0f, 200.0f, 40.0f); 44 | self.hide.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin; 45 | self.show.frame = self.hide.frame; 46 | self.show.autoresizingMask = self.hide.autoresizingMask; 47 | 48 | self.removeRightPanel.hidden = YES; 49 | self.addRightPanel.hidden = YES; 50 | self.changeCenterPanel.hidden = YES; 51 | } 52 | 53 | - (void)viewWillAppear:(BOOL)animated { 54 | [super viewWillAppear:animated]; 55 | self.label.center = CGPointMake(floorf((self.view.bounds.size.width - self.sidePanelController.rightVisibleWidth) + self.sidePanelController.rightVisibleWidth/2.0f), 25.0f); 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JASidePanels-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.nimjun.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /JASidePanels/Demo/JASidePanels-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'JASidePanels' target in the 'JASidePanels' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /JASidePanels/Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // JASidePanels 4 | // 5 | // Created by Jesse Andersen on 3/15/12. 6 | // Copyright (c) 2012 __MyCompanyName__. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "JAAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([JAAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /JASidePanels/Source/JASidePanelController.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | typedef enum _JASidePanelStyle { 29 | JASidePanelSingleActive = 0, 30 | JASidePanelMultipleActive 31 | } JASidePanelStyle; 32 | 33 | typedef enum _JASidePanelState { 34 | JASidePanelCenterVisible = 1, 35 | JASidePanelLeftVisible, 36 | JASidePanelRightVisible 37 | } JASidePanelState; 38 | 39 | @interface JASidePanelController : UIViewController 40 | 41 | #pragma mark - Usage 42 | 43 | // set the panels 44 | @property (nonatomic, strong) UIViewController *leftPanel; // optional 45 | @property (nonatomic, strong) UIViewController *centerPanel; // required 46 | @property (nonatomic, strong) UIViewController *rightPanel; // optional 47 | 48 | // show the panels 49 | - (void)showLeftPanel:(BOOL)animated __attribute__((deprecated("Use -showLeftPanelAnimated: instead"))); 50 | - (void)showRightPanel:(BOOL)animated __attribute__((deprecated("Use -showRightPanelAnimated: instead"))); 51 | - (void)showCenterPanel:(BOOL)animated __attribute__((deprecated("Use -showCenterPanelAnimated: instead"))); 52 | 53 | // show the panels 54 | - (void)showLeftPanelAnimated:(BOOL)animated; 55 | - (void)showRightPanelAnimated:(BOOL)animated; 56 | - (void)showCenterPanelAnimated:(BOOL)animated; 57 | 58 | // toggle them opened/closed 59 | - (void)toggleLeftPanel:(id)sender; 60 | - (void)toggleRightPanel:(id)sender; 61 | 62 | // Calling this while the left or right panel is visible causes the center panel to be completely hidden 63 | - (void)setCenterPanelHidden:(BOOL)centerPanelHidden animated:(BOOL)animated duration:(NSTimeInterval) duration; 64 | 65 | #pragma mark - Look & Feel 66 | 67 | // style 68 | @property (nonatomic) JASidePanelStyle style; // default is JASidePanelSingleActive 69 | 70 | // push side panels instead of overlapping them 71 | @property (nonatomic, assign) BOOL pushesSidePanels; 72 | 73 | // size the left panel based on % of total screen width 74 | @property (nonatomic) CGFloat leftGapPercentage; 75 | 76 | // size the left panel based on this fixed size. overrides leftGapPercentage 77 | @property (nonatomic) CGFloat leftFixedWidth; 78 | 79 | // the visible width of the left panel 80 | @property (nonatomic, readonly) CGFloat leftVisibleWidth; 81 | 82 | // size the right panel based on % of total screen width 83 | @property (nonatomic) CGFloat rightGapPercentage; 84 | 85 | // size the right panel based on this fixed size. overrides rightGapPercentage 86 | @property (nonatomic) CGFloat rightFixedWidth; 87 | 88 | // the visible width of the right panel 89 | @property (nonatomic, readonly) CGFloat rightVisibleWidth; 90 | 91 | // by default applies a black shadow to the container. override in sublcass to change 92 | - (void)styleContainer:(UIView *)container animate:(BOOL)animate duration:(NSTimeInterval)duration; 93 | 94 | // by default applies rounded corners to the panel. override in sublcass to change 95 | - (void)stylePanel:(UIView *)panel; 96 | 97 | #pragma mark - Animation 98 | 99 | // the minimum % of total screen width the centerPanel.view must move for panGesture to succeed 100 | @property (nonatomic) CGFloat minimumMovePercentage; 101 | 102 | // the maximum time panel opening/closing should take. Actual time may be less if panGesture has already moved the view. 103 | @property (nonatomic) CGFloat maximumAnimationDuration; 104 | 105 | // how long the bounce animation should take 106 | @property (nonatomic) CGFloat bounceDuration; 107 | 108 | // how far the view should bounce 109 | @property (nonatomic) CGFloat bouncePercentage; 110 | 111 | // should the center panel bounce when you are panning open a left/right panel. 112 | @property (nonatomic) BOOL bounceOnSidePanelOpen; // defaults to YES 113 | 114 | // should the center panel bounce when you are panning closed a left/right panel. 115 | @property (nonatomic) BOOL bounceOnSidePanelClose; // defaults to NO 116 | 117 | // while changing the center panel, should we bounce it offscreen? 118 | @property (nonatomic) BOOL bounceOnCenterPanelChange; // defaults to YES 119 | 120 | #pragma mark - Gesture Behavior 121 | 122 | // Determines whether the pan gesture is limited to the top ViewController in a UINavigationController/UITabBarController 123 | @property (nonatomic) BOOL panningLimitedToTopViewController; // default is YES 124 | 125 | // Determines whether showing panels can be controlled through pan gestures, or only through buttons 126 | @property (nonatomic) BOOL recognizesPanGesture; // default is YES 127 | 128 | #pragma mark - Menu Buttons 129 | 130 | // Gives you an image to use for your menu button. The image is three stacked white lines, similar to Path 2.0 or Facebook's menu button. 131 | + (UIImage *)defaultImage; 132 | 133 | // Default button to place in gestureViewControllers top viewController. Override in sublcass to change look of default button 134 | - (UIBarButtonItem *)leftButtonForCenterPanel; 135 | 136 | #pragma mark - Nuts & Bolts 137 | 138 | // Current state of panels. Use KVO to monitor state changes 139 | @property (nonatomic, readonly) JASidePanelState state; 140 | 141 | // Whether or not the center panel is completely hidden 142 | @property (nonatomic, assign) BOOL centerPanelHidden; 143 | 144 | // The currently visible panel 145 | @property (nonatomic, weak, readonly) UIViewController *visiblePanel; 146 | 147 | // If set to yes, "shouldAutorotateToInterfaceOrientation:" will be passed to self.visiblePanel instead of handled directly 148 | @property (nonatomic, assign) BOOL shouldDelegateAutorotateToVisiblePanel; // defaults to YES 149 | 150 | // Determines whether or not the panel's views are removed when not visble. If YES, rightPanel & leftPanel's views are eligible for viewDidUnload 151 | @property (nonatomic, assign) BOOL canUnloadRightPanel; // defaults to NO 152 | @property (nonatomic, assign) BOOL canUnloadLeftPanel; // defaults to NO 153 | 154 | // Determines whether or not the panel's views should be resized when they are displayed. If yes, the views will be resized to their visible width 155 | @property (nonatomic, assign) BOOL shouldResizeRightPanel; // defaults to NO 156 | @property (nonatomic, assign) BOOL shouldResizeLeftPanel; // defaults to NO 157 | 158 | // Determines whether or not the center panel can be panned beyound the the visible area of the side panels 159 | @property (nonatomic, assign) BOOL allowRightOverpan; // defaults to YES 160 | @property (nonatomic, assign) BOOL allowLeftOverpan; // defaults to YES 161 | 162 | // Determines whether or not the left or right panel can be swiped into view. Use if only way to view a panel is with a button 163 | @property (nonatomic, assign) BOOL allowLeftSwipe; // defaults to YES 164 | @property (nonatomic, assign) BOOL allowRightSwipe; // defaults to YES 165 | 166 | // Containers for the panels. 167 | @property (nonatomic, strong, readonly) UIView *leftPanelContainer; 168 | @property (nonatomic, strong, readonly) UIView *rightPanelContainer; 169 | @property (nonatomic, strong, readonly) UIView *centerPanelContainer; 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /JASidePanels/Source/JASidePanelController.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import 27 | #import "JASidePanelController.h" 28 | 29 | static char ja_kvoContext; 30 | 31 | @interface JASidePanelController() { 32 | CGRect _centerPanelRestingFrame; 33 | CGPoint _locationBeforePan; 34 | } 35 | 36 | @property (nonatomic, readwrite) JASidePanelState state; 37 | @property (nonatomic, weak) UIViewController *visiblePanel; 38 | @property (nonatomic, strong) UIView *tapView; 39 | 40 | // panel containers 41 | @property (nonatomic, strong) UIView *leftPanelContainer; 42 | @property (nonatomic, strong) UIView *rightPanelContainer; 43 | @property (nonatomic, strong) UIView *centerPanelContainer; 44 | 45 | @end 46 | 47 | @implementation JASidePanelController 48 | 49 | @synthesize leftPanelContainer = _leftPanelContainer; 50 | @synthesize rightPanelContainer = _rightPanelContainer; 51 | @synthesize centerPanelContainer = _centerPanelContainer; 52 | @synthesize tapView = _tapView; 53 | @synthesize style = _style; 54 | @synthesize state = _state; 55 | @synthesize leftPanel = _leftPanel; 56 | @synthesize centerPanel = _centerPanel; 57 | @synthesize rightPanel = _rightPanel; 58 | @synthesize leftGapPercentage = _leftGapPercentage; 59 | @synthesize leftFixedWidth = _leftFixedWidth; 60 | @synthesize rightGapPercentage = _rightGapPercentage; 61 | @synthesize rightFixedWidth = _rightFixedWidth; 62 | @synthesize minimumMovePercentage = _minimumMovePercentage; 63 | @synthesize maximumAnimationDuration = _maximumAnimationDuration; 64 | @synthesize bounceDuration = _bounceDuration; 65 | @synthesize bouncePercentage = _bouncePercentage; 66 | @synthesize panningLimitedToTopViewController = _panningLimitedToTopViewController; 67 | @synthesize recognizesPanGesture = _recognizesPanGesture; 68 | @synthesize canUnloadRightPanel = _canUnloadRightPanel; 69 | @synthesize canUnloadLeftPanel = _canUnloadLeftPanel; 70 | @synthesize shouldResizeLeftPanel = _shouldResizeLeftPanel; 71 | @synthesize shouldResizeRightPanel = _shouldResizeRightPanel; 72 | @synthesize allowLeftOverpan = _allowLeftOverpan; 73 | @synthesize allowRightOverpan = _allowRightOverpan; 74 | @synthesize bounceOnSidePanelOpen = _bounceOnSidePanelOpen; 75 | @synthesize bounceOnSidePanelClose = _bounceOnSidePanelClose; 76 | @synthesize bounceOnCenterPanelChange = _bounceOnCenterPanelChange; 77 | @synthesize visiblePanel = _visiblePanel; 78 | @synthesize shouldDelegateAutorotateToVisiblePanel = _shouldDelegateAutorotateToVisiblePanel; 79 | @synthesize centerPanelHidden = _centerPanelHidden; 80 | @synthesize allowLeftSwipe = _allowLeftSwipe; 81 | @synthesize allowRightSwipe = _allowRightSwipe; 82 | @synthesize pushesSidePanels = _pushesSidePanels; 83 | 84 | #pragma mark - Icon 85 | 86 | + (UIImage *)defaultImage { 87 | static UIImage *defaultImage = nil; 88 | static dispatch_once_t onceToken; 89 | dispatch_once(&onceToken, ^{ 90 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(20.f, 13.f), NO, 0.0f); 91 | 92 | [[UIColor blackColor] setFill]; 93 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 20, 1)] fill]; 94 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 5, 20, 1)] fill]; 95 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 10, 20, 1)] fill]; 96 | 97 | [[UIColor whiteColor] setFill]; 98 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 1, 20, 2)] fill]; 99 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 6, 20, 2)] fill]; 100 | [[UIBezierPath bezierPathWithRect:CGRectMake(0, 11, 20, 2)] fill]; 101 | 102 | defaultImage = UIGraphicsGetImageFromCurrentImageContext(); 103 | UIGraphicsEndImageContext(); 104 | 105 | }); 106 | return defaultImage; 107 | } 108 | 109 | #pragma mark - NSObject 110 | 111 | - (void)dealloc { 112 | if (_centerPanel) { 113 | [_centerPanel removeObserver:self forKeyPath:@"view"]; 114 | [_centerPanel removeObserver:self forKeyPath:@"viewControllers"]; 115 | } 116 | } 117 | 118 | //Support creating from Storyboard 119 | - (id)initWithCoder:(NSCoder *)aDecoder { 120 | if (self = [super initWithCoder:aDecoder]) { 121 | [self _baseInit]; 122 | } 123 | return self; 124 | } 125 | 126 | - (id)init { 127 | if (self = [super init]) { 128 | [self _baseInit]; 129 | } 130 | return self; 131 | } 132 | 133 | - (void)_baseInit { 134 | self.style = JASidePanelSingleActive; 135 | self.leftGapPercentage = 0.8f; 136 | self.rightGapPercentage = 0.8f; 137 | self.minimumMovePercentage = 0.15f; 138 | self.maximumAnimationDuration = 0.2f; 139 | self.bounceDuration = 0.1f; 140 | self.bouncePercentage = 0.075f; 141 | self.panningLimitedToTopViewController = YES; 142 | self.recognizesPanGesture = YES; 143 | self.allowLeftOverpan = YES; 144 | self.allowRightOverpan = YES; 145 | self.bounceOnSidePanelOpen = YES; 146 | self.bounceOnSidePanelClose = NO; 147 | self.bounceOnCenterPanelChange = YES; 148 | self.shouldDelegateAutorotateToVisiblePanel = YES; 149 | self.allowRightSwipe = YES; 150 | self.allowLeftSwipe = YES; 151 | } 152 | 153 | #pragma mark - UIViewController 154 | 155 | - (void)viewDidLoad { 156 | [super viewDidLoad]; 157 | self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 158 | 159 | self.centerPanelContainer = [[UIView alloc] initWithFrame:self.view.bounds]; 160 | _centerPanelRestingFrame = self.centerPanelContainer.frame; 161 | _centerPanelHidden = NO; 162 | 163 | self.leftPanelContainer = [[UIView alloc] initWithFrame:self.view.bounds]; 164 | self.leftPanelContainer.hidden = YES; 165 | 166 | self.rightPanelContainer = [[UIView alloc] initWithFrame:self.view.bounds]; 167 | self.rightPanelContainer.hidden = YES; 168 | 169 | [self _configureContainers]; 170 | 171 | [self.view addSubview:self.centerPanelContainer]; 172 | [self.view addSubview:self.leftPanelContainer]; 173 | [self.view addSubview:self.rightPanelContainer]; 174 | 175 | self.state = JASidePanelCenterVisible; 176 | 177 | [self _swapCenter:nil previousState:0 with:_centerPanel]; 178 | [self.view bringSubviewToFront:self.centerPanelContainer]; 179 | } 180 | 181 | - (void)viewWillAppear:(BOOL)animated { 182 | [super viewWillAppear:animated]; 183 | // ensure correct view dimensions 184 | [self _layoutSideContainers:NO duration:0.0f]; 185 | [self _layoutSidePanels]; 186 | self.centerPanelContainer.frame = [self _adjustCenterFrame]; 187 | [self styleContainer:self.centerPanelContainer animate:NO duration:0.0f]; 188 | } 189 | 190 | - (void)viewDidAppear:(BOOL)animated{ 191 | [super viewDidAppear:animated]; 192 | [self _adjustCenterFrame]; //Account for possible rotation while view appearing 193 | } 194 | 195 | #if !defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0 196 | 197 | - (void)viewDidUnload { 198 | [super viewDidUnload]; 199 | self.tapView = nil; 200 | self.centerPanelContainer = nil; 201 | self.leftPanelContainer = nil; 202 | self.rightPanelContainer = nil; 203 | } 204 | 205 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation { 206 | __strong UIViewController *visiblePanel = self.visiblePanel; 207 | 208 | if (self.shouldDelegateAutorotateToVisiblePanel) { 209 | return [visiblePanel shouldAutorotateToInterfaceOrientation:toInterfaceOrientation]; 210 | } else { 211 | return YES; 212 | } 213 | } 214 | 215 | #else 216 | 217 | - (BOOL)shouldAutorotate { 218 | __strong UIViewController *visiblePanel = self.visiblePanel; 219 | 220 | if (self.shouldDelegateAutorotateToVisiblePanel && [visiblePanel respondsToSelector:@selector(shouldAutorotate)]) { 221 | return [visiblePanel shouldAutorotate]; 222 | } else { 223 | return YES; 224 | } 225 | } 226 | 227 | 228 | #endif 229 | 230 | - (void)willAnimateRotationToInterfaceOrientation:(__unused UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 231 | self.centerPanelContainer.frame = [self _adjustCenterFrame]; 232 | [self _layoutSideContainers:YES duration:duration]; 233 | [self _layoutSidePanels]; 234 | [self styleContainer:self.centerPanelContainer animate:YES duration:duration]; 235 | if (self.centerPanelHidden) { 236 | CGRect frame = self.centerPanelContainer.frame; 237 | frame.origin.x = self.state == JASidePanelLeftVisible ? self.centerPanelContainer.frame.size.width : -self.centerPanelContainer.frame.size.width; 238 | self.centerPanelContainer.frame = frame; 239 | } 240 | } 241 | 242 | #pragma mark - State 243 | 244 | - (void)setState:(JASidePanelState)state { 245 | if (state != _state) { 246 | _state = state; 247 | switch (_state) { 248 | case JASidePanelCenterVisible: { 249 | self.visiblePanel = self.centerPanel; 250 | self.leftPanelContainer.userInteractionEnabled = NO; 251 | self.rightPanelContainer.userInteractionEnabled = NO; 252 | break; 253 | } 254 | case JASidePanelLeftVisible: { 255 | self.visiblePanel = self.leftPanel; 256 | self.leftPanelContainer.userInteractionEnabled = YES; 257 | break; 258 | } 259 | case JASidePanelRightVisible: { 260 | self.visiblePanel = self.rightPanel; 261 | self.rightPanelContainer.userInteractionEnabled = YES; 262 | break; 263 | } 264 | } 265 | } 266 | } 267 | 268 | #pragma mark - Style 269 | 270 | - (void)setStyle:(JASidePanelStyle)style { 271 | if (style != _style) { 272 | _style = style; 273 | if (self.isViewLoaded) { 274 | [self _configureContainers]; 275 | [self _layoutSideContainers:NO duration:0.0f]; 276 | } 277 | } 278 | } 279 | 280 | - (void)styleContainer:(UIView *)container animate:(BOOL)animate duration:(NSTimeInterval)duration { 281 | UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:container.bounds cornerRadius:0.0f]; 282 | if (animate) { 283 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"shadowPath"]; 284 | animation.fromValue = (id)container.layer.shadowPath; 285 | animation.toValue = (id)shadowPath.CGPath; 286 | animation.duration = duration; 287 | [container.layer addAnimation:animation forKey:@"shadowPath"]; 288 | } 289 | container.layer.shadowPath = shadowPath.CGPath; 290 | container.layer.shadowColor = [UIColor blackColor].CGColor; 291 | container.layer.shadowRadius = 10.0f; 292 | container.layer.shadowOpacity = 0.75f; 293 | container.clipsToBounds = NO; 294 | } 295 | 296 | - (void)stylePanel:(UIView *)panel { 297 | panel.layer.cornerRadius = 6.0f; 298 | panel.clipsToBounds = YES; 299 | } 300 | 301 | - (void)_configureContainers { 302 | self.leftPanelContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin; 303 | self.rightPanelContainer.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight; 304 | self.centerPanelContainer.frame = self.view.bounds; 305 | self.centerPanelContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 306 | } 307 | 308 | - (void)_layoutSideContainers:(BOOL)animate duration:(NSTimeInterval)duration { 309 | CGRect leftFrame = self.view.bounds; 310 | CGRect rightFrame = self.view.bounds; 311 | if (self.style == JASidePanelMultipleActive) { 312 | // left panel container 313 | leftFrame.size.width = self.leftVisibleWidth; 314 | leftFrame.origin.x = self.centerPanelContainer.frame.origin.x - leftFrame.size.width; 315 | 316 | // right panel container 317 | rightFrame.size.width = self.rightVisibleWidth; 318 | rightFrame.origin.x = self.centerPanelContainer.frame.origin.x + self.centerPanelContainer.frame.size.width; 319 | } else if (self.pushesSidePanels && !self.centerPanelHidden) { 320 | leftFrame.origin.x = self.centerPanelContainer.frame.origin.x - self.leftVisibleWidth; 321 | rightFrame.origin.x = self.centerPanelContainer.frame.origin.x + self.centerPanelContainer.frame.size.width; 322 | } 323 | self.leftPanelContainer.frame = leftFrame; 324 | self.rightPanelContainer.frame = rightFrame; 325 | [self styleContainer:self.leftPanelContainer animate:animate duration:duration]; 326 | [self styleContainer:self.rightPanelContainer animate:animate duration:duration]; 327 | } 328 | 329 | - (void)_layoutSidePanels { 330 | if (self.rightPanel.isViewLoaded) { 331 | CGRect frame = self.rightPanelContainer.bounds; 332 | if (self.shouldResizeRightPanel) { 333 | if (!self.pushesSidePanels) { 334 | frame.origin.x = self.rightPanelContainer.bounds.size.width - self.rightVisibleWidth; 335 | } 336 | frame.size.width = self.rightVisibleWidth; 337 | } 338 | self.rightPanel.view.frame = frame; 339 | } 340 | if (self.leftPanel.isViewLoaded) { 341 | CGRect frame = self.leftPanelContainer.bounds; 342 | if (self.shouldResizeLeftPanel) { 343 | frame.size.width = self.leftVisibleWidth; 344 | } 345 | self.leftPanel.view.frame = frame; 346 | } 347 | } 348 | 349 | #pragma mark - Panels 350 | 351 | - (void)setCenterPanel:(UIViewController *)centerPanel { 352 | UIViewController *previous = _centerPanel; 353 | if (centerPanel != _centerPanel) { 354 | [_centerPanel removeObserver:self forKeyPath:@"view"]; 355 | [_centerPanel removeObserver:self forKeyPath:@"viewControllers"]; 356 | _centerPanel = centerPanel; 357 | [_centerPanel addObserver:self forKeyPath:@"viewControllers" options:0 context:&ja_kvoContext]; 358 | [_centerPanel addObserver:self forKeyPath:@"view" options:NSKeyValueObservingOptionInitial context:&ja_kvoContext]; 359 | if (self.state == JASidePanelCenterVisible) { 360 | self.visiblePanel = _centerPanel; 361 | } 362 | } 363 | if (self.isViewLoaded && self.state == JASidePanelCenterVisible) { 364 | [self _swapCenter:previous previousState:0 with:_centerPanel]; 365 | } else if (self.isViewLoaded) { 366 | // update the state immediately to prevent user interaction on the side panels while animating 367 | JASidePanelState previousState = self.state; 368 | self.state = JASidePanelCenterVisible; 369 | [UIView animateWithDuration:0.2f animations:^{ 370 | if (self.bounceOnCenterPanelChange) { 371 | // first move the centerPanel offscreen 372 | CGFloat x = (previousState == JASidePanelLeftVisible) ? self.view.bounds.size.width : -self.view.bounds.size.width; 373 | _centerPanelRestingFrame.origin.x = x; 374 | } 375 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 376 | } completion:^(__unused BOOL finished) { 377 | [self _swapCenter:previous previousState:previousState with:_centerPanel]; 378 | [self _showCenterPanel:YES bounce:NO]; 379 | }]; 380 | } 381 | } 382 | 383 | - (void)_swapCenter:(UIViewController *)previous previousState:(JASidePanelState)previousState with:(UIViewController *)next { 384 | if (previous != next) { 385 | [previous willMoveToParentViewController:nil]; 386 | [previous.view removeFromSuperview]; 387 | [previous removeFromParentViewController]; 388 | 389 | if (next) { 390 | [self _loadCenterPanelWithPreviousState:previousState]; 391 | [self addChildViewController:next]; 392 | [self.centerPanelContainer addSubview:next.view]; 393 | [next didMoveToParentViewController:self]; 394 | } 395 | } 396 | } 397 | 398 | - (void)setLeftPanel:(UIViewController *)leftPanel { 399 | if (leftPanel != _leftPanel) { 400 | [_leftPanel willMoveToParentViewController:nil]; 401 | [_leftPanel.view removeFromSuperview]; 402 | [_leftPanel removeFromParentViewController]; 403 | _leftPanel = leftPanel; 404 | if (_leftPanel) { 405 | [self addChildViewController:_leftPanel]; 406 | [_leftPanel didMoveToParentViewController:self]; 407 | [self _placeButtonForLeftPanel]; 408 | } 409 | if (self.state == JASidePanelLeftVisible) { 410 | self.visiblePanel = _leftPanel; 411 | } 412 | } 413 | } 414 | 415 | - (void)setRightPanel:(UIViewController *)rightPanel { 416 | if (rightPanel != _rightPanel) { 417 | [_rightPanel willMoveToParentViewController:nil]; 418 | [_rightPanel.view removeFromSuperview]; 419 | [_rightPanel removeFromParentViewController]; 420 | _rightPanel = rightPanel; 421 | if (_rightPanel) { 422 | [self addChildViewController:_rightPanel]; 423 | [_rightPanel didMoveToParentViewController:self]; 424 | } 425 | if (self.state == JASidePanelRightVisible) { 426 | self.visiblePanel = _rightPanel; 427 | } 428 | } 429 | } 430 | 431 | #pragma mark - Panel Buttons 432 | 433 | - (void)_placeButtonForLeftPanel { 434 | if (self.leftPanel) { 435 | UIViewController *buttonController = self.centerPanel; 436 | if ([buttonController isKindOfClass:[UINavigationController class]]) { 437 | UINavigationController *nav = (UINavigationController *)buttonController; 438 | if ([nav.viewControllers count] > 0) { 439 | buttonController = [nav.viewControllers objectAtIndex:0]; 440 | } 441 | } 442 | if (!buttonController.navigationItem.leftBarButtonItem) { 443 | buttonController.navigationItem.leftBarButtonItem = [self leftButtonForCenterPanel]; 444 | } 445 | } 446 | } 447 | 448 | #pragma mark - Gesture Recognizer Delegate 449 | 450 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { 451 | if (gestureRecognizer.view == self.tapView) { 452 | return YES; 453 | } else if (self.panningLimitedToTopViewController && ![self _isOnTopLevelViewController:self.centerPanel]) { 454 | return NO; 455 | } else if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { 456 | UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer; 457 | CGPoint translate = [pan translationInView:self.centerPanelContainer]; 458 | // determine if right swipe is allowed 459 | if (translate.x < 0 && ! self.allowRightSwipe) { 460 | return NO; 461 | } 462 | // determine if left swipe is allowed 463 | if (translate.x > 0 && ! self.allowLeftSwipe) { 464 | return NO; 465 | } 466 | BOOL possible = translate.x != 0 && ((fabsf(translate.y) / fabsf(translate.x)) < 1.0f); 467 | if (possible && ((translate.x > 0 && self.leftPanel) || (translate.x < 0 && self.rightPanel))) { 468 | return YES; 469 | } 470 | } 471 | return NO; 472 | } 473 | 474 | #pragma mark - Pan Gestures 475 | 476 | - (void)_addPanGestureToView:(UIView *)view { 477 | UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_handlePan:)]; 478 | panGesture.delegate = self; 479 | panGesture.maximumNumberOfTouches = 1; 480 | panGesture.minimumNumberOfTouches = 1; 481 | [view addGestureRecognizer:panGesture]; 482 | } 483 | 484 | - (void)_handlePan:(UIGestureRecognizer *)sender { 485 | if (!_recognizesPanGesture) { 486 | return; 487 | } 488 | 489 | if ([sender isKindOfClass:[UIPanGestureRecognizer class]]) { 490 | UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)sender; 491 | 492 | if (pan.state == UIGestureRecognizerStateBegan) { 493 | _locationBeforePan = self.centerPanelContainer.frame.origin; 494 | } 495 | 496 | CGPoint translate = [pan translationInView:self.centerPanelContainer]; 497 | CGRect frame = _centerPanelRestingFrame; 498 | frame.origin.x += roundf([self _correctMovement:translate.x]); 499 | 500 | if (self.style == JASidePanelMultipleActive) { 501 | frame.size.width = self.view.bounds.size.width - frame.origin.x; 502 | } 503 | 504 | self.centerPanelContainer.frame = frame; 505 | 506 | // if center panel has focus, make sure correct side panel is revealed 507 | if (self.state == JASidePanelCenterVisible) { 508 | if (frame.origin.x > 0.0f) { 509 | [self _loadLeftPanel]; 510 | } else if(frame.origin.x < 0.0f) { 511 | [self _loadRightPanel]; 512 | } 513 | } 514 | 515 | // adjust side panel locations, if needed 516 | if (self.style == JASidePanelMultipleActive || self.pushesSidePanels) { 517 | [self _layoutSideContainers:NO duration:0]; 518 | } 519 | 520 | if (sender.state == UIGestureRecognizerStateEnded) { 521 | CGFloat deltaX = frame.origin.x - _locationBeforePan.x; 522 | if ([self _validateThreshold:deltaX]) { 523 | [self _completePan:deltaX]; 524 | } else { 525 | [self _undoPan]; 526 | } 527 | } else if (sender.state == UIGestureRecognizerStateCancelled) { 528 | [self _undoPan]; 529 | } 530 | } 531 | } 532 | 533 | - (void)_completePan:(CGFloat)deltaX { 534 | switch (self.state) { 535 | case JASidePanelCenterVisible: { 536 | if (deltaX > 0.0f) { 537 | [self _showLeftPanel:YES bounce:self.bounceOnSidePanelOpen]; 538 | } else { 539 | [self _showRightPanel:YES bounce:self.bounceOnSidePanelOpen]; 540 | } 541 | break; 542 | } 543 | case JASidePanelLeftVisible: { 544 | [self _showCenterPanel:YES bounce:self.bounceOnSidePanelClose]; 545 | break; 546 | } 547 | case JASidePanelRightVisible: { 548 | [self _showCenterPanel:YES bounce:self.bounceOnSidePanelClose]; 549 | break; 550 | } 551 | } 552 | } 553 | 554 | - (void)_undoPan { 555 | switch (self.state) { 556 | case JASidePanelCenterVisible: { 557 | [self _showCenterPanel:YES bounce:NO]; 558 | break; 559 | } 560 | case JASidePanelLeftVisible: { 561 | [self _showLeftPanel:YES bounce:NO]; 562 | break; 563 | } 564 | case JASidePanelRightVisible: { 565 | [self _showRightPanel:YES bounce:NO]; 566 | } 567 | } 568 | } 569 | 570 | #pragma mark - Tap Gesture 571 | 572 | - (void)setTapView:(UIView *)tapView { 573 | if (tapView != _tapView) { 574 | [_tapView removeFromSuperview]; 575 | _tapView = tapView; 576 | if (_tapView) { 577 | _tapView.frame = self.centerPanelContainer.bounds; 578 | _tapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 579 | [self _addTapGestureToView:_tapView]; 580 | if (self.recognizesPanGesture) { 581 | [self _addPanGestureToView:_tapView]; 582 | } 583 | [self.centerPanelContainer addSubview:_tapView]; 584 | } 585 | } 586 | } 587 | 588 | - (void)_addTapGestureToView:(UIView *)view { 589 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_centerPanelTapped:)]; 590 | [view addGestureRecognizer:tapGesture]; 591 | } 592 | 593 | - (void)_centerPanelTapped:(__unused UIGestureRecognizer *)gesture { 594 | [self _showCenterPanel:YES bounce:NO]; 595 | } 596 | 597 | #pragma mark - Internal Methods 598 | 599 | - (CGFloat)_correctMovement:(CGFloat)movement { 600 | CGFloat position = _centerPanelRestingFrame.origin.x + movement; 601 | if (self.state == JASidePanelCenterVisible) { 602 | if ((position > 0.0f && !self.leftPanel) || (position < 0.0f && !self.rightPanel)) { 603 | return 0.0f; 604 | } else if (!self.allowLeftOverpan && position > self.leftVisibleWidth) { 605 | return self.leftVisibleWidth; 606 | } else if (!self.allowRightOverpan && position < -self.rightVisibleWidth) { 607 | return -self.rightVisibleWidth; 608 | } 609 | } else if (self.state == JASidePanelRightVisible && !self.allowRightOverpan) { 610 | if (position < -self.rightVisibleWidth) { 611 | return 0.0f; 612 | } else if ((self.style == JASidePanelMultipleActive || self.pushesSidePanels) && position > 0.0f) { 613 | return -_centerPanelRestingFrame.origin.x; 614 | } else if (position > self.rightPanelContainer.frame.origin.x) { 615 | return self.rightPanelContainer.frame.origin.x - _centerPanelRestingFrame.origin.x; 616 | } 617 | } else if (self.state == JASidePanelLeftVisible && !self.allowLeftOverpan) { 618 | if (position > self.leftVisibleWidth) { 619 | return 0.0f; 620 | } else if ((self.style == JASidePanelMultipleActive || self.pushesSidePanels) && position < 0.0f) { 621 | return -_centerPanelRestingFrame.origin.x; 622 | } else if (position < self.leftPanelContainer.frame.origin.x) { 623 | return self.leftPanelContainer.frame.origin.x - _centerPanelRestingFrame.origin.x; 624 | } 625 | } 626 | return movement; 627 | } 628 | 629 | - (BOOL)_validateThreshold:(CGFloat)movement { 630 | CGFloat minimum = floorf(self.view.bounds.size.width * self.minimumMovePercentage); 631 | switch (self.state) { 632 | case JASidePanelLeftVisible: { 633 | return movement <= -minimum; 634 | } 635 | case JASidePanelCenterVisible: { 636 | return fabsf(movement) >= minimum; 637 | } 638 | case JASidePanelRightVisible: { 639 | return movement >= minimum; 640 | } 641 | } 642 | return NO; 643 | } 644 | 645 | - (BOOL)_isOnTopLevelViewController:(UIViewController *)root { 646 | if ([root isKindOfClass:[UINavigationController class]]) { 647 | UINavigationController *nav = (UINavigationController *)root; 648 | return [nav.viewControllers count] == 1; 649 | } else if ([root isKindOfClass:[UITabBarController class]]) { 650 | UITabBarController *tab = (UITabBarController *)root; 651 | return [self _isOnTopLevelViewController:tab.selectedViewController]; 652 | } 653 | return root != nil; 654 | } 655 | 656 | #pragma mark - Loading Panels 657 | 658 | - (void)_loadCenterPanelWithPreviousState:(JASidePanelState)previousState { 659 | [self _placeButtonForLeftPanel]; 660 | 661 | // for the multi-active style, it looks better if the new center starts out in it's fullsize and slides in 662 | if (self.style == JASidePanelMultipleActive) { 663 | switch (previousState) { 664 | case JASidePanelLeftVisible: { 665 | CGRect frame = self.centerPanelContainer.frame; 666 | frame.size.width = self.view.bounds.size.width; 667 | self.centerPanelContainer.frame = frame; 668 | break; 669 | } 670 | case JASidePanelRightVisible: { 671 | CGRect frame = self.centerPanelContainer.frame; 672 | frame.size.width = self.view.bounds.size.width; 673 | frame.origin.x = -self.rightVisibleWidth; 674 | self.centerPanelContainer.frame = frame; 675 | break; 676 | } 677 | default: 678 | break; 679 | } 680 | } 681 | 682 | _centerPanel.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 683 | _centerPanel.view.frame = self.centerPanelContainer.bounds; 684 | [self stylePanel:_centerPanel.view]; 685 | } 686 | 687 | - (void)_loadLeftPanel { 688 | self.rightPanelContainer.hidden = YES; 689 | if (self.leftPanelContainer.hidden && self.leftPanel) { 690 | 691 | if (!_leftPanel.view.superview) { 692 | [self _layoutSidePanels]; 693 | _leftPanel.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 694 | [self stylePanel:_leftPanel.view]; 695 | [self.leftPanelContainer addSubview:_leftPanel.view]; 696 | } 697 | 698 | self.leftPanelContainer.hidden = NO; 699 | } 700 | } 701 | 702 | - (void)_loadRightPanel { 703 | self.leftPanelContainer.hidden = YES; 704 | if (self.rightPanelContainer.hidden && self.rightPanel) { 705 | 706 | if (!_rightPanel.view.superview) { 707 | [self _layoutSidePanels]; 708 | _rightPanel.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 709 | [self stylePanel:_rightPanel.view]; 710 | [self.rightPanelContainer addSubview:_rightPanel.view]; 711 | } 712 | 713 | self.rightPanelContainer.hidden = NO; 714 | } 715 | } 716 | 717 | - (void)_unloadPanels { 718 | if (self.canUnloadLeftPanel && self.leftPanel.isViewLoaded) { 719 | [self.leftPanel.view removeFromSuperview]; 720 | } 721 | if (self.canUnloadRightPanel && self.rightPanel.isViewLoaded) { 722 | [self.rightPanel.view removeFromSuperview]; 723 | } 724 | } 725 | 726 | #pragma mark - Animation 727 | 728 | - (CGFloat)_calculatedDuration { 729 | CGFloat remaining = fabsf(self.centerPanelContainer.frame.origin.x - _centerPanelRestingFrame.origin.x); 730 | CGFloat max = _locationBeforePan.x == _centerPanelRestingFrame.origin.x ? remaining : fabsf(_locationBeforePan.x - _centerPanelRestingFrame.origin.x); 731 | return max > 0.0f ? self.maximumAnimationDuration * (remaining / max) : self.maximumAnimationDuration; 732 | } 733 | 734 | - (void)_animateCenterPanel:(BOOL)shouldBounce completion:(void (^)(BOOL finished))completion { 735 | CGFloat bounceDistance = (_centerPanelRestingFrame.origin.x - self.centerPanelContainer.frame.origin.x) * self.bouncePercentage; 736 | 737 | // looks bad if we bounce when the center panel grows 738 | if (_centerPanelRestingFrame.size.width > self.centerPanelContainer.frame.size.width) { 739 | shouldBounce = NO; 740 | } 741 | 742 | CGFloat duration = [self _calculatedDuration]; 743 | [UIView animateWithDuration:duration delay:0.0f options:UIViewAnimationOptionCurveLinear|UIViewAnimationOptionLayoutSubviews animations:^{ 744 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 745 | [self styleContainer:self.centerPanelContainer animate:YES duration:duration]; 746 | if (self.style == JASidePanelMultipleActive || self.pushesSidePanels) { 747 | [self _layoutSideContainers:NO duration:0.0f]; 748 | } 749 | } completion:^(BOOL finished) { 750 | if (shouldBounce) { 751 | // make sure correct panel is displayed under the bounce 752 | if (self.state == JASidePanelCenterVisible) { 753 | if (bounceDistance > 0.0f) { 754 | [self _loadLeftPanel]; 755 | } else { 756 | [self _loadRightPanel]; 757 | } 758 | } 759 | // animate the bounce 760 | [UIView animateWithDuration:self.bounceDuration delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{ 761 | CGRect bounceFrame = _centerPanelRestingFrame; 762 | bounceFrame.origin.x += bounceDistance; 763 | self.centerPanelContainer.frame = bounceFrame; 764 | } completion:^(__unused BOOL finished2) { 765 | [UIView animateWithDuration:self.bounceDuration delay:0.0f options:UIViewAnimationOptionCurveEaseIn animations:^{ 766 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 767 | } completion:completion]; 768 | }]; 769 | } else if (completion) { 770 | completion(finished); 771 | } 772 | }]; 773 | } 774 | 775 | #pragma mark - Panel Sizing 776 | 777 | - (CGRect)_adjustCenterFrame { 778 | CGRect frame = self.view.bounds; 779 | switch (self.state) { 780 | case JASidePanelCenterVisible: { 781 | frame.origin.x = 0.0f; 782 | if (self.style == JASidePanelMultipleActive) { 783 | frame.size.width = self.view.bounds.size.width; 784 | } 785 | break; 786 | } 787 | case JASidePanelLeftVisible: { 788 | frame.origin.x = self.leftVisibleWidth; 789 | if (self.style == JASidePanelMultipleActive) { 790 | frame.size.width = self.view.bounds.size.width - self.leftVisibleWidth; 791 | } 792 | break; 793 | } 794 | case JASidePanelRightVisible: { 795 | frame.origin.x = -self.rightVisibleWidth; 796 | if (self.style == JASidePanelMultipleActive) { 797 | frame.origin.x = 0.0f; 798 | frame.size.width = self.view.bounds.size.width - self.rightVisibleWidth; 799 | } 800 | break; 801 | } 802 | } 803 | _centerPanelRestingFrame = frame; 804 | return _centerPanelRestingFrame; 805 | } 806 | 807 | - (CGFloat)leftVisibleWidth { 808 | if (self.centerPanelHidden && self.shouldResizeLeftPanel) { 809 | return self.view.bounds.size.width; 810 | } else { 811 | return self.leftFixedWidth ? self.leftFixedWidth : floorf(self.view.bounds.size.width * self.leftGapPercentage); 812 | } 813 | } 814 | 815 | - (CGFloat)rightVisibleWidth { 816 | if (self.centerPanelHidden && self.shouldResizeRightPanel) { 817 | return self.view.bounds.size.width; 818 | } else { 819 | return self.rightFixedWidth ? self.rightFixedWidth : floorf(self.view.bounds.size.width * self.rightGapPercentage); 820 | } 821 | } 822 | 823 | #pragma mark - Showing Panels 824 | 825 | - (void)_showLeftPanel:(BOOL)animated bounce:(BOOL)shouldBounce { 826 | self.state = JASidePanelLeftVisible; 827 | [self _loadLeftPanel]; 828 | 829 | [self _adjustCenterFrame]; 830 | 831 | if (animated) { 832 | [self _animateCenterPanel:shouldBounce completion:nil]; 833 | } else { 834 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 835 | [self styleContainer:self.centerPanelContainer animate:NO duration:0.0f]; 836 | if (self.style == JASidePanelMultipleActive || self.pushesSidePanels) { 837 | [self _layoutSideContainers:NO duration:0.0f]; 838 | } 839 | } 840 | 841 | if (self.style == JASidePanelSingleActive) { 842 | self.tapView = [[UIView alloc] init]; 843 | } 844 | [self _toggleScrollsToTopForCenter:NO left:YES right:NO]; 845 | } 846 | 847 | - (void)_showRightPanel:(BOOL)animated bounce:(BOOL)shouldBounce { 848 | self.state = JASidePanelRightVisible; 849 | [self _loadRightPanel]; 850 | 851 | [self _adjustCenterFrame]; 852 | 853 | if (animated) { 854 | [self _animateCenterPanel:shouldBounce completion:nil]; 855 | } else { 856 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 857 | [self styleContainer:self.centerPanelContainer animate:NO duration:0.0f]; 858 | if (self.style == JASidePanelMultipleActive || self.pushesSidePanels) { 859 | [self _layoutSideContainers:NO duration:0.0f]; 860 | } 861 | } 862 | 863 | if (self.style == JASidePanelSingleActive) { 864 | self.tapView = [[UIView alloc] init]; 865 | } 866 | [self _toggleScrollsToTopForCenter:NO left:NO right:YES]; 867 | } 868 | 869 | - (void)_showCenterPanel:(BOOL)animated bounce:(BOOL)shouldBounce { 870 | self.state = JASidePanelCenterVisible; 871 | 872 | [self _adjustCenterFrame]; 873 | 874 | if (animated) { 875 | [self _animateCenterPanel:shouldBounce completion:^(__unused BOOL finished) { 876 | self.leftPanelContainer.hidden = YES; 877 | self.rightPanelContainer.hidden = YES; 878 | [self _unloadPanels]; 879 | }]; 880 | } else { 881 | self.centerPanelContainer.frame = _centerPanelRestingFrame; 882 | [self styleContainer:self.centerPanelContainer animate:NO duration:0.0f]; 883 | if (self.style == JASidePanelMultipleActive || self.pushesSidePanels) { 884 | [self _layoutSideContainers:NO duration:0.0f]; 885 | } 886 | self.leftPanelContainer.hidden = YES; 887 | self.rightPanelContainer.hidden = YES; 888 | [self _unloadPanels]; 889 | } 890 | 891 | self.tapView = nil; 892 | [self _toggleScrollsToTopForCenter:YES left:NO right:NO]; 893 | } 894 | 895 | - (void)_hideCenterPanel { 896 | self.centerPanelContainer.hidden = YES; 897 | if (self.centerPanel.isViewLoaded) { 898 | [self.centerPanel.view removeFromSuperview]; 899 | } 900 | } 901 | 902 | - (void)_unhideCenterPanel { 903 | self.centerPanelContainer.hidden = NO; 904 | if (!self.centerPanel.view.superview) { 905 | self.centerPanel.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 906 | self.centerPanel.view.frame = self.centerPanelContainer.bounds; 907 | [self stylePanel:self.centerPanel.view]; 908 | [self.centerPanelContainer addSubview:self.centerPanel.view]; 909 | } 910 | } 911 | 912 | - (void)_toggleScrollsToTopForCenter:(BOOL)center left:(BOOL)left right:(BOOL)right { 913 | // iPhone only supports 1 active UIScrollViewController at a time 914 | if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { 915 | [self _toggleScrollsToTop:center forView:self.centerPanelContainer]; 916 | [self _toggleScrollsToTop:left forView:self.leftPanelContainer]; 917 | [self _toggleScrollsToTop:right forView:self.rightPanelContainer]; 918 | } 919 | } 920 | 921 | - (BOOL)_toggleScrollsToTop:(BOOL)enabled forView:(UIView *)view { 922 | if ([view isKindOfClass:[UIScrollView class]]) { 923 | UIScrollView *scrollView = (UIScrollView *)view; 924 | scrollView.scrollsToTop = enabled; 925 | return YES; 926 | } else { 927 | for (UIView *subview in view.subviews) { 928 | if([self _toggleScrollsToTop:enabled forView:subview]) { 929 | return YES; 930 | } 931 | } 932 | } 933 | return NO; 934 | } 935 | 936 | #pragma mark - Key Value Observing 937 | 938 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context { 939 | if (context == &ja_kvoContext) { 940 | if ([keyPath isEqualToString:@"view"]) { 941 | if (self.centerPanel.isViewLoaded && self.recognizesPanGesture) { 942 | [self _addPanGestureToView:self.centerPanel.view]; 943 | } 944 | } else if ([keyPath isEqualToString:@"viewControllers"] && object == self.centerPanel) { 945 | // view controllers have changed, need to replace the button 946 | [self _placeButtonForLeftPanel]; 947 | } 948 | } else { 949 | [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 950 | } 951 | } 952 | 953 | #pragma mark - Public Methods 954 | 955 | - (UIBarButtonItem *)leftButtonForCenterPanel { 956 | return [[UIBarButtonItem alloc] initWithImage:[[self class] defaultImage] style:UIBarButtonItemStylePlain target:self action:@selector(toggleLeftPanel:)]; 957 | } 958 | 959 | - (void)showLeftPanel:(BOOL)animated { 960 | [self showLeftPanelAnimated:animated]; 961 | } 962 | 963 | - (void)showRightPanel:(BOOL)animated { 964 | [self showRightPanelAnimated:animated]; 965 | } 966 | 967 | - (void)showCenterPanel:(BOOL)animated { 968 | [self showCenterPanelAnimated:animated]; 969 | } 970 | 971 | - (void)showLeftPanelAnimated:(BOOL)animated { 972 | [self _showLeftPanel:animated bounce:NO]; 973 | } 974 | 975 | - (void)showRightPanelAnimated:(BOOL)animated { 976 | [self _showRightPanel:animated bounce:NO]; 977 | } 978 | 979 | - (void)showCenterPanelAnimated:(BOOL)animated { 980 | // make sure center panel isn't hidden 981 | if (_centerPanelHidden) { 982 | _centerPanelHidden = NO; 983 | [self _unhideCenterPanel]; 984 | } 985 | [self _showCenterPanel:animated bounce:NO]; 986 | } 987 | 988 | - (void)toggleLeftPanel:(__unused id)sender { 989 | if (self.state == JASidePanelLeftVisible) { 990 | [self _showCenterPanel:YES bounce:NO]; 991 | } else if (self.state == JASidePanelCenterVisible) { 992 | [self _showLeftPanel:YES bounce:NO]; 993 | } 994 | } 995 | 996 | - (void)toggleRightPanel:(__unused id)sender { 997 | if (self.state == JASidePanelRightVisible) { 998 | [self _showCenterPanel:YES bounce:NO]; 999 | } else if (self.state == JASidePanelCenterVisible) { 1000 | [self _showRightPanel:YES bounce:NO]; 1001 | } 1002 | } 1003 | 1004 | - (void)setCenterPanelHidden:(BOOL)centerPanelHidden { 1005 | [self setCenterPanelHidden:centerPanelHidden animated:NO duration:0.0]; 1006 | } 1007 | 1008 | - (void)setCenterPanelHidden:(BOOL)centerPanelHidden animated:(BOOL)animated duration:(NSTimeInterval) duration { 1009 | if (centerPanelHidden != _centerPanelHidden && self.state != JASidePanelCenterVisible) { 1010 | _centerPanelHidden = centerPanelHidden; 1011 | duration = animated ? duration : 0.0f; 1012 | if (centerPanelHidden) { 1013 | [UIView animateWithDuration:duration animations:^{ 1014 | CGRect frame = self.centerPanelContainer.frame; 1015 | frame.origin.x = self.state == JASidePanelLeftVisible ? self.centerPanelContainer.frame.size.width : -self.centerPanelContainer.frame.size.width; 1016 | self.centerPanelContainer.frame = frame; 1017 | [self _layoutSideContainers:NO duration:0]; 1018 | if (self.shouldResizeLeftPanel || self.shouldResizeRightPanel) { 1019 | [self _layoutSidePanels]; 1020 | } 1021 | } completion:^(__unused BOOL finished) { 1022 | // need to double check in case the user tapped really fast 1023 | if (_centerPanelHidden) { 1024 | [self _hideCenterPanel]; 1025 | } 1026 | }]; 1027 | } else { 1028 | [self _unhideCenterPanel]; 1029 | [UIView animateWithDuration:duration animations:^{ 1030 | if (self.state == JASidePanelLeftVisible) { 1031 | [self showLeftPanelAnimated:NO]; 1032 | } else { 1033 | [self showRightPanelAnimated:NO]; 1034 | } 1035 | if (self.shouldResizeLeftPanel || self.shouldResizeRightPanel) { 1036 | [self _layoutSidePanels]; 1037 | } 1038 | }]; 1039 | } 1040 | } 1041 | } 1042 | 1043 | @end 1044 | -------------------------------------------------------------------------------- /JASidePanels/Source/UIViewController+JASidePanel.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import 27 | 28 | @class JASidePanelController; 29 | 30 | /* This optional category provides a convenience method for finding the current 31 | side panel controller that your view controller belongs to. It is similar to the 32 | Apple provided "navigationController" and "tabBarController" methods. 33 | */ 34 | @interface UIViewController (JASidePanel) 35 | 36 | // The nearest ancestor in the view controller hierarchy that is a side panel controller. 37 | @property (nonatomic, weak, readonly) JASidePanelController *sidePanelController; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /JASidePanels/Source/UIViewController+JASidePanel.m: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Jesse Andersen. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | If you happen to meet one of the copyright holders in a bar you are obligated 15 | to buy them one pint of beer. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #import "UIViewController+JASidePanel.h" 27 | 28 | #import "JASidePanelController.h" 29 | 30 | @implementation UIViewController (JASidePanel) 31 | 32 | - (JASidePanelController *)sidePanelController { 33 | UIViewController *iter = self.parentViewController; 34 | while (iter) { 35 | if ([iter isKindOfClass:[JASidePanelController class]]) { 36 | return (JASidePanelController *)iter; 37 | } else if (iter.parentViewController && iter.parentViewController != iter) { 38 | iter = iter.parentViewController; 39 | } else { 40 | iter = nil; 41 | } 42 | } 43 | return nil; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /JASidePanels/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | JASidePanels 2 | === 3 | 4 | JASidePanels is a UIViewController container designed for presenting a center panel with revealable side panels - one to the left and one to the right. The main inspiration for this project is the menuing system in Path 2.0 and Facebook's iOS apps. 5 | 6 | Demo 7 | --- 8 | ![iPhone Example](https://img.skitch.com/20120322-dx6k69577ra37wwgqgmsgksqpx.jpg) 9 | ![iPad Example](https://img.skitch.com/20120322-ttu951nfb8cpd5ti5r1ni8428y.jpg) 10 | 11 | Example 1: Code 12 | --- 13 | 14 | ``` objc 15 | 16 | #import "JAAppDelegate.h" 17 | 18 | #import "JASidePanelController.h" 19 | #import "JACenterViewController.h" 20 | #import "JALeftViewController.h" 21 | #import "JARightViewController.h" 22 | 23 | @implementation JAAppDelegate 24 | 25 | @synthesize window = _window; 26 | @synthesize viewController = _viewController; 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 29 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 30 | 31 | self.viewController = [[JASidePanelController alloc] init]; 32 | self.viewController.leftPanel = [[JALeftViewController alloc] init]; 33 | self.viewController.centerPanel = [[UINavigationController alloc] initWithRootViewController:[[JACenterViewController alloc] init]]; 34 | self.viewController.rightPanel = [[JARightViewController alloc] init]; 35 | 36 | self.window.rootViewController = self.viewController; 37 | [self.window makeKeyAndVisible]; 38 | return YES; 39 | } 40 | 41 | 42 | @end 43 | 44 | ``` 45 | 46 | Example 2: Storyboards 47 | --- 48 | 49 | 1. Create a subclass of `JASidePanelController`. In this example we call it `MySidePanelController`. 50 | 2. In the Storyboard designate the root view's owner as `MySidePanelController`. 51 | 3. Make sure to `#import "JASidePanelController.h"` in `MySidePanelController.h`. 52 | 4. Add more view controllers to your Storyboard, and give them identifiers "leftViewController", "centerViewController" and "rightViewController". Note that in the new XCode the identifier is called "Storyboard ID" and can be found in the Identity inspector (in older versions the identifier is found in the Attributes inspector). 53 | 5. Add a method `awakeFromNib` to `MySidePanelController.m` with the following code: 54 | 55 | ``` objc 56 | 57 | -(void) awakeFromNib 58 | { 59 | [self setLeftPanel:[self.storyboard instantiateViewControllerWithIdentifier:@"leftViewController"]]; 60 | [self setCenterPanel:[self.storyboard instantiateViewControllerWithIdentifier:@"centerViewController"]]; 61 | [self setRightPanel:[self.storyboard instantiateViewControllerWithIdentifier:@"rightViewController"]]; 62 | } 63 | 64 | ``` 65 | 66 | Usage 67 | --- 68 | 69 | Only two files are required for using JASidePanels: ` JASidePanelController.h ` & ` JASidePanelController.m ` 70 | 71 | The easiest way to use JASidePanels is to copy the files into your XCode Project. 72 | 73 | Alternatively, you can setup a git submodule and reference the files in your Xcode project. I prefer this method as it enables you to receive bugfixes/updates for the project. 74 | ` git submodule add https://github.com/gotosleep/JASidePanels.git JASidePanels ` 75 | 76 | Make sure to include the QuartzCore framework in your target. 77 | 78 | UIViewController+JASidePanel Category 79 | --- 80 | 81 | A UIViewController+JASidePanel category is included in the project. Usage is optional. The category adds a single convenience property to UIViewController: __sidePanelController__. The property provides access to the nearest JASidePanelController ancestor in your view controller heirarchy. It behaves similar to the _navigationController_ UIViewController property provided by Apple. Here's an example: 82 | 83 | ``` objc 84 | 85 | #import "JASidePanelController.h" 86 | #import "UIViewController+JASidePanel.h" 87 | 88 | @interface JALeftViewController : UIViewController 89 | @end 90 | 91 | @implementation JALeftViewController 92 | 93 | - (void)viewDidLoad { 94 | [super viewDidLoad]; 95 | // sweet, I can access my parent JASidePanelController as a property! 96 | [self.sidePanelController showCenterPanelAnimated:YES]; 97 | } 98 | 99 | @end 100 | 101 | ``` 102 | 103 | Requirements 104 | --- 105 | 106 | JASidePanels requires iOS 5.0+ and Xcode 4.3+ The projects uses ARC, but it may be used with non-ARC projects by setting the: ` -fobjc-arc ` compiler flag on ` JASidePanelController.m `. You can set this flag under Target -> Build Phases -> Compile Sources 107 | 108 | Apps 109 | --- 110 | JASidePanels is used in the following apps: 111 | 112 | * Scribd - [http://itunes.apple.com/us/app/scribd-worlds-largest-online/id542557212?ls=1&mt=8](http://itunes.apple.com/us/app/scribd-worlds-largest-online/id542557212?ls=1&mt=8) 113 | * Float Reader - [http://itunes.apple.com/us/app/float-reader/id447992005?ls=1&mt=8](http://itunes.apple.com/us/app/float-reader/id447992005?ls=1&mt=8) 114 | 115 | License 116 | --- 117 | 118 | ``` 119 | 120 | Permission is hereby granted, free of charge, to any person obtaining a copy of 121 | this software and associated documentation files (the "Software"), to deal in 122 | the Software without restriction, including without limitation the rights to 123 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 124 | of the Software, and to permit persons to whom the Software is furnished to do 125 | so, subject to the following conditions: 126 | 127 | The above copyright notice and this permission notice shall be included in all 128 | copies or substantial portions of the Software. 129 | 130 | If you happen to meet one of the copyright holders in a bar you are obligated 131 | to buy them one pint of beer. 132 | 133 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 134 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 135 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 136 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 137 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 138 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 139 | SOFTWARE. 140 | 141 | ``` 142 | 143 | Alternatives 144 | --- 145 | Other projects implementing a similar UI 146 | 147 | * DDMenuController - [https://github.com/devindoty/DDMenuController](https://github.com/devindoty/DDMenuController) 148 | * JTRevealSidebarDemo - [https://github.com/mystcolor/JTRevealSidebarDemo](https://github.com/mystcolor/JTRevealSidebarDemo) 149 | * ECSlidingViewController - [https://github.com/edgecase/ECSlidingViewController](https://github.com/edgecase/ECSlidingViewController) 150 | * ViewDeck - [https://github.com/Inferis/ViewDeck](https://github.com/Inferis/ViewDeck) 151 | * ZUUIRevealController - [https://github.com/pkluz/ZUUIRevealController](https://github.com/pkluz/ZUUIRevealController) 152 | * GHSidebarNav - [https://github.com/gresrun/GHSidebarNav](https://github.com/gresrun/GHSidebarNav) 153 | --------------------------------------------------------------------------------