├── .gitignore ├── .gitmodules ├── Getting Ahead Of The Curve NSSpain 2015.pdf ├── PDPseudo3DTouchGestureRecognizer.h ├── PDPseudo3DTouchGestureRecognizer.m ├── Pseudo3DTouch.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Pseudo3DTouch ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | UserInterfaceState.xcuserstate 3 | xcuserdata 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Libraries/pop"] 2 | path = Libraries/pop 3 | url = https://github.com/facebook/pop.git 4 | -------------------------------------------------------------------------------- /Getting Ahead Of The Curve NSSpain 2015.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3ll/Pseudo3DTouch/a84971003c9843d4b98938ea820c0084b050ceec/Getting Ahead Of The Curve NSSpain 2015.pdf -------------------------------------------------------------------------------- /PDPseudo3DTouchGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // PDPseudo3DTouchGestureRecognizer.h 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern void PDPseudo3DTouchPlayTap(); 12 | 13 | @interface PDPseudo3DTouchGestureRecognizer : UIGestureRecognizer 14 | 15 | /** 16 | @abstract Defines the amount the gesture has depressed into the screen from 1.0 to infinity. Larger means "deeper" into the screen. 17 | */ 18 | @property (nonatomic, readonly) CGFloat depth; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /PDPseudo3DTouchGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // PDPseudo3DTouchGestureRecognizer.m 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import "PDPseudo3DTouchGestureRecognizer.h" 10 | 11 | #import 12 | 13 | // This isn't a pan gesture, so this defines the max translation allowed. 14 | static CGFloat const kMaxTouchTranslationAllowed = 50.0; 15 | 16 | #if TARGET_IPHONE_SIMULATOR 17 | #else 18 | // 1.0 is basically the same as 1.1; not a lot of force is applied. 19 | static CGFloat const kDepthCalculationFudgeFactor = 0.1; 20 | #endif 21 | 22 | #pragma mark - Taptics 23 | extern int AudioServicesPlaySystemSoundWithVibration(int, id obj, NSDictionary *dictionary); 24 | 25 | //! @abstract Plays a tiny small vibration alluding to Taptic Feedback. 26 | void PDPseudo3DTouchPlayTap() { 27 | NSArray *vibrationPattern = @[ 28 | @YES, // YES to vibrate 29 | @50, // 50ms 30 | ]; 31 | 32 | NSDictionary *dictionary = @{ 33 | @"Intensity" : @1.0, 34 | @"VibePattern" : vibrationPattern, 35 | }; 36 | 37 | // 0xFFF magic number for silence or no sound. 38 | AudioServicesPlaySystemSoundWithVibration(0xFFF, nil, dictionary); 39 | } 40 | 41 | @implementation PDPseudo3DTouchGestureRecognizer { 42 | CGFloat _touchRadius; 43 | 44 | CGFloat _initialTouchRadius; 45 | } 46 | 47 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 48 | { 49 | [super touchesBegan:touches withEvent:event]; 50 | 51 | // Only work with one touch. Doesn't make sense with more than one. 52 | if (touches.count > 1) { 53 | self.state = UIGestureRecognizerStateFailed; 54 | } else { 55 | UITouch *touch = [touches anyObject]; 56 | _touchRadius = touch.majorRadius; 57 | 58 | // Grabs the initial touch radius as a starting point. 59 | _initialTouchRadius = _touchRadius; 60 | 61 | [self _beginObservingTouchMajorRadius:touch]; 62 | 63 | self.state = UIGestureRecognizerStateBegan; 64 | } 65 | } 66 | 67 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 68 | { 69 | [super touchesMoved:touches withEvent:event]; 70 | 71 | UITouch *touch = [touches anyObject]; 72 | 73 | CGPoint touchLocation = [touch locationInView:self.view]; 74 | 75 | // Ignore any sort of movement with the finger... this isn't a pan gesture. 76 | if ((fabs(touchLocation.x) > kMaxTouchTranslationAllowed) || 77 | (fabs(touchLocation.y) > kMaxTouchTranslationAllowed)) { 78 | self.state = UIGestureRecognizerStateFailed; 79 | [self _endObservingTouchMajorRadius:touch]; 80 | } 81 | } 82 | 83 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 84 | { 85 | [super touchesCancelled:touches withEvent:event]; 86 | 87 | UITouch *touch = [touches anyObject]; 88 | [self _endObservingTouchMajorRadius:touch]; 89 | 90 | _touchRadius = _initialTouchRadius; 91 | 92 | self.state = UIGestureRecognizerStateCancelled; 93 | } 94 | 95 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 96 | { 97 | [super touchesEnded:touches withEvent:event]; 98 | 99 | UITouch *touch = [touches anyObject]; 100 | [self _endObservingTouchMajorRadius:touch]; 101 | 102 | _touchRadius = _initialTouchRadius; 103 | 104 | self.state = UIGestureRecognizerStateEnded; 105 | } 106 | 107 | #pragma mark - 3D Touch Observing 108 | - (void)_beginObservingTouchMajorRadius:(UITouch *)touch { 109 | // Observe UITouch -majorRadius changes. 110 | [touch addObserver:self 111 | forKeyPath:NSStringFromSelector(@selector(majorRadius)) 112 | options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew 113 | context:NULL]; 114 | } 115 | 116 | - (void)_endObservingTouchMajorRadius:(UITouch *)touch { 117 | // yolo don't care 118 | @try { 119 | [touch removeObserver:self 120 | forKeyPath:NSStringFromSelector(@selector(majorRadius)) 121 | context:NULL]; 122 | } @catch (NSException *e) { } 123 | } 124 | 125 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 126 | { 127 | // Anytime -majorRadius changes, indicate that the gesture's state has changed. 128 | if ([keyPath isEqualToString:NSStringFromSelector(@selector(majorRadius))]) { 129 | self.state = UIGestureRecognizerStateChanged; 130 | _touchRadius = [(UITouch *)object majorRadius]; 131 | } 132 | } 133 | 134 | #pragma mark - Getters / Setters 135 | - (CGFloat)depth 136 | { 137 | // Hardcodes depth on iPhone Simulator 1.25. 138 | CGFloat depth = 1.0; 139 | #if TARGET_IPHONE_SIMULATOR 140 | if (self.state == UIGestureRecognizerStateChanged) { 141 | depth += 0.25; 142 | } 143 | #else 144 | // Converts the delta between the initial touch radius and the current one to a useable depth property. 145 | depth = MAX(depth, (_touchRadius / _initialTouchRadius)); 146 | if ((depth - 1.0) <= kDepthCalculationFudgeFactor) { 147 | depth = 1.0; 148 | } 149 | #endif 150 | return depth; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /Pseudo3DTouch.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6625876B1BA5961D009F56CA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6625876A1BA5961D009F56CA /* main.m */; }; 11 | 6625876E1BA5961D009F56CA /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6625876D1BA5961D009F56CA /* AppDelegate.m */; }; 12 | 662587711BA5961D009F56CA /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 662587701BA5961D009F56CA /* ViewController.m */; }; 13 | 662587741BA5961D009F56CA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662587721BA5961D009F56CA /* Main.storyboard */; }; 14 | 662587761BA5961D009F56CA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 662587751BA5961D009F56CA /* Assets.xcassets */; }; 15 | 662587791BA5961D009F56CA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 662587771BA5961D009F56CA /* LaunchScreen.storyboard */; }; 16 | 662587821BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 662587811BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.m */; settings = {ASSET_TAGS = (); }; }; 17 | 662587951BA59F97009F56CA /* pop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6625878E1BA59F8A009F56CA /* pop.framework */; }; 18 | 662587961BA59F97009F56CA /* pop.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6625878E1BA59F8A009F56CA /* pop.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | 66F8572C1BA82FB9006B936D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66F8572B1BA82FB9006B936D /* AudioToolbox.framework */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 6625878B1BA59F8A009F56CA /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 26 | proxyType = 2; 27 | remoteGlobalIDString = EC191218162FB53A00E0CC76; 28 | remoteInfo = "pop-ios-static"; 29 | }; 30 | 6625878D1BA59F8A009F56CA /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 33 | proxyType = 2; 34 | remoteGlobalIDString = 0B6BE74819FFD3B900762101; 35 | remoteInfo = "pop-ios-framework"; 36 | }; 37 | 6625878F1BA59F8A009F56CA /* PBXContainerItemProxy */ = { 38 | isa = PBXContainerItemProxy; 39 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 40 | proxyType = 2; 41 | remoteGlobalIDString = EC68857F18C7B60000C6194C; 42 | remoteInfo = "pop-osx-framework"; 43 | }; 44 | 662587911BA59F8A009F56CA /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 47 | proxyType = 2; 48 | remoteGlobalIDString = ECF01ED318C92B7F009E0AD1; 49 | remoteInfo = "pop-tests-ios"; 50 | }; 51 | 662587931BA59F8A009F56CA /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 54 | proxyType = 2; 55 | remoteGlobalIDString = EC7E319918C93D6500B38170; 56 | remoteInfo = "pop-tests-osx"; 57 | }; 58 | 662587971BA59F98009F56CA /* PBXContainerItemProxy */ = { 59 | isa = PBXContainerItemProxy; 60 | containerPortal = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 61 | proxyType = 1; 62 | remoteGlobalIDString = 0B6BE74719FFD3B900762101; 63 | remoteInfo = "pop-ios-framework"; 64 | }; 65 | /* End PBXContainerItemProxy section */ 66 | 67 | /* Begin PBXCopyFilesBuildPhase section */ 68 | 662587991BA59F98009F56CA /* Embed Frameworks */ = { 69 | isa = PBXCopyFilesBuildPhase; 70 | buildActionMask = 2147483647; 71 | dstPath = ""; 72 | dstSubfolderSpec = 10; 73 | files = ( 74 | 662587961BA59F97009F56CA /* pop.framework in Embed Frameworks */, 75 | ); 76 | name = "Embed Frameworks"; 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXCopyFilesBuildPhase section */ 80 | 81 | /* Begin PBXFileReference section */ 82 | 662587661BA5961D009F56CA /* Pseudo3DTouch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pseudo3DTouch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | 6625876A1BA5961D009F56CA /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 84 | 6625876C1BA5961D009F56CA /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 85 | 6625876D1BA5961D009F56CA /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 86 | 6625876F1BA5961D009F56CA /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 87 | 662587701BA5961D009F56CA /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 88 | 662587731BA5961D009F56CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 89 | 662587751BA5961D009F56CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 90 | 662587781BA5961D009F56CA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 91 | 6625877A1BA5961D009F56CA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 92 | 662587801BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDPseudo3DTouchGestureRecognizer.h; sourceTree = SOURCE_ROOT; }; 93 | 662587811BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDPseudo3DTouchGestureRecognizer.m; sourceTree = SOURCE_ROOT; }; 94 | 662587831BA59F8A009F56CA /* pop.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = pop.xcodeproj; path = Libraries/pop/pop.xcodeproj; sourceTree = ""; }; 95 | 66F8572B1BA82FB9006B936D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 96 | /* End PBXFileReference section */ 97 | 98 | /* Begin PBXFrameworksBuildPhase section */ 99 | 662587631BA5961D009F56CA /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | 66F8572C1BA82FB9006B936D /* AudioToolbox.framework in Frameworks */, 104 | 662587951BA59F97009F56CA /* pop.framework in Frameworks */, 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | /* End PBXFrameworksBuildPhase section */ 109 | 110 | /* Begin PBXGroup section */ 111 | 6625875D1BA5961D009F56CA = { 112 | isa = PBXGroup; 113 | children = ( 114 | 66F8572B1BA82FB9006B936D /* AudioToolbox.framework */, 115 | 662587831BA59F8A009F56CA /* pop.xcodeproj */, 116 | 662587681BA5961D009F56CA /* Pseudo3DTouch */, 117 | 662587671BA5961D009F56CA /* Products */, 118 | ); 119 | sourceTree = ""; 120 | }; 121 | 662587671BA5961D009F56CA /* Products */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | 662587661BA5961D009F56CA /* Pseudo3DTouch.app */, 125 | ); 126 | name = Products; 127 | sourceTree = ""; 128 | }; 129 | 662587681BA5961D009F56CA /* Pseudo3DTouch */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 6625876C1BA5961D009F56CA /* AppDelegate.h */, 133 | 6625876D1BA5961D009F56CA /* AppDelegate.m */, 134 | 6625876F1BA5961D009F56CA /* ViewController.h */, 135 | 662587701BA5961D009F56CA /* ViewController.m */, 136 | 662587801BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.h */, 137 | 662587811BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.m */, 138 | 662587721BA5961D009F56CA /* Main.storyboard */, 139 | 662587751BA5961D009F56CA /* Assets.xcassets */, 140 | 662587771BA5961D009F56CA /* LaunchScreen.storyboard */, 141 | 6625877A1BA5961D009F56CA /* Info.plist */, 142 | 662587691BA5961D009F56CA /* Supporting Files */, 143 | ); 144 | path = Pseudo3DTouch; 145 | sourceTree = ""; 146 | }; 147 | 662587691BA5961D009F56CA /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 6625876A1BA5961D009F56CA /* main.m */, 151 | ); 152 | name = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | 662587841BA59F8A009F56CA /* Products */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 6625878C1BA59F8A009F56CA /* libpop.a */, 159 | 6625878E1BA59F8A009F56CA /* pop.framework */, 160 | 662587901BA59F8A009F56CA /* pop.framework */, 161 | 662587921BA59F8A009F56CA /* pop-tests.xctest */, 162 | 662587941BA59F8A009F56CA /* pop-tests.xctest */, 163 | ); 164 | name = Products; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 662587651BA5961D009F56CA /* Pseudo3DTouch */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 6625877D1BA5961D009F56CA /* Build configuration list for PBXNativeTarget "Pseudo3DTouch" */; 173 | buildPhases = ( 174 | 662587621BA5961D009F56CA /* Sources */, 175 | 662587631BA5961D009F56CA /* Frameworks */, 176 | 662587641BA5961D009F56CA /* Resources */, 177 | 662587991BA59F98009F56CA /* Embed Frameworks */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | 662587981BA59F98009F56CA /* PBXTargetDependency */, 183 | ); 184 | name = Pseudo3DTouch; 185 | productName = Pseudo3DTouch; 186 | productReference = 662587661BA5961D009F56CA /* Pseudo3DTouch.app */; 187 | productType = "com.apple.product-type.application"; 188 | }; 189 | /* End PBXNativeTarget section */ 190 | 191 | /* Begin PBXProject section */ 192 | 6625875E1BA5961D009F56CA /* Project object */ = { 193 | isa = PBXProject; 194 | attributes = { 195 | LastUpgradeCheck = 0700; 196 | ORGANIZATIONNAME = "Adam Bell"; 197 | TargetAttributes = { 198 | 662587651BA5961D009F56CA = { 199 | CreatedOnToolsVersion = 7.0; 200 | }; 201 | }; 202 | }; 203 | buildConfigurationList = 662587611BA5961D009F56CA /* Build configuration list for PBXProject "Pseudo3DTouch" */; 204 | compatibilityVersion = "Xcode 3.2"; 205 | developmentRegion = English; 206 | hasScannedForEncodings = 0; 207 | knownRegions = ( 208 | en, 209 | Base, 210 | ); 211 | mainGroup = 6625875D1BA5961D009F56CA; 212 | productRefGroup = 662587671BA5961D009F56CA /* Products */; 213 | projectDirPath = ""; 214 | projectReferences = ( 215 | { 216 | ProductGroup = 662587841BA59F8A009F56CA /* Products */; 217 | ProjectRef = 662587831BA59F8A009F56CA /* pop.xcodeproj */; 218 | }, 219 | ); 220 | projectRoot = ""; 221 | targets = ( 222 | 662587651BA5961D009F56CA /* Pseudo3DTouch */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXReferenceProxy section */ 228 | 6625878C1BA59F8A009F56CA /* libpop.a */ = { 229 | isa = PBXReferenceProxy; 230 | fileType = archive.ar; 231 | path = libpop.a; 232 | remoteRef = 6625878B1BA59F8A009F56CA /* PBXContainerItemProxy */; 233 | sourceTree = BUILT_PRODUCTS_DIR; 234 | }; 235 | 6625878E1BA59F8A009F56CA /* pop.framework */ = { 236 | isa = PBXReferenceProxy; 237 | fileType = wrapper.framework; 238 | path = pop.framework; 239 | remoteRef = 6625878D1BA59F8A009F56CA /* PBXContainerItemProxy */; 240 | sourceTree = BUILT_PRODUCTS_DIR; 241 | }; 242 | 662587901BA59F8A009F56CA /* pop.framework */ = { 243 | isa = PBXReferenceProxy; 244 | fileType = wrapper.framework; 245 | path = pop.framework; 246 | remoteRef = 6625878F1BA59F8A009F56CA /* PBXContainerItemProxy */; 247 | sourceTree = BUILT_PRODUCTS_DIR; 248 | }; 249 | 662587921BA59F8A009F56CA /* pop-tests.xctest */ = { 250 | isa = PBXReferenceProxy; 251 | fileType = wrapper.cfbundle; 252 | path = "pop-tests.xctest"; 253 | remoteRef = 662587911BA59F8A009F56CA /* PBXContainerItemProxy */; 254 | sourceTree = BUILT_PRODUCTS_DIR; 255 | }; 256 | 662587941BA59F8A009F56CA /* pop-tests.xctest */ = { 257 | isa = PBXReferenceProxy; 258 | fileType = wrapper.cfbundle; 259 | path = "pop-tests.xctest"; 260 | remoteRef = 662587931BA59F8A009F56CA /* PBXContainerItemProxy */; 261 | sourceTree = BUILT_PRODUCTS_DIR; 262 | }; 263 | /* End PBXReferenceProxy section */ 264 | 265 | /* Begin PBXResourcesBuildPhase section */ 266 | 662587641BA5961D009F56CA /* Resources */ = { 267 | isa = PBXResourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 662587791BA5961D009F56CA /* LaunchScreen.storyboard in Resources */, 271 | 662587761BA5961D009F56CA /* Assets.xcassets in Resources */, 272 | 662587741BA5961D009F56CA /* Main.storyboard in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | /* End PBXResourcesBuildPhase section */ 277 | 278 | /* Begin PBXSourcesBuildPhase section */ 279 | 662587621BA5961D009F56CA /* Sources */ = { 280 | isa = PBXSourcesBuildPhase; 281 | buildActionMask = 2147483647; 282 | files = ( 283 | 662587711BA5961D009F56CA /* ViewController.m in Sources */, 284 | 6625876E1BA5961D009F56CA /* AppDelegate.m in Sources */, 285 | 662587821BA59678009F56CA /* PDPseudo3DTouchGestureRecognizer.m in Sources */, 286 | 6625876B1BA5961D009F56CA /* main.m in Sources */, 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | }; 290 | /* End PBXSourcesBuildPhase section */ 291 | 292 | /* Begin PBXTargetDependency section */ 293 | 662587981BA59F98009F56CA /* PBXTargetDependency */ = { 294 | isa = PBXTargetDependency; 295 | name = "pop-ios-framework"; 296 | targetProxy = 662587971BA59F98009F56CA /* PBXContainerItemProxy */; 297 | }; 298 | /* End PBXTargetDependency section */ 299 | 300 | /* Begin PBXVariantGroup section */ 301 | 662587721BA5961D009F56CA /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 662587731BA5961D009F56CA /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | 662587771BA5961D009F56CA /* LaunchScreen.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 662587781BA5961D009F56CA /* Base */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 6625877B1BA5961D009F56CA /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 325 | CLANG_CXX_LIBRARY = "libc++"; 326 | CLANG_ENABLE_MODULES = YES; 327 | CLANG_ENABLE_OBJC_ARC = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_CONSTANT_CONVERSION = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_EMPTY_BODY = YES; 332 | CLANG_WARN_ENUM_CONVERSION = YES; 333 | CLANG_WARN_INT_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = dwarf; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | ENABLE_TESTABILITY = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 357 | MTL_ENABLE_DEBUG_INFO = YES; 358 | ONLY_ACTIVE_ARCH = YES; 359 | SDKROOT = iphoneos; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | }; 362 | name = Debug; 363 | }; 364 | 6625877C1BA5961D009F56CA /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 369 | CLANG_CXX_LIBRARY = "libc++"; 370 | CLANG_ENABLE_MODULES = YES; 371 | CLANG_ENABLE_OBJC_ARC = YES; 372 | CLANG_WARN_BOOL_CONVERSION = YES; 373 | CLANG_WARN_CONSTANT_CONVERSION = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INT_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 382 | COPY_PHASE_STRIP = NO; 383 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 384 | ENABLE_NS_ASSERTIONS = NO; 385 | ENABLE_STRICT_OBJC_MSGSEND = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu99; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 395 | MTL_ENABLE_DEBUG_INFO = NO; 396 | SDKROOT = iphoneos; 397 | TARGETED_DEVICE_FAMILY = "1,2"; 398 | VALIDATE_PRODUCT = YES; 399 | }; 400 | name = Release; 401 | }; 402 | 6625877E1BA5961D009F56CA /* Debug */ = { 403 | isa = XCBuildConfiguration; 404 | buildSettings = { 405 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 406 | INFOPLIST_FILE = Pseudo3DTouch/Info.plist; 407 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 409 | PRODUCT_BUNDLE_IDENTIFIER = ca.adambell.Pseudo3DTouch; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | }; 412 | name = Debug; 413 | }; 414 | 6625877F1BA5961D009F56CA /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 418 | INFOPLIST_FILE = Pseudo3DTouch/Info.plist; 419 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 421 | PRODUCT_BUNDLE_IDENTIFIER = ca.adambell.Pseudo3DTouch; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | }; 424 | name = Release; 425 | }; 426 | /* End XCBuildConfiguration section */ 427 | 428 | /* Begin XCConfigurationList section */ 429 | 662587611BA5961D009F56CA /* Build configuration list for PBXProject "Pseudo3DTouch" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | 6625877B1BA5961D009F56CA /* Debug */, 433 | 6625877C1BA5961D009F56CA /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | 6625877D1BA5961D009F56CA /* Build configuration list for PBXNativeTarget "Pseudo3DTouch" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | 6625877E1BA5961D009F56CA /* Debug */, 442 | 6625877F1BA5961D009F56CA /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = 6625875E1BA5961D009F56CA /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /Pseudo3DTouch.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pseudo3DTouch/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Pseudo3DTouch/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Pseudo3DTouch/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 | } -------------------------------------------------------------------------------- /Pseudo3DTouch/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 | -------------------------------------------------------------------------------- /Pseudo3DTouch/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 | -------------------------------------------------------------------------------- /Pseudo3DTouch/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 | -------------------------------------------------------------------------------- /Pseudo3DTouch/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Pseudo3DTouch/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import 12 | 13 | #import "PDPseudo3DTouchGestureRecognizer.h" 14 | 15 | static const CGFloat kCircleDiameter = 220.0; 16 | 17 | static const CGFloat kCircleScaleDampener = 0.6; 18 | static const CGFloat kCircleScaleDampenerDelta = 0.2; 19 | 20 | static NSString *const kDepthSpringAnimationKey = @"kDepthSpringAnimationKey"; 21 | 22 | @interface ViewController () 23 | 24 | @end 25 | 26 | @implementation ViewController { 27 | PDPseudo3DTouchGestureRecognizer *_3DTouchGestureRecognizer; 28 | 29 | UIView *_circleView; 30 | 31 | POPSpringAnimation *_depthSpringAnimation; 32 | 33 | BOOL _playedTaptics; 34 | } 35 | 36 | - (void)viewDidLoad { 37 | [super viewDidLoad]; 38 | 39 | // Create our demo circle to force-touch. 40 | _circleView = [[UIView alloc] initWithFrame:CGRectZero]; 41 | _circleView.backgroundColor = [UIColor purpleColor]; 42 | [self.view addSubview:_circleView]; 43 | 44 | // Create our 3D touch gesture recognizer and attach it to the circle view. 45 | _3DTouchGestureRecognizer = [[PDPseudo3DTouchGestureRecognizer alloc] initWithTarget:self action:@selector(_handle3DTouchGestureRecognizer:)]; 46 | [_circleView addGestureRecognizer:_3DTouchGestureRecognizer]; 47 | 48 | // Setup a simple spring animation to make the circle look as if it's bouncing. 49 | _depthSpringAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY]; 50 | _depthSpringAnimation.removedOnCompletion = NO; 51 | _depthSpringAnimation.fromValue = [NSValue valueWithCGPoint:POPLayerGetScaleXY(_circleView.layer)]; 52 | [_circleView.layer pop_addAnimation:_depthSpringAnimation forKey:kDepthSpringAnimationKey]; 53 | } 54 | 55 | #pragma mark - Layout 56 | - (void)viewDidLayoutSubviews 57 | { 58 | [super viewDidLayoutSubviews]; 59 | 60 | CGRect bounds = self.view.bounds; 61 | 62 | _circleView.bounds = CGRectMake(0.0, 63 | 0.0, 64 | kCircleDiameter, 65 | kCircleDiameter); 66 | _circleView.layer.position = CGPointMake(floor(CGRectGetMidX(bounds)), floor(CGRectGetMidY(bounds))); 67 | _circleView.layer.cornerRadius = floor(kCircleDiameter / 2.0); 68 | } 69 | 70 | #pragma mark - Gesture Recognizer Handling 71 | - (void)_handle3DTouchGestureRecognizer:(PDPseudo3DTouchGestureRecognizer *)gestureRecognizer 72 | { 73 | // Grab our depth, and scale it down to a sensible scale for the circle's transform. 74 | // Scales down by 40%. 75 | // Mostly magic numbers, but just creates a nicer scaling effect for the string. 76 | CGFloat depth = gestureRecognizer.depth; 77 | CGFloat scaleDelta = (1.0 - (fabs(1.0 - depth) * kCircleScaleDampenerDelta) - kCircleScaleDampenerDelta); 78 | 79 | CGPoint scale = CGPointMake(scaleDelta, scaleDelta); 80 | 81 | switch (gestureRecognizer.state) { 82 | case UIGestureRecognizerStateBegan: { 83 | _depthSpringAnimation.toValue = [NSValue valueWithCGPoint:scale]; 84 | _playedTaptics = NO; 85 | break; 86 | } 87 | case UIGestureRecognizerStateChanged: { 88 | _depthSpringAnimation.toValue = [NSValue valueWithCGPoint:scale]; 89 | 90 | // Only play taptics once. 91 | if (depth >= 1.2 && !_playedTaptics) { 92 | PDPseudo3DTouchPlayTap(); 93 | _playedTaptics = YES; 94 | } 95 | break; 96 | } 97 | case UIGestureRecognizerStateEnded: 98 | case UIGestureRecognizerStateCancelled: { 99 | _depthSpringAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; 100 | _playedTaptics = NO; 101 | break; 102 | } 103 | 104 | default: 105 | break; 106 | } 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /Pseudo3DTouch/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Pseudo3DTouch 4 | // 5 | // Created by Adam Bell on 9/13/15. 6 | // Copyright © 2015 Adam Bell. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PDPseudo3DTouchGestureRecognizer 2 | 3 | **Disclaimer**: PS don't break your screen pls :) 4 | 5 | A yolo implementation of a 3D gesture recognizer using UITouch's -majorRadius property for comparing the differences in touch radii. 6 | 7 | Definitely not prime for shipping I wrote it just for demo purposes, but it's fun to mess with. Use it to get a feel for how 3D touch will be! :D 8 | 9 | Was built for my presentation at [NSSpain 2015](http://www.nsspain.com), so be sure to check out my talk when it's posted! :D 10 | 11 | Also, it's not super amazingly accurate, but it gets the job done. Helps sometimes to change the angle of your thumb or finger when pressing harder. 12 | 13 | Depth values scale from 1 -> infinity, the scale isn't really unbound. 14 | 15 | Example includes a bouncing circle with pseudo "taptic feedback" based on how hard you press the screen. 16 | 17 | ### How do I compile? 18 | Xcode pretty much. 19 | 20 | ### License? 21 | Pretty much the BSD license, just don't repackage it and call it your own please! 22 | 23 | Also if you do make some changes, feel free to make a pull request and help make things more awesome! 24 | 25 | ### Contact Info? 26 | 27 | Feel free to follow me on twitter: [@b3ll](https://www.twitter.com/b3ll)! 28 | --------------------------------------------------------------------------------