├── .gitignore ├── Motion Shadows Demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── nachbaur.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Motion Shadows Demo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AddButton.imageset │ │ ├── AddButton@2x.png │ │ ├── AddButton@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CustomButton.swift ├── FloatingViewTableViewController.swift ├── Info.plist ├── ShadowPathViewController.swift ├── TransformViewController.swift ├── UIView+InterpolatingMotion.swift └── ViewController.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | DerivedData/ 3 | .DS_Store 4 | .*.sw? 5 | .sw? 6 | *.xcscmblueprint 7 | -------------------------------------------------------------------------------- /Motion Shadows Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1B3FDFF6226E2A0300BD8D2A /* UIView+InterpolatingMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B3FDFF5226E2A0300BD8D2A /* UIView+InterpolatingMotion.swift */; }; 11 | 1B446FF5226E766F00AE9FE4 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B446FF4226E766F00AE9FE4 /* CustomButton.swift */; }; 12 | 1B446FF7226F783000AE9FE4 /* FloatingViewTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B446FF6226F783000AE9FE4 /* FloatingViewTableViewController.swift */; }; 13 | 1B446FF9226FCE6300AE9FE4 /* ShadowPathViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B446FF8226FCE6200AE9FE4 /* ShadowPathViewController.swift */; }; 14 | 1B446FFB226FD43C00AE9FE4 /* TransformViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B446FFA226FD43C00AE9FE4 /* TransformViewController.swift */; }; 15 | 1B61BD84225D3D4000070D4B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B61BD83225D3D4000070D4B /* AppDelegate.swift */; }; 16 | 1B61BD86225D3D4000070D4B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B61BD85225D3D4000070D4B /* ViewController.swift */; }; 17 | 1B61BD89225D3D4000070D4B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1B61BD87225D3D4000070D4B /* Main.storyboard */; }; 18 | 1B61BD8B225D3D4200070D4B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1B61BD8A225D3D4200070D4B /* Assets.xcassets */; }; 19 | 1B61BD8E225D3D4200070D4B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1B61BD8C225D3D4200070D4B /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 1B3FDFF5226E2A0300BD8D2A /* UIView+InterpolatingMotion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+InterpolatingMotion.swift"; sourceTree = ""; }; 24 | 1B446FF4226E766F00AE9FE4 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; 25 | 1B446FF6226F783000AE9FE4 /* FloatingViewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingViewTableViewController.swift; sourceTree = ""; }; 26 | 1B446FF8226FCE6200AE9FE4 /* ShadowPathViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowPathViewController.swift; sourceTree = ""; }; 27 | 1B446FFA226FD43C00AE9FE4 /* TransformViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransformViewController.swift; sourceTree = ""; }; 28 | 1B61BD80225D3D4000070D4B /* Motion Shadows Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Motion Shadows Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 1B61BD83225D3D4000070D4B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 30 | 1B61BD85225D3D4000070D4B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 31 | 1B61BD88225D3D4000070D4B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32 | 1B61BD8A225D3D4200070D4B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | 1B61BD8D225D3D4200070D4B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | 1B61BD8F225D3D4200070D4B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 1B61BD7D225D3D4000070D4B /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 1B61BD77225D3D4000070D4B = { 49 | isa = PBXGroup; 50 | children = ( 51 | 1B61BD82225D3D4000070D4B /* Motion Shadows Demo */, 52 | 1B61BD81225D3D4000070D4B /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 1B61BD81225D3D4000070D4B /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 1B61BD80225D3D4000070D4B /* Motion Shadows Demo.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 1B61BD82225D3D4000070D4B /* Motion Shadows Demo */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 1B61BD83225D3D4000070D4B /* AppDelegate.swift */, 68 | 1B61BD85225D3D4000070D4B /* ViewController.swift */, 69 | 1B61BD87225D3D4000070D4B /* Main.storyboard */, 70 | 1B61BD8A225D3D4200070D4B /* Assets.xcassets */, 71 | 1B61BD8C225D3D4200070D4B /* LaunchScreen.storyboard */, 72 | 1B61BD8F225D3D4200070D4B /* Info.plist */, 73 | 1B3FDFF5226E2A0300BD8D2A /* UIView+InterpolatingMotion.swift */, 74 | 1B446FF4226E766F00AE9FE4 /* CustomButton.swift */, 75 | 1B446FF6226F783000AE9FE4 /* FloatingViewTableViewController.swift */, 76 | 1B446FF8226FCE6200AE9FE4 /* ShadowPathViewController.swift */, 77 | 1B446FFA226FD43C00AE9FE4 /* TransformViewController.swift */, 78 | ); 79 | path = "Motion Shadows Demo"; 80 | sourceTree = ""; 81 | }; 82 | /* End PBXGroup section */ 83 | 84 | /* Begin PBXNativeTarget section */ 85 | 1B61BD7F225D3D4000070D4B /* Motion Shadows Demo */ = { 86 | isa = PBXNativeTarget; 87 | buildConfigurationList = 1B61BD92225D3D4200070D4B /* Build configuration list for PBXNativeTarget "Motion Shadows Demo" */; 88 | buildPhases = ( 89 | 1B61BD7C225D3D4000070D4B /* Sources */, 90 | 1B61BD7D225D3D4000070D4B /* Frameworks */, 91 | 1B61BD7E225D3D4000070D4B /* Resources */, 92 | ); 93 | buildRules = ( 94 | ); 95 | dependencies = ( 96 | ); 97 | name = "Motion Shadows Demo"; 98 | productName = "Motion Shadows Demo"; 99 | productReference = 1B61BD80225D3D4000070D4B /* Motion Shadows Demo.app */; 100 | productType = "com.apple.product-type.application"; 101 | }; 102 | /* End PBXNativeTarget section */ 103 | 104 | /* Begin PBXProject section */ 105 | 1B61BD78225D3D4000070D4B /* Project object */ = { 106 | isa = PBXProject; 107 | attributes = { 108 | LastSwiftUpdateCheck = 1020; 109 | LastUpgradeCheck = 1020; 110 | ORGANIZATIONNAME = "Michael Nachbaur"; 111 | TargetAttributes = { 112 | 1B61BD7F225D3D4000070D4B = { 113 | CreatedOnToolsVersion = 10.2; 114 | }; 115 | }; 116 | }; 117 | buildConfigurationList = 1B61BD7B225D3D4000070D4B /* Build configuration list for PBXProject "Motion Shadows Demo" */; 118 | compatibilityVersion = "Xcode 9.3"; 119 | developmentRegion = en; 120 | hasScannedForEncodings = 0; 121 | knownRegions = ( 122 | en, 123 | Base, 124 | ); 125 | mainGroup = 1B61BD77225D3D4000070D4B; 126 | productRefGroup = 1B61BD81225D3D4000070D4B /* Products */; 127 | projectDirPath = ""; 128 | projectRoot = ""; 129 | targets = ( 130 | 1B61BD7F225D3D4000070D4B /* Motion Shadows Demo */, 131 | ); 132 | }; 133 | /* End PBXProject section */ 134 | 135 | /* Begin PBXResourcesBuildPhase section */ 136 | 1B61BD7E225D3D4000070D4B /* Resources */ = { 137 | isa = PBXResourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | 1B61BD8E225D3D4200070D4B /* LaunchScreen.storyboard in Resources */, 141 | 1B61BD8B225D3D4200070D4B /* Assets.xcassets in Resources */, 142 | 1B61BD89225D3D4000070D4B /* Main.storyboard in Resources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXResourcesBuildPhase section */ 147 | 148 | /* Begin PBXSourcesBuildPhase section */ 149 | 1B61BD7C225D3D4000070D4B /* Sources */ = { 150 | isa = PBXSourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | 1B446FFB226FD43C00AE9FE4 /* TransformViewController.swift in Sources */, 154 | 1B446FF5226E766F00AE9FE4 /* CustomButton.swift in Sources */, 155 | 1B61BD86225D3D4000070D4B /* ViewController.swift in Sources */, 156 | 1B446FF7226F783000AE9FE4 /* FloatingViewTableViewController.swift in Sources */, 157 | 1B3FDFF6226E2A0300BD8D2A /* UIView+InterpolatingMotion.swift in Sources */, 158 | 1B61BD84225D3D4000070D4B /* AppDelegate.swift in Sources */, 159 | 1B446FF9226FCE6300AE9FE4 /* ShadowPathViewController.swift in Sources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXSourcesBuildPhase section */ 164 | 165 | /* Begin PBXVariantGroup section */ 166 | 1B61BD87225D3D4000070D4B /* Main.storyboard */ = { 167 | isa = PBXVariantGroup; 168 | children = ( 169 | 1B61BD88225D3D4000070D4B /* Base */, 170 | ); 171 | name = Main.storyboard; 172 | sourceTree = ""; 173 | }; 174 | 1B61BD8C225D3D4200070D4B /* LaunchScreen.storyboard */ = { 175 | isa = PBXVariantGroup; 176 | children = ( 177 | 1B61BD8D225D3D4200070D4B /* Base */, 178 | ); 179 | name = LaunchScreen.storyboard; 180 | sourceTree = ""; 181 | }; 182 | /* End PBXVariantGroup section */ 183 | 184 | /* Begin XCBuildConfiguration section */ 185 | 1B61BD90225D3D4200070D4B /* Debug */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_ANALYZER_NONNULL = YES; 190 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 191 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 192 | CLANG_CXX_LIBRARY = "libc++"; 193 | CLANG_ENABLE_MODULES = YES; 194 | CLANG_ENABLE_OBJC_ARC = YES; 195 | CLANG_ENABLE_OBJC_WEAK = YES; 196 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 197 | CLANG_WARN_BOOL_CONVERSION = YES; 198 | CLANG_WARN_COMMA = YES; 199 | CLANG_WARN_CONSTANT_CONVERSION = YES; 200 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_INFINITE_RECURSION = YES; 206 | CLANG_WARN_INT_CONVERSION = YES; 207 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 208 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 209 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 210 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 211 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 212 | CLANG_WARN_STRICT_PROTOTYPES = YES; 213 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 214 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 215 | CLANG_WARN_UNREACHABLE_CODE = YES; 216 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 217 | CODE_SIGN_IDENTITY = "iPhone Developer"; 218 | COPY_PHASE_STRIP = NO; 219 | DEBUG_INFORMATION_FORMAT = dwarf; 220 | ENABLE_STRICT_OBJC_MSGSEND = YES; 221 | ENABLE_TESTABILITY = YES; 222 | GCC_C_LANGUAGE_STANDARD = gnu11; 223 | GCC_DYNAMIC_NO_PIC = NO; 224 | GCC_NO_COMMON_BLOCKS = YES; 225 | GCC_OPTIMIZATION_LEVEL = 0; 226 | GCC_PREPROCESSOR_DEFINITIONS = ( 227 | "DEBUG=1", 228 | "$(inherited)", 229 | ); 230 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 231 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 232 | GCC_WARN_UNDECLARED_SELECTOR = YES; 233 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 234 | GCC_WARN_UNUSED_FUNCTION = YES; 235 | GCC_WARN_UNUSED_VARIABLE = YES; 236 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 237 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 238 | MTL_FAST_MATH = YES; 239 | ONLY_ACTIVE_ARCH = YES; 240 | SDKROOT = iphoneos; 241 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 242 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 243 | }; 244 | name = Debug; 245 | }; 246 | 1B61BD91225D3D4200070D4B /* Release */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | ALWAYS_SEARCH_USER_PATHS = NO; 250 | CLANG_ANALYZER_NONNULL = YES; 251 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_ENABLE_OBJC_WEAK = YES; 257 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_COMMA = YES; 260 | CLANG_WARN_CONSTANT_CONVERSION = YES; 261 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 264 | CLANG_WARN_EMPTY_BODY = YES; 265 | CLANG_WARN_ENUM_CONVERSION = YES; 266 | CLANG_WARN_INFINITE_RECURSION = YES; 267 | CLANG_WARN_INT_CONVERSION = YES; 268 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 269 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 270 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 271 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 272 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 273 | CLANG_WARN_STRICT_PROTOTYPES = YES; 274 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 275 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | CODE_SIGN_IDENTITY = "iPhone Developer"; 279 | COPY_PHASE_STRIP = NO; 280 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 281 | ENABLE_NS_ASSERTIONS = NO; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | GCC_C_LANGUAGE_STANDARD = gnu11; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 292 | MTL_ENABLE_DEBUG_INFO = NO; 293 | MTL_FAST_MATH = YES; 294 | SDKROOT = iphoneos; 295 | SWIFT_COMPILATION_MODE = wholemodule; 296 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 297 | VALIDATE_PRODUCT = YES; 298 | }; 299 | name = Release; 300 | }; 301 | 1B61BD93225D3D4200070D4B /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | CODE_SIGN_STYLE = Automatic; 306 | DEVELOPMENT_TEAM = 4U57NCZEKP; 307 | INFOPLIST_FILE = "Motion Shadows Demo/Info.plist"; 308 | LD_RUNPATH_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | "@executable_path/Frameworks", 311 | ); 312 | PRODUCT_BUNDLE_IDENTIFIER = "com.nachbaur.demo.Motion-Shadows-Demo"; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | SWIFT_VERSION = 5.0; 315 | TARGETED_DEVICE_FAMILY = "1,2"; 316 | }; 317 | name = Debug; 318 | }; 319 | 1B61BD94225D3D4200070D4B /* Release */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 323 | CODE_SIGN_STYLE = Automatic; 324 | DEVELOPMENT_TEAM = 4U57NCZEKP; 325 | INFOPLIST_FILE = "Motion Shadows Demo/Info.plist"; 326 | LD_RUNPATH_SEARCH_PATHS = ( 327 | "$(inherited)", 328 | "@executable_path/Frameworks", 329 | ); 330 | PRODUCT_BUNDLE_IDENTIFIER = "com.nachbaur.demo.Motion-Shadows-Demo"; 331 | PRODUCT_NAME = "$(TARGET_NAME)"; 332 | SWIFT_VERSION = 5.0; 333 | TARGETED_DEVICE_FAMILY = "1,2"; 334 | }; 335 | name = Release; 336 | }; 337 | /* End XCBuildConfiguration section */ 338 | 339 | /* Begin XCConfigurationList section */ 340 | 1B61BD7B225D3D4000070D4B /* Build configuration list for PBXProject "Motion Shadows Demo" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 1B61BD90225D3D4200070D4B /* Debug */, 344 | 1B61BD91225D3D4200070D4B /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | 1B61BD92225D3D4200070D4B /* Build configuration list for PBXNativeTarget "Motion Shadows Demo" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 1B61BD93225D3D4200070D4B /* Debug */, 353 | 1B61BD94225D3D4200070D4B /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | /* End XCConfigurationList section */ 359 | }; 360 | rootObject = 1B61BD78225D3D4000070D4B /* Project object */; 361 | } 362 | -------------------------------------------------------------------------------- /Motion Shadows Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Motion Shadows Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Motion Shadows Demo.xcodeproj/xcuserdata/nachbaur.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Motion Shadows Demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Motion Shadows Demo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-09. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | CustomButton.appearance().shadowMotionOffset = CGSize(width: -16, height: -16) 18 | CustomButton.appearance().shadowColor = UIColor.black 19 | CustomButton.appearance().shadowOpacity = 0.75 20 | CustomButton.appearance().shadowRadius = 3 21 | CustomButton.appearance().cornerRadius = 12 22 | CustomButton.appearance().borderWidth = 2 23 | CustomButton.appearance().borderColor = UIColor(red: 0, green: 0.692, blue: 0.549, alpha: 0.82) 24 | CustomButton.appearance().backgroundColor = UIColor(red: 0, green: 0.478, blue: 1, alpha: 1) 25 | CustomButton.appearance().tintColor = UIColor.white 26 | return true 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Motion Shadows Demo/Assets.xcassets/AddButton.imageset/AddButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexNachbaur/Example-UIMotionEffect/a6301f0803bcbf517914fd69ba832550ca9455d3/Motion Shadows Demo/Assets.xcassets/AddButton.imageset/AddButton@2x.png -------------------------------------------------------------------------------- /Motion Shadows Demo/Assets.xcassets/AddButton.imageset/AddButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexNachbaur/Example-UIMotionEffect/a6301f0803bcbf517914fd69ba832550ca9455d3/Motion Shadows Demo/Assets.xcassets/AddButton.imageset/AddButton@3x.png -------------------------------------------------------------------------------- /Motion Shadows Demo/Assets.xcassets/AddButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "AddButton@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "AddButton@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | }, 22 | "properties" : { 23 | "template-rendering-intent" : "template" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Motion Shadows Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Motion Shadows Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Motion Shadows Demo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Motion Shadows Demo/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 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 164 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 249 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 320 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 425 | 426 | 427 | 428 | 434 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 472 | 476 | 480 | 484 | 488 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /Motion Shadows Demo/CustomButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomButton.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-22. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CustomButton: UIButton { 12 | 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | 16 | contentEdgeInsets = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Motion Shadows Demo/FloatingViewTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FloatingViewTableViewController.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-23. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FloatingViewTableViewController: UIViewController, UITableViewDataSource { 12 | 13 | func numberOfSections(in tableView: UITableView) -> Int { 14 | return 1 15 | } 16 | 17 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 18 | return 100 19 | } 20 | 21 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 22 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 23 | 24 | cell.textLabel?.text = "Cell \(indexPath.row)"; 25 | cell.detailTextLabel?.text = "Detail for \(indexPath.row)"; 26 | 27 | return cell 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Motion Shadows Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationLandscapeRight 34 | UIInterfaceOrientationPortrait 35 | UIInterfaceOrientationLandscapeLeft 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Motion Shadows Demo/ShadowPathViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowPathViewController.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-23. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ShadowPathViewController: UIViewController { 12 | 13 | @IBOutlet var contentView: UIView! 14 | 15 | override func viewDidLayoutSubviews() { 16 | super.viewDidLayoutSubviews() 17 | 18 | updateMotionEffects() 19 | } 20 | 21 | func updateMotionEffects() { 22 | for effect in contentView.motionEffects { 23 | contentView.removeMotionEffect(effect) 24 | } 25 | 26 | let size = contentView.bounds.size 27 | let minimumPath = UIBezierPath() 28 | minimumPath.move(to: CGPoint(x: size.width * 0.33, 29 | y: size.height * 0.66)) 30 | minimumPath.addLine(to: CGPoint(x: size.width * 0.66, 31 | y: size.height * 0.66)) 32 | minimumPath.addLine(to: CGPoint(x: size.width * 1.15, 33 | y: size.height * 1.15)) 34 | minimumPath.addLine(to: CGPoint(x: size.width * -0.15, 35 | y: size.height * 1.15)) 36 | minimumPath.close() 37 | 38 | let maximumPath = UIBezierPath(rect: CGRect(origin: .zero, size: size)) 39 | 40 | let effect = UIInterpolatingMotionEffect( 41 | keyPath: "layer.shadowPath", 42 | type: .tiltAlongVerticalAxis) 43 | effect.minimumRelativeValue = minimumPath.cgPath 44 | effect.maximumRelativeValue = maximumPath.cgPath 45 | 46 | contentView.addMotionEffect(effect) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Motion Shadows Demo/TransformViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransformViewController.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-23. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TransformViewController: UIViewController { 12 | 13 | @IBOutlet var contentView: UIView! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | var identity = CATransform3DIdentity 19 | identity.m34 = -1 / 500.0 20 | 21 | let minimumTransform = CATransform3DRotate(identity, (315 * .pi) / 180.0, 1.0, 0.0, 0.0) 22 | let maximumTransform = CATransform3DRotate(identity, (45 * .pi) / 180.0, 1.0, 0.0, 0.0) 23 | 24 | contentView.layer.transform = identity 25 | let effect = UIInterpolatingMotionEffect( 26 | keyPath: "layer.transform", 27 | type: .tiltAlongVerticalAxis) 28 | effect.minimumRelativeValue = minimumTransform 29 | effect.maximumRelativeValue = maximumTransform 30 | 31 | contentView.addMotionEffect(effect) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Motion Shadows Demo/UIView+InterpolatingMotion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+InterpolatingMotion.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-22. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ObjectiveC 11 | 12 | @IBDesignable public extension UIView { 13 | private struct AssociatedKeys { 14 | static var shadowMotionOffset: UInt8 = 0 15 | static var motionOffset: UInt8 = 0 16 | } 17 | 18 | @objc @IBInspectable var cornerRadius : CGFloat { 19 | get { 20 | return layer.cornerRadius 21 | } 22 | set(radius) { 23 | layer.cornerRadius = radius 24 | } 25 | } 26 | 27 | @objc @IBInspectable var borderWidth : CGFloat { 28 | get { 29 | return layer.borderWidth 30 | } 31 | set(width) { 32 | layer.borderWidth = width 33 | } 34 | } 35 | 36 | @objc @IBInspectable var borderColor : UIColor? { 37 | get { 38 | return layer.borderColor as? UIColor? ?? nil 39 | } 40 | set(color) { 41 | layer.borderColor = color?.cgColor 42 | } 43 | } 44 | 45 | @objc @IBInspectable var shadowColor : UIColor? { 46 | get { 47 | return layer.shadowColor as? UIColor? ?? nil 48 | } 49 | set(color) { 50 | layer.shadowColor = color?.cgColor 51 | } 52 | } 53 | 54 | @objc @IBInspectable var shadowRadius : CGFloat { 55 | get { 56 | return layer.shadowRadius 57 | } 58 | set(radius) { 59 | layer.shadowRadius = radius 60 | } 61 | } 62 | 63 | @objc @IBInspectable var shadowOpacity : Float { 64 | get { 65 | return layer.shadowOpacity 66 | } 67 | set(opacity) { 68 | layer.shadowOpacity = opacity 69 | } 70 | } 71 | 72 | @objc @IBInspectable var shadowOffset : CGSize { 73 | get { 74 | return layer.shadowOffset 75 | } 76 | set(offset) { 77 | layer.shadowOffset = offset 78 | } 79 | } 80 | 81 | @objc @IBInspectable var shadowMotionOffset : CGSize { 82 | get { 83 | guard let value = objc_getAssociatedObject(self, &AssociatedKeys.shadowMotionOffset) as? NSValue else { 84 | return .zero 85 | } 86 | 87 | return value.cgSizeValue 88 | } 89 | set(newSize) { 90 | var currentSize = CGSize.zero 91 | let currentValue = objc_getAssociatedObject(self, &AssociatedKeys.shadowMotionOffset) as? NSValue 92 | if currentValue != nil { 93 | currentSize = currentValue!.cgSizeValue 94 | } 95 | 96 | if currentSize.equalTo(newSize) { 97 | return 98 | } 99 | 100 | objc_setAssociatedObject(self, &AssociatedKeys.shadowMotionOffset, NSValue(cgSize: newSize), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 101 | 102 | addInterpolatingMotionEffect(keyPath: "layer.shadowOffset.width", effectType: .tiltAlongHorizontalAxis, range: newSize.width) 103 | addInterpolatingMotionEffect(keyPath: "layer.shadowOffset.height", effectType: .tiltAlongVerticalAxis, range: newSize.height) 104 | } 105 | } 106 | 107 | @objc @IBInspectable var motionOffset : CGSize { 108 | get { 109 | guard let value = objc_getAssociatedObject(self, &AssociatedKeys.motionOffset) as? NSValue else { 110 | return .zero 111 | } 112 | 113 | return value.cgSizeValue 114 | } 115 | set(newSize) { 116 | var currentSize = CGSize.zero 117 | let currentValue = objc_getAssociatedObject(self, &AssociatedKeys.motionOffset) as? NSValue 118 | if currentValue != nil { 119 | currentSize = currentValue!.cgSizeValue 120 | } 121 | 122 | if currentSize.equalTo(newSize) { 123 | return 124 | } 125 | 126 | objc_setAssociatedObject(self, &AssociatedKeys.motionOffset, NSValue(cgSize: newSize), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 127 | 128 | addInterpolatingMotionEffect(keyPath: "center.x", effectType: .tiltAlongHorizontalAxis, range: newSize.width) 129 | addInterpolatingMotionEffect(keyPath: "center.y", effectType: .tiltAlongVerticalAxis, range: newSize.height) 130 | } 131 | } 132 | 133 | private func addInterpolatingMotionEffect(keyPath : String, effectType : UIInterpolatingMotionEffect.EffectType, range value : CGFloat) { 134 | for effect in motionEffects { 135 | if let interpolatingEffect = effect as? UIInterpolatingMotionEffect { 136 | if interpolatingEffect.keyPath == keyPath { 137 | removeMotionEffect(interpolatingEffect) 138 | break 139 | } 140 | } 141 | } 142 | 143 | if value == 0 { 144 | return 145 | } 146 | 147 | let effect = UIInterpolatingMotionEffect(keyPath: keyPath, type: effectType) 148 | if value < 0 { 149 | effect.minimumRelativeValue = -(abs(value)); 150 | effect.maximumRelativeValue = abs(value); 151 | } else { 152 | effect.minimumRelativeValue = abs(value); 153 | effect.maximumRelativeValue = -(abs(value)); 154 | } 155 | addMotionEffect(effect) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Motion Shadows Demo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Motion Shadows Demo 4 | // 5 | // Created by Michael Nachbaur on 2019-04-09. 6 | // Copyright © 2019 Michael Nachbaur. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | } 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UIMotionEffect demo 2 | 3 | This is an example project that demonstrates the use of UIMotionEffect within an application, and providing a UIView extension in Swift that makes it easy to configure these effects using 4 | UIAppearance or Interface Builder. 5 | 6 | ## Interface Builder 7 | 8 | When configuring a view in Interface Builder, utilize the motionOffset or shadowMotionOffset properties. When using negative values for width or height, it will automatically invert the 9 | device's gyro response accordingly. This also includes extra UIAppearance and IBInspectable properties for controlling borders and corner radii to make the demo more compelling. 10 | --------------------------------------------------------------------------------