├── .gitignore ├── .travis.yml ├── README.md ├── RMParallax.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── mbabiy.xcuserdatad │ └── xcschemes │ ├── RMParallax.xcscheme │ └── xcschememanagement.plist └── RMParallax ├── AppDelegate.swift ├── Base.lproj ├── LaunchScreen.xib └── Main.storyboard ├── Images.xcassets ├── AppIcon.appiconset │ ├── Contents.json │ ├── icon60@2x.png │ ├── icon76.png │ ├── icon76@2x.png │ └── icon83.5@2x.png ├── close_button.imageset │ ├── Contents.json │ └── close_button@2x.png ├── item1.imageset │ ├── Contents.json │ └── splash_4.png ├── item2.imageset │ ├── Contents.json │ └── splash_3.png └── item3.imageset │ ├── Contents.json │ └── splash_2.png ├── Info.plist ├── RMParallax ├── RMController.swift ├── RMItem.swift └── RMStyle.swift └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | env: 4 | global: 5 | - LC_CTYPE=en_US.UTF-8 6 | - LANG=en_US.UTF-8 7 | - PROJECT=RMParallax.xcodeproj 8 | - IOS_FRAMEWORK_SCHEME="RMParallax iOS" 9 | matrix: 10 | - DESTINATION="OS=9.0,name=iPhone 6S" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" POD_LINT="YES" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RMParallax 2 | 3 | [![Build Status](https://travis-ci.org/michaelbabiy/RMParallax.svg?branch=api)](https://travis-ci.org/michaelbabiy/RMParallax) 4 | [![Swift 3](https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 5 | [![Platforms](https://img.shields.io/badge/Platforms-iOS-lightgray.svg?style=flat)](https://developer.apple.com/swift/) 6 | [![Twitter](https://img.shields.io/badge/Twitter-@michaelbabiy-blue.svg?style=flat)](http://twitter.com/michaelbabiy) 7 | 8 | RMParallax is a library designed to help you introduce the features of your app on the first app launch. See it in [action](https://www.youtube.com/watch?v=5QRMohq1nBE). 9 | 10 | ## Requirements 11 | 12 | - iOS 9.0+ 13 | - Xcode 8.0+ 14 | - Swift 3.0+ 15 | 16 | ## Installation 17 | 18 | - [X] Add **RMParallax** folder containing ```RMController.swift```, ```RMItem.swift```, ```RMStyle.swift``` to your project 19 | - [X] There is no step two 20 | 21 | ## Usage 22 | 23 | RMParallax is simple to use. All you have to do is create RMItem: 24 | 25 | ```swift 26 | let item1 = RMItem(image: UIImage(named: "item1")!, text: "SHARE LIGHTBOXES WITH YOUR TEAM") 27 | let item2 = RMItem(image: UIImage(named: "item2")!, text: "FOLLOW WORLD CLASS PHOTOGRAPHERS") 28 | let item3 = RMItem(image: UIImage(named: "item3")!, text: "EXPLORE OUR COLLECTION BY CATEGORY") 29 | let items = [item1, item2, item3] 30 | ``` 31 | 32 | Create RMParallax controller with items you created earlier: 33 | 34 | ```swift 35 | let introducing = RMController(with: items) 36 | introducing.dismiss = { 37 | introducing.view.removeFromSuperview() 38 | introducing.removeFromParentViewController() 39 | } 40 | ``` 41 | 42 | Add your RMParallax controller to the view controller: 43 | 44 | ```swift 45 | addChildViewController(introducing) 46 | view.addSubview(introducing.view) 47 | introducing.didMove(toParentViewController: self) 48 | ``` 49 | 50 | > RMParallax uses closures to notify presenting view controller when the user is done paging through. 51 | 52 | Please checkout included sample project. 53 | 54 | ## Creators 55 | * [Michael Babiy](http://twitter.com/michael.babiy) 56 | * Raphael Miller -------------------------------------------------------------------------------- /RMParallax.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1C04F3E01A760BED005C2DA7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04F3DF1A760BED005C2DA7 /* AppDelegate.swift */; }; 11 | 1C04F3E21A760BED005C2DA7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04F3E11A760BED005C2DA7 /* ViewController.swift */; }; 12 | 1C04F3E51A760BED005C2DA7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1C04F3E31A760BED005C2DA7 /* Main.storyboard */; }; 13 | 1C04F3E71A760BED005C2DA7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1C04F3E61A760BED005C2DA7 /* Images.xcassets */; }; 14 | 1C04F3EA1A760BED005C2DA7 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1C04F3E81A760BED005C2DA7 /* LaunchScreen.xib */; }; 15 | 1C04F4011A760C75005C2DA7 /* RMItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04F4001A760C75005C2DA7 /* RMItem.swift */; }; 16 | 1C04F4031A760D2A005C2DA7 /* RMController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C04F4021A760D2A005C2DA7 /* RMController.swift */; }; 17 | 1C18ECC81D9C9605002A340C /* RMStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C18ECC71D9C9605002A340C /* RMStyle.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 1C04F3DA1A760BED005C2DA7 /* RMParallax.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RMParallax.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 1C04F3DE1A760BED005C2DA7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | 1C04F3DF1A760BED005C2DA7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 1C04F3E11A760BED005C2DA7 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 1C04F3E41A760BED005C2DA7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 1C04F3E61A760BED005C2DA7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 27 | 1C04F3E91A760BED005C2DA7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 28 | 1C04F4001A760C75005C2DA7 /* RMItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RMItem.swift; sourceTree = ""; }; 29 | 1C04F4021A760D2A005C2DA7 /* RMController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RMController.swift; sourceTree = ""; }; 30 | 1C18ECC71D9C9605002A340C /* RMStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RMStyle.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 1C04F3D71A760BED005C2DA7 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 1C04F3D11A760BED005C2DA7 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 1C04F3DC1A760BED005C2DA7 /* RMParallax */, 48 | 1C04F3DB1A760BED005C2DA7 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 1C04F3DB1A760BED005C2DA7 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 1C04F3DA1A760BED005C2DA7 /* RMParallax.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 1C04F3DC1A760BED005C2DA7 /* RMParallax */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 1C04F3DF1A760BED005C2DA7 /* AppDelegate.swift */, 64 | 1C04F3E11A760BED005C2DA7 /* ViewController.swift */, 65 | 1C04F3FF1A760C1B005C2DA7 /* RMParallax */, 66 | 1C04F3DD1A760BED005C2DA7 /* Supporting Files */, 67 | ); 68 | path = RMParallax; 69 | sourceTree = ""; 70 | }; 71 | 1C04F3DD1A760BED005C2DA7 /* Supporting Files */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 1C04F3E61A760BED005C2DA7 /* Images.xcassets */, 75 | 1C04F3DE1A760BED005C2DA7 /* Info.plist */, 76 | 1C04F3E81A760BED005C2DA7 /* LaunchScreen.xib */, 77 | 1C04F3E31A760BED005C2DA7 /* Main.storyboard */, 78 | ); 79 | name = "Supporting Files"; 80 | sourceTree = ""; 81 | }; 82 | 1C04F3FF1A760C1B005C2DA7 /* RMParallax */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 1C04F4021A760D2A005C2DA7 /* RMController.swift */, 86 | 1C04F4001A760C75005C2DA7 /* RMItem.swift */, 87 | 1C18ECC71D9C9605002A340C /* RMStyle.swift */, 88 | ); 89 | path = RMParallax; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | 1C04F3D91A760BED005C2DA7 /* RMParallax */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = 1C04F3F91A760BED005C2DA7 /* Build configuration list for PBXNativeTarget "RMParallax" */; 98 | buildPhases = ( 99 | 1C04F3D61A760BED005C2DA7 /* Sources */, 100 | 1C04F3D71A760BED005C2DA7 /* Frameworks */, 101 | 1C04F3D81A760BED005C2DA7 /* Resources */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = RMParallax; 108 | productName = RMParallax; 109 | productReference = 1C04F3DA1A760BED005C2DA7 /* RMParallax.app */; 110 | productType = "com.apple.product-type.application"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | 1C04F3D21A760BED005C2DA7 /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | LastSwiftMigration = 0700; 119 | LastSwiftUpdateCheck = 0700; 120 | LastUpgradeCheck = 0800; 121 | ORGANIZATIONNAME = "Raphael Miller & Michael Babiy"; 122 | TargetAttributes = { 123 | 1C04F3D91A760BED005C2DA7 = { 124 | CreatedOnToolsVersion = 6.1.1; 125 | DevelopmentTeam = VV4VQB3X2M; 126 | DevelopmentTeamName = "Michael Babiy"; 127 | LastSwiftMigration = 0800; 128 | }; 129 | }; 130 | }; 131 | buildConfigurationList = 1C04F3D51A760BED005C2DA7 /* Build configuration list for PBXProject "RMParallax" */; 132 | compatibilityVersion = "Xcode 3.2"; 133 | developmentRegion = English; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | Base, 138 | ); 139 | mainGroup = 1C04F3D11A760BED005C2DA7; 140 | productRefGroup = 1C04F3DB1A760BED005C2DA7 /* Products */; 141 | projectDirPath = ""; 142 | projectRoot = ""; 143 | targets = ( 144 | 1C04F3D91A760BED005C2DA7 /* RMParallax */, 145 | ); 146 | }; 147 | /* End PBXProject section */ 148 | 149 | /* Begin PBXResourcesBuildPhase section */ 150 | 1C04F3D81A760BED005C2DA7 /* Resources */ = { 151 | isa = PBXResourcesBuildPhase; 152 | buildActionMask = 2147483647; 153 | files = ( 154 | 1C04F3E51A760BED005C2DA7 /* Main.storyboard in Resources */, 155 | 1C04F3EA1A760BED005C2DA7 /* LaunchScreen.xib in Resources */, 156 | 1C04F3E71A760BED005C2DA7 /* Images.xcassets in Resources */, 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXResourcesBuildPhase section */ 161 | 162 | /* Begin PBXSourcesBuildPhase section */ 163 | 1C04F3D61A760BED005C2DA7 /* Sources */ = { 164 | isa = PBXSourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 1C18ECC81D9C9605002A340C /* RMStyle.swift in Sources */, 168 | 1C04F3E21A760BED005C2DA7 /* ViewController.swift in Sources */, 169 | 1C04F4011A760C75005C2DA7 /* RMItem.swift in Sources */, 170 | 1C04F3E01A760BED005C2DA7 /* AppDelegate.swift in Sources */, 171 | 1C04F4031A760D2A005C2DA7 /* RMController.swift in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin PBXVariantGroup section */ 178 | 1C04F3E31A760BED005C2DA7 /* Main.storyboard */ = { 179 | isa = PBXVariantGroup; 180 | children = ( 181 | 1C04F3E41A760BED005C2DA7 /* Base */, 182 | ); 183 | name = Main.storyboard; 184 | sourceTree = ""; 185 | }; 186 | 1C04F3E81A760BED005C2DA7 /* LaunchScreen.xib */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 1C04F3E91A760BED005C2DA7 /* Base */, 190 | ); 191 | name = LaunchScreen.xib; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXVariantGroup section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 1C04F3F71A760BED005C2DA7 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 202 | CLANG_CXX_LIBRARY = "libc++"; 203 | CLANG_ENABLE_MODULES = YES; 204 | CLANG_ENABLE_OBJC_ARC = YES; 205 | CLANG_WARN_BOOL_CONVERSION = YES; 206 | CLANG_WARN_CONSTANT_CONVERSION = YES; 207 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 208 | CLANG_WARN_EMPTY_BODY = YES; 209 | CLANG_WARN_ENUM_CONVERSION = YES; 210 | CLANG_WARN_INT_CONVERSION = YES; 211 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 212 | CLANG_WARN_UNREACHABLE_CODE = YES; 213 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 214 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 215 | COPY_PHASE_STRIP = NO; 216 | ENABLE_STRICT_OBJC_MSGSEND = YES; 217 | ENABLE_TESTABILITY = YES; 218 | GCC_C_LANGUAGE_STANDARD = gnu99; 219 | GCC_DYNAMIC_NO_PIC = NO; 220 | GCC_NO_COMMON_BLOCKS = YES; 221 | GCC_OPTIMIZATION_LEVEL = 0; 222 | GCC_PREPROCESSOR_DEFINITIONS = ( 223 | "DEBUG=1", 224 | "$(inherited)", 225 | ); 226 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 227 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 228 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 229 | GCC_WARN_UNDECLARED_SELECTOR = YES; 230 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 231 | GCC_WARN_UNUSED_FUNCTION = YES; 232 | GCC_WARN_UNUSED_VARIABLE = YES; 233 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 234 | MTL_ENABLE_DEBUG_INFO = YES; 235 | ONLY_ACTIVE_ARCH = YES; 236 | SDKROOT = iphoneos; 237 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 238 | TARGETED_DEVICE_FAMILY = "1,2"; 239 | }; 240 | name = Debug; 241 | }; 242 | 1C04F3F81A760BED005C2DA7 /* Release */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_CONSTANT_CONVERSION = YES; 252 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 253 | CLANG_WARN_EMPTY_BODY = YES; 254 | CLANG_WARN_ENUM_CONVERSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_UNREACHABLE_CODE = YES; 258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 259 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 260 | COPY_PHASE_STRIP = YES; 261 | ENABLE_NS_ASSERTIONS = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu99; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 272 | MTL_ENABLE_DEBUG_INFO = NO; 273 | SDKROOT = iphoneos; 274 | TARGETED_DEVICE_FAMILY = "1,2"; 275 | VALIDATE_PRODUCT = YES; 276 | }; 277 | name = Release; 278 | }; 279 | 1C04F3FA1A760BED005C2DA7 /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 283 | DEVELOPMENT_TEAM = VV4VQB3X2M; 284 | INFOPLIST_FILE = RMParallax/Info.plist; 285 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 286 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 287 | PRODUCT_BUNDLE_IDENTIFIER = "com.michaelbabiy.$(PRODUCT_NAME:rfc1034identifier)"; 288 | PRODUCT_NAME = "$(TARGET_NAME)"; 289 | SWIFT_VERSION = 3.0; 290 | }; 291 | name = Debug; 292 | }; 293 | 1C04F3FB1A760BED005C2DA7 /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | DEVELOPMENT_TEAM = VV4VQB3X2M; 298 | INFOPLIST_FILE = RMParallax/Info.plist; 299 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 301 | PRODUCT_BUNDLE_IDENTIFIER = "com.michaelbabiy.$(PRODUCT_NAME:rfc1034identifier)"; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 304 | SWIFT_VERSION = 3.0; 305 | }; 306 | name = Release; 307 | }; 308 | /* End XCBuildConfiguration section */ 309 | 310 | /* Begin XCConfigurationList section */ 311 | 1C04F3D51A760BED005C2DA7 /* Build configuration list for PBXProject "RMParallax" */ = { 312 | isa = XCConfigurationList; 313 | buildConfigurations = ( 314 | 1C04F3F71A760BED005C2DA7 /* Debug */, 315 | 1C04F3F81A760BED005C2DA7 /* Release */, 316 | ); 317 | defaultConfigurationIsVisible = 0; 318 | defaultConfigurationName = Release; 319 | }; 320 | 1C04F3F91A760BED005C2DA7 /* Build configuration list for PBXNativeTarget "RMParallax" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | 1C04F3FA1A760BED005C2DA7 /* Debug */, 324 | 1C04F3FB1A760BED005C2DA7 /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | /* End XCConfigurationList section */ 330 | }; 331 | rootObject = 1C04F3D21A760BED005C2DA7 /* Project object */; 332 | } 333 | -------------------------------------------------------------------------------- /RMParallax.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RMParallax.xcodeproj/xcuserdata/mbabiy.xcuserdatad/xcschemes/RMParallax.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /RMParallax.xcodeproj/xcuserdata/mbabiy.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RMParallax.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 1C04F3D91A760BED005C2DA7 16 | 17 | primary 18 | 19 | 20 | 1C04F3EE1A760BED005C2DA7 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /RMParallax/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // RMParallax 2 | // 3 | // Copyright (c) 2016 RMParallax 4 | // 5 | // Created by Raphael Miller & Michael Babiy 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 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 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | 27 | @UIApplicationMain 28 | 29 | class AppDelegate: UIResponder, UIApplicationDelegate { 30 | var window: UIWindow? 31 | 32 | private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 33 | return true 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /RMParallax/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /RMParallax/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "size" : "60x60", 25 | "idiom" : "iphone", 26 | "filename" : "icon60@2x.png", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "idiom" : "iphone", 31 | "size" : "60x60", 32 | "scale" : "3x" 33 | }, 34 | { 35 | "idiom" : "ipad", 36 | "size" : "29x29", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "idiom" : "ipad", 41 | "size" : "29x29", 42 | "scale" : "2x" 43 | }, 44 | { 45 | "idiom" : "ipad", 46 | "size" : "40x40", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "40x40", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "size" : "76x76", 56 | "idiom" : "ipad", 57 | "filename" : "icon76.png", 58 | "scale" : "1x" 59 | }, 60 | { 61 | "size" : "76x76", 62 | "idiom" : "ipad", 63 | "filename" : "icon76@2x.png", 64 | "scale" : "2x" 65 | }, 66 | { 67 | "size" : "83.5x83.5", 68 | "idiom" : "ipad", 69 | "filename" : "icon83.5@2x.png", 70 | "scale" : "2x" 71 | } 72 | ], 73 | "info" : { 74 | "version" : 1, 75 | "author" : "xcode" 76 | } 77 | } -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/AppIcon.appiconset/icon60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/AppIcon.appiconset/icon60@2x.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/AppIcon.appiconset/icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/AppIcon.appiconset/icon76.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/AppIcon.appiconset/icon76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/AppIcon.appiconset/icon76@2x.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/AppIcon.appiconset/icon83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/AppIcon.appiconset/icon83.5@2x.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/close_button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "close_button@2x.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/close_button.imageset/close_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/close_button.imageset/close_button@2x.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "splash_4.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item1.imageset/splash_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/item1.imageset/splash_4.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "splash_3.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item2.imageset/splash_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/item2.imageset/splash_3.png -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "splash_2.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /RMParallax/Images.xcassets/item3.imageset/splash_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbabiy/RMParallax/2bedbf3f71ac2a7929857d69eb1fff087e2ee291/RMParallax/Images.xcassets/item3.imageset/splash_2.png -------------------------------------------------------------------------------- /RMParallax/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /RMParallax/RMParallax/RMController.swift: -------------------------------------------------------------------------------- 1 | // RMParallax 2 | // 3 | // Copyright (c) 2016 RMParallax 4 | // 5 | // Created by Raphael Miller & Michael Babiy 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 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 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | 27 | class RMController : UIViewController { 28 | var dismiss: DismissCompletion? 29 | 30 | private let items: [RMItem] 31 | private var scrollView = UIScrollView() 32 | private var dismissButton = UIButton() 33 | 34 | fileprivate let style: Style 35 | fileprivate var currentPageNumber = 0 36 | fileprivate var otherPageNumber = 0 37 | fileprivate var lastContentOffset: CGFloat = 0.0 38 | 39 | enum Style { 40 | case motion 41 | case subtle 42 | } 43 | 44 | /* 45 | Designated initializer. 46 | 47 | @param items - an array of items to page through. 48 | @param style - style to apply when paging. 49 | */ 50 | 51 | required init(with items: [RMItem], style: Style = .subtle) { 52 | self.items = items 53 | self.style = style 54 | super.init(nibName: nil, bundle: nil) 55 | } 56 | 57 | required init?(coder aDecoder: NSCoder) { 58 | fatalError("Use init with items, motion.") 59 | } 60 | 61 | // MARK: Life Cycle 62 | 63 | override func viewDidLoad() { 64 | super.viewDidLoad() 65 | setupScrollView() 66 | setupDismissButton() 67 | setupItems() 68 | } 69 | 70 | // MARK: Setup 71 | 72 | private func setupScrollView() { 73 | view.addSubview(scrollView) 74 | scrollView.delegate = self 75 | scrollView.bounces = false 76 | scrollView.showsHorizontalScrollIndicator = false 77 | scrollView.isPagingEnabled = true 78 | scrollView.translatesAutoresizingMaskIntoConstraints = false 79 | scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 80 | scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 81 | scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 82 | scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 83 | } 84 | 85 | private func setupDismissButton() { 86 | view.insertSubview(dismissButton, aboveSubview: scrollView) 87 | dismissButton.setImage(UIImage(named: "close_button"), for: .normal) 88 | dismissButton.addTarget(self, action: #selector(closeButtonSelected(_:)), for: .touchUpInside) 89 | dismissButton.translatesAutoresizingMaskIntoConstraints = false 90 | dismissButton.widthAnchor.constraint(equalToConstant: 23.0).isActive = true 91 | dismissButton.heightAnchor.constraint(equalToConstant: 23.0).isActive = true 92 | dismissButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 93 | dismissButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10.0).isActive = true 94 | } 95 | 96 | private func setupItems() { 97 | for (index, item) in items.enumerated() { 98 | let containerFrame = CGRect(x: (view.frame.width * CGFloat(index)), y: 0.0, width: view.frame.width, height: view.frame.height) 99 | let container = UIView(frame: containerFrame) 100 | let internalScrollView = makeScrollView(withTag: (index + 1) * 10) 101 | let internalTextScrollView = makeScrollView(withTag: (index + 1) * 100) 102 | let imageView = makeImageView(with: CGSize(width: internalScrollView.frame.width + style.motionFrameOffset, height: view.frame.height + style.motionFrameOffset), image: item.image) 103 | let textLabel = makeLabel(with: item.text) 104 | 105 | internalTextScrollView.addSubview(textLabel) 106 | internalScrollView.addSubview(imageView) 107 | internalScrollView.addSubview(internalTextScrollView) 108 | container.addSubview(internalScrollView) 109 | 110 | scrollView.addSubview(container) 111 | addMotionEffect(to: imageView) 112 | } 113 | scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(items.count), height: view.frame.height) 114 | } 115 | 116 | // MARK: Factory & Constraints 117 | 118 | private func makeImageView(with size: CGSize, image: UIImage) -> UIImageView { 119 | let imageViewFrame = CGRect(origin: .zero, size: size) 120 | let imageView = UIImageView(frame: imageViewFrame) 121 | imageView.contentMode = .scaleAspectFill 122 | imageView.image = image 123 | return imageView 124 | } 125 | 126 | private func makeLabel(with text: String) -> UILabel { 127 | let attributes = [NSFontAttributeName : style.font] 128 | let context = NSStringDrawingContext() 129 | let rect = text.boundingRect(with: CGSize(width: style.textSpan, height: .greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: attributes, context: context) 130 | let labelFrame = CGRect(x: 10.0, y: view.frame.height / 2.0, width: style.textSpan, height: rect.size.height) 131 | let label = UILabel(frame: labelFrame) 132 | label.numberOfLines = 0 133 | label.text = text 134 | label.textColor = style.textColor 135 | label.font = style.font 136 | return label 137 | } 138 | 139 | private func makeScrollView(withTag tag: Int) -> UIScrollView { 140 | let internalScrollView = UIScrollView(frame: CGRect(x: 0.0, y: 0.0, width: view.frame.width, height: view.frame.height)) 141 | internalScrollView.isScrollEnabled = false 142 | internalScrollView.tag = tag 143 | return internalScrollView 144 | } 145 | 146 | // MARK: Motion 147 | 148 | private func addMotionEffect(to view: UIView) -> Void { 149 | let xMotion = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis) 150 | let yMotion = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis) 151 | xMotion.minimumRelativeValue = (-style.motionMagnitude) 152 | xMotion.maximumRelativeValue = (style.motionMagnitude) 153 | yMotion.minimumRelativeValue = (-style.motionMagnitude) 154 | yMotion.maximumRelativeValue = (style.motionMagnitude) 155 | let motionGroup = UIMotionEffectGroup() 156 | motionGroup.motionEffects = [xMotion, yMotion] 157 | view.addMotionEffect(motionGroup) 158 | } 159 | 160 | // MARK: Action 161 | 162 | @objc private func closeButtonSelected(_ sender: UIButton) { 163 | dismiss?() 164 | } 165 | 166 | typealias DismissCompletion = () -> Void 167 | } 168 | 169 | extension RMController: UIScrollViewDelegate { 170 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 171 | if let internalScrollView = scrollView.viewWithTag((currentPageNumber + 1) * 10) as? UIScrollView { 172 | internalScrollView.contentOffset = .zero 173 | } 174 | 175 | if let otherScrollView = scrollView.viewWithTag((otherPageNumber + 1) * 10) as? UIScrollView { 176 | otherScrollView.contentOffset = .zero 177 | } 178 | 179 | if let internalTextScrollView = scrollView.viewWithTag((currentPageNumber + 1) * 100) as? UIScrollView { 180 | internalTextScrollView.contentOffset = .zero 181 | } 182 | 183 | if let otherTextScrollView = scrollView.viewWithTag((otherPageNumber + 1) * 100) as? UIScrollView { 184 | otherTextScrollView.contentOffset = .zero 185 | } 186 | currentPageNumber = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width) 187 | } 188 | 189 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 190 | var multiplier: CGFloat = 1.0 191 | let offset: CGFloat = scrollView.contentOffset.x 192 | if lastContentOffset > scrollView.contentOffset.x { 193 | if currentPageNumber > 0 { 194 | if offset > CGFloat(currentPageNumber - 1) * view.frame.width { 195 | otherPageNumber = currentPageNumber + 1 196 | multiplier = 1.0 197 | } else { 198 | otherPageNumber = currentPageNumber - 1 199 | multiplier = -1.0 200 | } 201 | } 202 | } else if lastContentOffset < scrollView.contentOffset.x { 203 | if offset < CGFloat(currentPageNumber - 1) * view.frame.width { 204 | otherPageNumber = currentPageNumber - 1 205 | multiplier = -1.0 206 | } else { 207 | otherPageNumber = currentPageNumber + 1 208 | multiplier = 1.0 209 | } 210 | } 211 | lastContentOffset = scrollView.contentOffset.x 212 | if let internalScrollView = scrollView.viewWithTag((currentPageNumber + 1) * 10) as? UIScrollView { 213 | internalScrollView.contentOffset = CGPoint(x: -style.viewOffsetMultiplier * (offset - (view.frame.width * CGFloat(currentPageNumber))), y: 0.0) 214 | } 215 | 216 | if let otherScrollView = scrollView.viewWithTag((otherPageNumber + 1) * 10) as? UIScrollView { 217 | let x: CGFloat = multiplier * style.viewOffsetMultiplier * view.frame.width - (style.viewOffsetMultiplier * (offset - (view.frame.width * CGFloat(currentPageNumber)))) 218 | otherScrollView.contentOffset = CGPoint(x: x, y: 0.0) 219 | } 220 | 221 | if let internalTextScrollView = scrollView.viewWithTag((currentPageNumber + 1) * 100) as? UIScrollView { 222 | internalTextScrollView.contentOffset = CGPoint(x: -style.textOffsetMultiplier * (offset - (view.frame.width * CGFloat(currentPageNumber))), y: 0.0) 223 | } 224 | 225 | if let otherTextScrollView = scrollView.viewWithTag((otherPageNumber + 1) * 100) as? UIScrollView { 226 | let x: CGFloat = multiplier * style.textOffsetMultiplier * view.frame.width - (style.textOffsetMultiplier * (offset - (view.frame.width * CGFloat(currentPageNumber)))) 227 | otherTextScrollView.contentOffset = CGPoint(x: x, y: 0.0) 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /RMParallax/RMParallax/RMItem.swift: -------------------------------------------------------------------------------- 1 | // RMParallax 2 | // 3 | // Copyright (c) 2016 RMParallax 4 | // 5 | // Created by Raphael Miller & Michael Babiy 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 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 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | 27 | struct RMItem { 28 | var image: UIImage 29 | var text: String 30 | } 31 | -------------------------------------------------------------------------------- /RMParallax/RMParallax/RMStyle.swift: -------------------------------------------------------------------------------- 1 | // RMParallax 2 | // 3 | // Copyright (c) 2016 RMParallax 4 | // 5 | // Created by Raphael Miller & Michael Babiy 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 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 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | 27 | extension RMController.Style { 28 | var font: UIFont { 29 | switch self { 30 | default: 31 | return .systemFont(ofSize: 25.0) 32 | } 33 | } 34 | 35 | var viewOffsetMultiplier: CGFloat { 36 | switch self { 37 | default: 38 | return 0.4 39 | } 40 | } 41 | 42 | var motionFrameOffset: CGFloat { 43 | switch self { 44 | default: 45 | return 0.0 46 | } 47 | } 48 | 49 | var motionMagnitude: CGFloat { 50 | switch self { 51 | case .motion: 52 | return 5.0 53 | default: 54 | return 0.0 55 | } 56 | } 57 | 58 | var textColor: UIColor { 59 | switch self { 60 | default: 61 | return .white 62 | } 63 | } 64 | 65 | var textOffsetMultiplier: CGFloat { 66 | switch self { 67 | default: 68 | return 0.8 69 | } 70 | } 71 | 72 | var textSpan: CGFloat { 73 | switch self { 74 | default: 75 | return 300.0 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /RMParallax/ViewController.swift: -------------------------------------------------------------------------------- 1 | // RMParallax 2 | // 3 | // Copyright (c) 2016 RMParallax 4 | // 5 | // Created by Raphael Miller & Michael Babiy 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 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 23 | // THE SOFTWARE. 24 | 25 | import UIKit 26 | 27 | class ViewController: UIViewController { 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | setupParallaxController() 31 | } 32 | 33 | func setupParallaxController() { 34 | let item1 = RMItem(image: UIImage(named: "item1")!, text: "SHARE LIGHTBOXES WITH YOUR TEAM") 35 | let item2 = RMItem(image: UIImage(named: "item2")!, text: "FOLLOW WORLD CLASS PHOTOGRAPHERS") 36 | let item3 = RMItem(image: UIImage(named: "item3")!, text: "EXPLORE OUR COLLECTION BY CATEGORY") 37 | let items = [item1, item2, item3] 38 | 39 | let introducing = RMController(with: items) 40 | introducing.dismiss = { 41 | introducing.view.removeFromSuperview() 42 | introducing.removeFromParentViewController() 43 | } 44 | 45 | // Adding parallax view controller. 46 | addChildViewController(introducing) 47 | view.addSubview(introducing.view) 48 | introducing.didMove(toParentViewController: self) 49 | } 50 | 51 | override var prefersStatusBarHidden: Bool { 52 | return true 53 | } 54 | } 55 | 56 | --------------------------------------------------------------------------------