├── AdvancedTouch.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── simongladman.xcuserdatad │ └── xcschemes │ ├── AdvancedTouch.xcscheme │ └── xcschememanagement.plist ├── AdvancedTouch ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift └── assets │ └── screenshot.jpg ├── AdvancedTouchTests ├── AdvancedTouchTests.swift └── Info.plist ├── AdvancedTouchUITests ├── AdvancedTouchUITests.swift └── Info.plist └── README.md /AdvancedTouch.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3E33423D1BAC8AF20065C0D7 /* screenshot.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 3E33423C1BAC8AF20065C0D7 /* screenshot.jpg */; settings = {ASSET_TAGS = (); }; }; 11 | 3EECB12B1BAA923000F90C32 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EECB12A1BAA923000F90C32 /* AppDelegate.swift */; }; 12 | 3EECB12D1BAA923000F90C32 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EECB12C1BAA923000F90C32 /* ViewController.swift */; }; 13 | 3EECB1301BAA923000F90C32 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3EECB12E1BAA923000F90C32 /* Main.storyboard */; }; 14 | 3EECB1321BAA923000F90C32 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3EECB1311BAA923000F90C32 /* Assets.xcassets */; }; 15 | 3EECB1351BAA923000F90C32 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3EECB1331BAA923000F90C32 /* LaunchScreen.storyboard */; }; 16 | 3EECB1401BAA923000F90C32 /* AdvancedTouchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EECB13F1BAA923000F90C32 /* AdvancedTouchTests.swift */; }; 17 | 3EECB14B1BAA923000F90C32 /* AdvancedTouchUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EECB14A1BAA923000F90C32 /* AdvancedTouchUITests.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 3EECB13C1BAA923000F90C32 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 3EECB11F1BAA923000F90C32 /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 3EECB1261BAA923000F90C32; 26 | remoteInfo = AdvancedTouch; 27 | }; 28 | 3EECB1471BAA923000F90C32 /* PBXContainerItemProxy */ = { 29 | isa = PBXContainerItemProxy; 30 | containerPortal = 3EECB11F1BAA923000F90C32 /* Project object */; 31 | proxyType = 1; 32 | remoteGlobalIDString = 3EECB1261BAA923000F90C32; 33 | remoteInfo = AdvancedTouch; 34 | }; 35 | /* End PBXContainerItemProxy section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 3E33423C1BAC8AF20065C0D7 /* screenshot.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = screenshot.jpg; path = assets/screenshot.jpg; sourceTree = ""; }; 39 | 3EECB1271BAA923000F90C32 /* AdvancedTouch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AdvancedTouch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 3EECB12A1BAA923000F90C32 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 3EECB12C1BAA923000F90C32 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | 3EECB12F1BAA923000F90C32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 3EECB1311BAA923000F90C32 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 3EECB1341BAA923000F90C32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 45 | 3EECB1361BAA923000F90C32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 3EECB13B1BAA923000F90C32 /* AdvancedTouchTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdvancedTouchTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 3EECB13F1BAA923000F90C32 /* AdvancedTouchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedTouchTests.swift; sourceTree = ""; }; 48 | 3EECB1411BAA923000F90C32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 3EECB1461BAA923000F90C32 /* AdvancedTouchUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdvancedTouchUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 3EECB14A1BAA923000F90C32 /* AdvancedTouchUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedTouchUITests.swift; sourceTree = ""; }; 51 | 3EECB14C1BAA923000F90C32 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 3EECB1241BAA923000F90C32 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | 3EECB1381BAA923000F90C32 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 3EECB1431BAA923000F90C32 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 3E33423E1BAC8AFC0065C0D7 /* assets */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3E33423C1BAC8AF20065C0D7 /* screenshot.jpg */, 83 | ); 84 | name = assets; 85 | sourceTree = ""; 86 | }; 87 | 3EECB11E1BAA923000F90C32 = { 88 | isa = PBXGroup; 89 | children = ( 90 | 3EECB1291BAA923000F90C32 /* AdvancedTouch */, 91 | 3EECB13E1BAA923000F90C32 /* AdvancedTouchTests */, 92 | 3EECB1491BAA923000F90C32 /* AdvancedTouchUITests */, 93 | 3EECB1281BAA923000F90C32 /* Products */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 3EECB1281BAA923000F90C32 /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 3EECB1271BAA923000F90C32 /* AdvancedTouch.app */, 101 | 3EECB13B1BAA923000F90C32 /* AdvancedTouchTests.xctest */, 102 | 3EECB1461BAA923000F90C32 /* AdvancedTouchUITests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 3EECB1291BAA923000F90C32 /* AdvancedTouch */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 3E33423E1BAC8AFC0065C0D7 /* assets */, 111 | 3EECB12A1BAA923000F90C32 /* AppDelegate.swift */, 112 | 3EECB12C1BAA923000F90C32 /* ViewController.swift */, 113 | 3EECB12E1BAA923000F90C32 /* Main.storyboard */, 114 | 3EECB1311BAA923000F90C32 /* Assets.xcassets */, 115 | 3EECB1331BAA923000F90C32 /* LaunchScreen.storyboard */, 116 | 3EECB1361BAA923000F90C32 /* Info.plist */, 117 | ); 118 | path = AdvancedTouch; 119 | sourceTree = ""; 120 | }; 121 | 3EECB13E1BAA923000F90C32 /* AdvancedTouchTests */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 3EECB13F1BAA923000F90C32 /* AdvancedTouchTests.swift */, 125 | 3EECB1411BAA923000F90C32 /* Info.plist */, 126 | ); 127 | path = AdvancedTouchTests; 128 | sourceTree = ""; 129 | }; 130 | 3EECB1491BAA923000F90C32 /* AdvancedTouchUITests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 3EECB14A1BAA923000F90C32 /* AdvancedTouchUITests.swift */, 134 | 3EECB14C1BAA923000F90C32 /* Info.plist */, 135 | ); 136 | path = AdvancedTouchUITests; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXNativeTarget section */ 142 | 3EECB1261BAA923000F90C32 /* AdvancedTouch */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 3EECB14F1BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouch" */; 145 | buildPhases = ( 146 | 3EECB1231BAA923000F90C32 /* Sources */, 147 | 3EECB1241BAA923000F90C32 /* Frameworks */, 148 | 3EECB1251BAA923000F90C32 /* Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | ); 154 | name = AdvancedTouch; 155 | productName = AdvancedTouch; 156 | productReference = 3EECB1271BAA923000F90C32 /* AdvancedTouch.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | 3EECB13A1BAA923000F90C32 /* AdvancedTouchTests */ = { 160 | isa = PBXNativeTarget; 161 | buildConfigurationList = 3EECB1521BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouchTests" */; 162 | buildPhases = ( 163 | 3EECB1371BAA923000F90C32 /* Sources */, 164 | 3EECB1381BAA923000F90C32 /* Frameworks */, 165 | 3EECB1391BAA923000F90C32 /* Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | 3EECB13D1BAA923000F90C32 /* PBXTargetDependency */, 171 | ); 172 | name = AdvancedTouchTests; 173 | productName = AdvancedTouchTests; 174 | productReference = 3EECB13B1BAA923000F90C32 /* AdvancedTouchTests.xctest */; 175 | productType = "com.apple.product-type.bundle.unit-test"; 176 | }; 177 | 3EECB1451BAA923000F90C32 /* AdvancedTouchUITests */ = { 178 | isa = PBXNativeTarget; 179 | buildConfigurationList = 3EECB1551BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouchUITests" */; 180 | buildPhases = ( 181 | 3EECB1421BAA923000F90C32 /* Sources */, 182 | 3EECB1431BAA923000F90C32 /* Frameworks */, 183 | 3EECB1441BAA923000F90C32 /* Resources */, 184 | ); 185 | buildRules = ( 186 | ); 187 | dependencies = ( 188 | 3EECB1481BAA923000F90C32 /* PBXTargetDependency */, 189 | ); 190 | name = AdvancedTouchUITests; 191 | productName = AdvancedTouchUITests; 192 | productReference = 3EECB1461BAA923000F90C32 /* AdvancedTouchUITests.xctest */; 193 | productType = "com.apple.product-type.bundle.ui-testing"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 3EECB11F1BAA923000F90C32 /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastUpgradeCheck = 0700; 202 | ORGANIZATIONNAME = "Simon Gladman"; 203 | TargetAttributes = { 204 | 3EECB1261BAA923000F90C32 = { 205 | CreatedOnToolsVersion = 7.0; 206 | }; 207 | 3EECB13A1BAA923000F90C32 = { 208 | CreatedOnToolsVersion = 7.0; 209 | TestTargetID = 3EECB1261BAA923000F90C32; 210 | }; 211 | 3EECB1451BAA923000F90C32 = { 212 | CreatedOnToolsVersion = 7.0; 213 | TestTargetID = 3EECB1261BAA923000F90C32; 214 | }; 215 | }; 216 | }; 217 | buildConfigurationList = 3EECB1221BAA923000F90C32 /* Build configuration list for PBXProject "AdvancedTouch" */; 218 | compatibilityVersion = "Xcode 3.2"; 219 | developmentRegion = English; 220 | hasScannedForEncodings = 0; 221 | knownRegions = ( 222 | en, 223 | Base, 224 | ); 225 | mainGroup = 3EECB11E1BAA923000F90C32; 226 | productRefGroup = 3EECB1281BAA923000F90C32 /* Products */; 227 | projectDirPath = ""; 228 | projectRoot = ""; 229 | targets = ( 230 | 3EECB1261BAA923000F90C32 /* AdvancedTouch */, 231 | 3EECB13A1BAA923000F90C32 /* AdvancedTouchTests */, 232 | 3EECB1451BAA923000F90C32 /* AdvancedTouchUITests */, 233 | ); 234 | }; 235 | /* End PBXProject section */ 236 | 237 | /* Begin PBXResourcesBuildPhase section */ 238 | 3EECB1251BAA923000F90C32 /* Resources */ = { 239 | isa = PBXResourcesBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | 3EECB1351BAA923000F90C32 /* LaunchScreen.storyboard in Resources */, 243 | 3EECB1321BAA923000F90C32 /* Assets.xcassets in Resources */, 244 | 3EECB1301BAA923000F90C32 /* Main.storyboard in Resources */, 245 | 3E33423D1BAC8AF20065C0D7 /* screenshot.jpg in Resources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | 3EECB1391BAA923000F90C32 /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | 3EECB1441BAA923000F90C32 /* Resources */ = { 257 | isa = PBXResourcesBuildPhase; 258 | buildActionMask = 2147483647; 259 | files = ( 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXResourcesBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | 3EECB1231BAA923000F90C32 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 3EECB12D1BAA923000F90C32 /* ViewController.swift in Sources */, 271 | 3EECB12B1BAA923000F90C32 /* AppDelegate.swift in Sources */, 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | 3EECB1371BAA923000F90C32 /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 3EECB1401BAA923000F90C32 /* AdvancedTouchTests.swift in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | 3EECB1421BAA923000F90C32 /* Sources */ = { 284 | isa = PBXSourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 3EECB14B1BAA923000F90C32 /* AdvancedTouchUITests.swift in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | /* End PBXSourcesBuildPhase section */ 292 | 293 | /* Begin PBXTargetDependency section */ 294 | 3EECB13D1BAA923000F90C32 /* PBXTargetDependency */ = { 295 | isa = PBXTargetDependency; 296 | target = 3EECB1261BAA923000F90C32 /* AdvancedTouch */; 297 | targetProxy = 3EECB13C1BAA923000F90C32 /* PBXContainerItemProxy */; 298 | }; 299 | 3EECB1481BAA923000F90C32 /* PBXTargetDependency */ = { 300 | isa = PBXTargetDependency; 301 | target = 3EECB1261BAA923000F90C32 /* AdvancedTouch */; 302 | targetProxy = 3EECB1471BAA923000F90C32 /* PBXContainerItemProxy */; 303 | }; 304 | /* End PBXTargetDependency section */ 305 | 306 | /* Begin PBXVariantGroup section */ 307 | 3EECB12E1BAA923000F90C32 /* Main.storyboard */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | 3EECB12F1BAA923000F90C32 /* Base */, 311 | ); 312 | name = Main.storyboard; 313 | sourceTree = ""; 314 | }; 315 | 3EECB1331BAA923000F90C32 /* LaunchScreen.storyboard */ = { 316 | isa = PBXVariantGroup; 317 | children = ( 318 | 3EECB1341BAA923000F90C32 /* Base */, 319 | ); 320 | name = LaunchScreen.storyboard; 321 | sourceTree = ""; 322 | }; 323 | /* End PBXVariantGroup section */ 324 | 325 | /* Begin XCBuildConfiguration section */ 326 | 3EECB14D1BAA923000F90C32 /* Debug */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = NO; 345 | DEBUG_INFORMATION_FORMAT = dwarf; 346 | ENABLE_STRICT_OBJC_MSGSEND = YES; 347 | ENABLE_TESTABILITY = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_DYNAMIC_NO_PIC = NO; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_OPTIMIZATION_LEVEL = 0; 352 | GCC_PREPROCESSOR_DEFINITIONS = ( 353 | "DEBUG=1", 354 | "$(inherited)", 355 | ); 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 363 | MTL_ENABLE_DEBUG_INFO = YES; 364 | ONLY_ACTIVE_ARCH = YES; 365 | SDKROOT = iphoneos; 366 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 367 | TARGETED_DEVICE_FAMILY = "1,2"; 368 | }; 369 | name = Debug; 370 | }; 371 | 3EECB14E1BAA923000F90C32 /* Release */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ALWAYS_SEARCH_USER_PATHS = NO; 375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 376 | CLANG_CXX_LIBRARY = "libc++"; 377 | CLANG_ENABLE_MODULES = YES; 378 | CLANG_ENABLE_OBJC_ARC = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_EMPTY_BODY = YES; 383 | CLANG_WARN_ENUM_CONVERSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | TARGETED_DEVICE_FAMILY = "1,2"; 405 | VALIDATE_PRODUCT = YES; 406 | }; 407 | name = Release; 408 | }; 409 | 3EECB1501BAA923000F90C32 /* Debug */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | INFOPLIST_FILE = AdvancedTouch/Info.plist; 414 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 415 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouch; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | }; 418 | name = Debug; 419 | }; 420 | 3EECB1511BAA923000F90C32 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | INFOPLIST_FILE = AdvancedTouch/Info.plist; 425 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 426 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouch; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | }; 429 | name = Release; 430 | }; 431 | 3EECB1531BAA923000F90C32 /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | BUNDLE_LOADER = "$(TEST_HOST)"; 435 | INFOPLIST_FILE = AdvancedTouchTests/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouchTests; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AdvancedTouch.app/AdvancedTouch"; 440 | }; 441 | name = Debug; 442 | }; 443 | 3EECB1541BAA923000F90C32 /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | buildSettings = { 446 | BUNDLE_LOADER = "$(TEST_HOST)"; 447 | INFOPLIST_FILE = AdvancedTouchTests/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouchTests; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AdvancedTouch.app/AdvancedTouch"; 452 | }; 453 | name = Release; 454 | }; 455 | 3EECB1561BAA923000F90C32 /* Debug */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | INFOPLIST_FILE = AdvancedTouchUITests/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouchUITests; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | TEST_TARGET_NAME = AdvancedTouch; 463 | USES_XCTRUNNER = YES; 464 | }; 465 | name = Debug; 466 | }; 467 | 3EECB1571BAA923000F90C32 /* Release */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | INFOPLIST_FILE = AdvancedTouchUITests/Info.plist; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 472 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.AdvancedTouchUITests; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | TEST_TARGET_NAME = AdvancedTouch; 475 | USES_XCTRUNNER = YES; 476 | }; 477 | name = Release; 478 | }; 479 | /* End XCBuildConfiguration section */ 480 | 481 | /* Begin XCConfigurationList section */ 482 | 3EECB1221BAA923000F90C32 /* Build configuration list for PBXProject "AdvancedTouch" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | 3EECB14D1BAA923000F90C32 /* Debug */, 486 | 3EECB14E1BAA923000F90C32 /* Release */, 487 | ); 488 | defaultConfigurationIsVisible = 0; 489 | defaultConfigurationName = Release; 490 | }; 491 | 3EECB14F1BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouch" */ = { 492 | isa = XCConfigurationList; 493 | buildConfigurations = ( 494 | 3EECB1501BAA923000F90C32 /* Debug */, 495 | 3EECB1511BAA923000F90C32 /* Release */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | 3EECB1521BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouchTests" */ = { 501 | isa = XCConfigurationList; 502 | buildConfigurations = ( 503 | 3EECB1531BAA923000F90C32 /* Debug */, 504 | 3EECB1541BAA923000F90C32 /* Release */, 505 | ); 506 | defaultConfigurationIsVisible = 0; 507 | defaultConfigurationName = Release; 508 | }; 509 | 3EECB1551BAA923000F90C32 /* Build configuration list for PBXNativeTarget "AdvancedTouchUITests" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 3EECB1561BAA923000F90C32 /* Debug */, 513 | 3EECB1571BAA923000F90C32 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | /* End XCConfigurationList section */ 519 | }; 520 | rootObject = 3EECB11F1BAA923000F90C32 /* Project object */; 521 | } 522 | -------------------------------------------------------------------------------- /AdvancedTouch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AdvancedTouch.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/AdvancedTouch.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /AdvancedTouch.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AdvancedTouch.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 3EECB1261BAA923000F90C32 16 | 17 | primary 18 | 19 | 20 | 3EECB13A1BAA923000F90C32 21 | 22 | primary 23 | 24 | 25 | 3EECB1451BAA923000F90C32 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /AdvancedTouch/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AdvancedTouch 4 | // 5 | // Created by Simon Gladman on 17/09/2015. 6 | // Copyright © 2015 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /AdvancedTouch/Assets.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 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /AdvancedTouch/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 | 27 | 28 | -------------------------------------------------------------------------------- /AdvancedTouch/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 | -------------------------------------------------------------------------------- /AdvancedTouch/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 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /AdvancedTouch/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AdvancedTouch 4 | // 5 | // Created by Simon Gladman on 17/09/2015. 6 | // Copyright © 2015 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | let mainDrawLayer = CAShapeLayer() 14 | let mainDrawPath = UIBezierPath() 15 | 16 | let coalescedDrawLayer = CAShapeLayer() 17 | let coalescedDrawPath = UIBezierPath() 18 | 19 | let predictedDrawLayer = CAShapeLayer() 20 | let predictedDrawPath = UIBezierPath() 21 | 22 | let layers: [CAShapeLayer] 23 | let paths: [UIBezierPath] 24 | 25 | required init?(coder aDecoder: NSCoder) 26 | { 27 | layers = [mainDrawLayer, coalescedDrawLayer, predictedDrawLayer] 28 | paths = [mainDrawPath, coalescedDrawPath, predictedDrawPath] 29 | 30 | super.init(coder: aDecoder) 31 | } 32 | 33 | override func viewDidLoad() 34 | { 35 | super.viewDidLoad() 36 | view.backgroundColor = UIColor.blackColor() 37 | 38 | // common 39 | 40 | for layer in layers 41 | { 42 | layer.lineCap = kCALineCapRound 43 | layer.lineWidth = 2 44 | layer.fillColor = nil 45 | 46 | view.layer.addSublayer(layer) 47 | } 48 | 49 | // mainDrawLayer 50 | 51 | mainDrawLayer.strokeColor = UIColor(red: 0.5, green: 0.5, blue: 1, alpha: 1).CGColor 52 | 53 | // coalescedDrawLayer 54 | 55 | coalescedDrawLayer.strokeColor = UIColor.yellowColor().CGColor 56 | 57 | // predictedDrawLayer 58 | 59 | predictedDrawLayer.strokeColor = UIColor.whiteColor().CGColor 60 | predictedDrawLayer.lineWidth = 1 61 | predictedDrawLayer.fillColor = UIColor.whiteColor().CGColor 62 | } 63 | 64 | 65 | override func touchesBegan(touches: Set, withEvent event: UIEvent?) 66 | { 67 | super.touchesBegan(touches, withEvent: event) 68 | 69 | for (path, layer) in zip(paths, layers) 70 | { 71 | path.removeAllPoints() 72 | 73 | layer.path = path.CGPath 74 | layer.hidden = false 75 | } 76 | 77 | guard let touch = touches.first else 78 | { 79 | return 80 | } 81 | 82 | let locationInView = touch.locationInView(view) 83 | 84 | for path in paths 85 | { 86 | path.moveToPoint(locationInView) 87 | } 88 | 89 | for layer in layers 90 | { 91 | layer.hidden = false 92 | } 93 | } 94 | 95 | override func touchesMoved(touches: Set, withEvent event: UIEvent?) 96 | { 97 | super.touchesMoved(touches, withEvent: event) 98 | 99 | guard let touch = touches.first, event = event else 100 | { 101 | return 102 | } 103 | 104 | let locationInView = touch.locationInView(view) 105 | 106 | mainDrawPath.addLineToPoint(locationInView) 107 | mainDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 4)) 108 | mainDrawPath.moveToPoint(locationInView) 109 | 110 | mainDrawLayer.path = mainDrawPath.CGPath 111 | 112 | // draw coalescedTouches 113 | 114 | if let coalescedTouches = event.coalescedTouchesForTouch(touch) 115 | { 116 | print("coalescedTouches:", coalescedTouches.count) 117 | 118 | for coalescedTouch in coalescedTouches 119 | { 120 | let locationInView = coalescedTouch.locationInView(view) 121 | 122 | coalescedDrawPath.addLineToPoint(locationInView) 123 | coalescedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 2)) 124 | coalescedDrawPath.moveToPoint(locationInView) 125 | } 126 | 127 | coalescedDrawLayer.path = coalescedDrawPath.CGPath 128 | } 129 | 130 | // draw predictedTouches 131 | 132 | if let predictedTouches = event.predictedTouchesForTouch(touch) 133 | { 134 | print("predictedTouches:", predictedTouches.count) 135 | 136 | for predictedTouch in predictedTouches 137 | { 138 | let locationInView = predictedTouch.locationInView(view) 139 | 140 | predictedDrawPath.moveToPoint(touch.locationInView(view)) 141 | predictedDrawPath.addLineToPoint(locationInView) 142 | 143 | predictedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 1)) 144 | } 145 | 146 | predictedDrawLayer.path = predictedDrawPath.CGPath 147 | } 148 | 149 | // stupid synchronous calculation 150 | 151 | var foo = Double(1) 152 | 153 | for bar in 0 ... 4_000_000 154 | { 155 | foo += sqrt(Double(bar)) 156 | } 157 | } 158 | 159 | override func touchesEnded(touches: Set, withEvent event: UIEvent?) 160 | { 161 | super.touchesEnded(touches, withEvent: event) 162 | 163 | 164 | } 165 | } 166 | 167 | extension UIBezierPath 168 | { 169 | static func createCircleAtPoint(origin: CGPoint, radius: CGFloat) -> UIBezierPath 170 | { 171 | let boundingRect = CGRect(x: origin.x - radius, 172 | y: origin.y - radius, 173 | width: radius * 2, 174 | height: radius * 2) 175 | 176 | let circle = UIBezierPath(ovalInRect: boundingRect) 177 | 178 | return circle 179 | } 180 | } 181 | 182 | -------------------------------------------------------------------------------- /AdvancedTouch/assets/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlexMonkey/AdvancedTouch/b241502ff1fa8a25c82c6fdc0958615980bc02ef/AdvancedTouch/assets/screenshot.jpg -------------------------------------------------------------------------------- /AdvancedTouchTests/AdvancedTouchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvancedTouchTests.swift 3 | // AdvancedTouchTests 4 | // 5 | // Created by Simon Gladman on 17/09/2015. 6 | // Copyright © 2015 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import AdvancedTouch 11 | 12 | class AdvancedTouchTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /AdvancedTouchTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /AdvancedTouchUITests/AdvancedTouchUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdvancedTouchUITests.swift 3 | // AdvancedTouchUITests 4 | // 5 | // Created by Simon Gladman on 17/09/2015. 6 | // Copyright © 2015 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class AdvancedTouchUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /AdvancedTouchUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvancedTouch 2 | ###Swift Advanced Touch Handling in iOS9: Coalescing and Prediction 3 | 4 | *Companion project to http://flexmonkey.blogspot.co.uk/2015/09/advanced-touch-handling-in-ios9.html* 5 | 6 | ![screenshot](AdvancedTouch/assets/screenshot.jpg) 7 | 8 | With the introduction of the iPad Pro, low latency, high resolution touch handling has become more important than ever. Even if your app runs smoothly at 60 frames per second, high-end iOS devices have a touch scan rate of 120 hertz and with the standard out-of-the-box touch handling, you could easily miss half of your user’s touch movements. 9 | 10 | Luckily, with iOS 9, Apple have introduced some new functionality to `UIEvent` to reduce touch latency and improve temporal resolution. There's a demo application to go with with post that you can find at my GitHub repository here. 11 | 12 | ###Touch Coalescing 13 | 14 | The first feature is touch coalescing. If your app is sampling touches with an overridden `touchesMoved()`, the most amount of touch samples you’ll get is 60 per second and, if your app is concerning itself with some fancy calculations on the main thread, it could well be less than that. 15 | 16 | Touch coalescing allows you to access all the intermediate touches that may have occurred between `touchesMoved()` invocations. This allows, for example, your app to draw a smooth curve consisting of half a dozen points when you’ve only received one `UIEvent`. 17 | 18 | The syntax is super simple. In my demo application, I have a few `CAShapeLayer` instances that I draw upon. The first layer (`mainDrawLayer`, which contains blue lines with circles at each vertex) is drawn on using information from the main `UIEvent` from `touchesMoved()`: 19 | 20 | override func touchesMoved(touches: Set, withEvent event: UIEvent?) 21 | { 22 | super.touchesMoved(touches, withEvent: event) 23 | 24 | guard let touch = touches.first, event = event else 25 | { 26 | return 27 | } 28 | 29 | let locationInView = touch.locationInView(view) 30 | 31 | mainDrawPath.addLineToPoint(locationInView) 32 | mainDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 4)) 33 | mainDrawPath.moveToPoint(locationInView) 34 | 35 | mainDrawLayer.path = mainDrawPath.CGPath 36 | [...] 37 | 38 | If you’re wondering where `createCircleAtPoint()` method comes from, it’s a small extension I wrote to `UIBezierPath` that returns a circle path at a given point: 39 | 40 | extension UIBezierPath 41 | { 42 | static func createCircleAtPoint(origin: CGPoint, radius: CGFloat) -> UIBezierPath 43 | { 44 | let boundingRect = CGRect(x: origin.x - radius, 45 | y: origin.y - radius, 46 | width: radius * 2, 47 | height: radius * 2) 48 | 49 | let circle = UIBezierPath(ovalInRect: boundingRect) 50 | 51 | return circle 52 | } 53 | } 54 | 55 | If the user moves their finger across the screen fast enough, they’ll get a jagged line, which is probably not what they want. To access the intermediate touches, I use the `coalescedTouchesForTouch()` method of the event which returns an array of `UITouch`: 56 | 57 | [...] 58 | if let coalescedTouches = event.coalescedTouchesForTouch(touch) 59 | { 60 | print("coalescedTouches:", coalescedTouches.count) 61 | 62 | for coalescedTouch in coalescedTouches 63 | { 64 | let locationInView = coalescedTouch.locationInView(view) 65 | 66 | coalescedDrawPath.addLineToPoint(locationInView) 67 | coalescedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 2)) 68 | coalescedDrawPath.moveToPoint(locationInView) 69 | } 70 | 71 | coalescedDrawLayer.path = coalescedDrawPath.CGPath 72 | } 73 | [...] 74 | 75 | Here, I loop over those touches (I trace the number of touches to the console for information) and create a yellow line by drawing on `coalescedDrawLayer` as an overlay. I’ve added a horrible loop to the touchesMoved method to slow things down a bit: 76 | 77 | var foo = Double(1) 78 | 79 | for bar in 0 ... 1_000_000 80 | { 81 | foo += sqrt(Double(bar)) 82 | } 83 | 84 | If you run this app on your iOS 9 device and scribble away you can see the two results: a jagged blue line and a beautifully smooth yellow line based on all those intermediate touch events that could have been missed. At each touch location, I add a small circle which clearly illustrates the increased resolution of the yellow curve based on the coalesced data. 85 | 86 | ###Predictive Touch 87 | 88 | Something maybe even cleverer is touch prediction which allows you to preempt where a user’s finger (or Apple Pencil) may be in the near future. Predictive touch uses some highly tuned algorithms and is continually updated with where iOS expects the users touch to be in approximately a frame in the future. This means you could begin preparing user interface components (e.g. beginning a fade or instantiating some objects) before they’re actually required to help reduce latency. 89 | 90 | In my demo application, I display the predicted touch as small white spots with “tails” that originate for their predicting touch. The syntax is not dissimilar to that of coalesced touch: the event has a new method, `predictedTouchesForTouch()`, which returns an an array of `UITouch`: 91 | 92 | [...] 93 | if let predictedTouches = event.predictedTouchesForTouch(touch) 94 | { 95 | print("predictedTouches:", predictedTouches.count) 96 | 97 | for predictedTouch in predictedTouches 98 | { 99 | let locationInView = predictedTouch.locationInView(view) 100 | 101 | predictedDrawPath.moveToPoint(touch.locationInView(view)) 102 | predictedDrawPath.addLineToPoint(locationInView) 103 | predictedDrawPath.appendPath(UIBezierPath.createCircleAtPoint(locationInView, radius: 1)) 104 | } 105 | 106 | predictedDrawLayer.path = predictedDrawPath.CGPath 107 | } 108 | [...] 109 | 110 | 111 | When you run the app, as you move your finger across the screen, the little predicted touch spots give an indication of where iOS thinks your next touch will be. With a jagged motion, where the prediction doesn’t work so well, you can see those points as little tadpoles following the momentum of your touch. 112 | 113 | You can see a few grey circles in the screen grab above which show two touches iOS predicted I would do to complete the spiral - that I never actually did! Spooky! 114 | 115 | ###Conclusion 116 | 117 | As users demand less latency and a higher touch resolution, touch coalescing and prediction allow our apps to support that. Whatever the framerate of our apps, we can respond to all of the user’s gestures and even preempt some of them! 118 | 119 | The source code for this demo app is available at my GitHub repository here. 120 | --------------------------------------------------------------------------------