├── Classes ├── UIApplication+TouchHints.h └── UIApplication+TouchHints.m ├── Images └── screencast.gif ├── LICENSE ├── README.md └── Resources ├── touch.png └── touch@2x.png /Classes/UIApplication+TouchHints.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+TouchHints.h 3 | // CocoaTouchPlayground 4 | // 5 | // Created by 杨弘宇 on 16/6/3. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIApplication (TouchHints) 12 | 13 | - (void)tch_enableTouchHintsWithImage:(UIImage *)image; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Classes/UIApplication+TouchHints.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+TouchHints.m 3 | // CocoaTouchPlayground 4 | // 5 | // Created by 杨弘宇 on 16/6/3. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIApplication+TouchHints.h" 11 | 12 | static const char kOverlayWindowKey; 13 | static const char kHintsImageKey; 14 | static const char kTouchesDictKey; 15 | 16 | @implementation UIApplication (TouchHints) 17 | 18 | + (void)_swizzleMethodWithSelector:(SEL)aSelector andSelector:(SEL)anotherSelector { 19 | Class cls = [self class]; 20 | 21 | Method oriMethod = class_getInstanceMethod(cls, aSelector); 22 | Method newMethod = class_getInstanceMethod(cls, anotherSelector); 23 | 24 | BOOL didAddMethod = class_addMethod(cls, aSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); 25 | if (didAddMethod) { 26 | class_replaceMethod(cls, anotherSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); 27 | } 28 | else { 29 | method_exchangeImplementations(oriMethod, newMethod); 30 | } 31 | } 32 | 33 | + (void)load { 34 | [super load]; 35 | 36 | [self _swizzleMethodWithSelector:@selector(sendEvent:) andSelector:@selector(tch_sendEvent:)]; 37 | } 38 | 39 | - (NSString *)_stringFromPointer:(id)pointer { 40 | return [NSString stringWithFormat:@"%ld", (long) pointer]; 41 | } 42 | 43 | - (UIWindow *)_overlayWindow { 44 | id overlayWindow = objc_getAssociatedObject(self, &kOverlayWindowKey); 45 | 46 | if ([overlayWindow isKindOfClass:[UIWindow class]]) { 47 | return overlayWindow; 48 | } 49 | 50 | return nil; 51 | } 52 | 53 | - (UIImage *)_hintsImage { 54 | id hintsImage = objc_getAssociatedObject(self, &kHintsImageKey); 55 | 56 | if ([hintsImage isKindOfClass:[UIImage class]]) { 57 | return hintsImage; 58 | } 59 | 60 | return nil; 61 | } 62 | 63 | - (NSMutableDictionary *)_touchesDict { 64 | id touchesDict = objc_getAssociatedObject(self, &kTouchesDictKey); 65 | 66 | if ([touchesDict isKindOfClass:[NSMutableDictionary class]]) { 67 | return touchesDict; 68 | } 69 | 70 | return nil; 71 | } 72 | 73 | - (CGRect)_frameForTouch:(UITouch *)touch { 74 | CGPoint loc = [touch locationInView:[self _overlayWindow]]; 75 | return CGRectMake(loc.x - 32, loc.y - 32, 64, 64); 76 | } 77 | 78 | - (void)_createAndShowTouch:(UITouch *)touch { 79 | CALayer *layer = [CALayer layer]; 80 | layer.frame = [self _frameForTouch:touch]; 81 | layer.contents = (id) [self _hintsImage].CGImage; 82 | 83 | [[self _touchesDict] setObject:layer forKey:[self _stringFromPointer:touch]]; 84 | [[self _overlayWindow].rootViewController.view.layer addSublayer:layer]; 85 | } 86 | 87 | - (void)_moveTouch:(UITouch *)touch { 88 | CALayer *layer = [[self _touchesDict] objectForKey:[self _stringFromPointer:touch]]; 89 | 90 | if (!layer) { 91 | return; 92 | } 93 | 94 | [CATransaction begin]; 95 | [CATransaction setDisableActions:YES]; 96 | layer.frame = layer.frame = [self _frameForTouch:touch]; 97 | [CATransaction commit]; 98 | } 99 | 100 | - (void)_hideAndReleaseTouch:(UITouch *)touch { 101 | NSString *pointerString = [self _stringFromPointer:touch]; 102 | CALayer *layer = [[self _touchesDict] objectForKey:pointerString]; 103 | 104 | if (!layer) { 105 | return; 106 | } 107 | 108 | [CATransaction begin]; 109 | [CATransaction setAnimationDuration:0.4]; 110 | layer.opacity = 0; 111 | layer.transform = CATransform3DMakeScale(0.92, 0.92, 1); 112 | [CATransaction commit]; 113 | 114 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(400 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{ 115 | [[self _touchesDict] removeObjectForKey:pointerString]; 116 | [layer removeFromSuperlayer]; 117 | }); 118 | } 119 | 120 | - (void)_clearInvalidTouches:(NSSet *)liveTouches { 121 | NSMutableDictionary *dict = [self _touchesDict]; 122 | NSMutableArray *liveTouchStrings = [NSMutableArray array]; 123 | [liveTouches enumerateObjectsUsingBlock:^(UITouch * _Nonnull obj, BOOL * _Nonnull stop) { 124 | [liveTouchStrings addObject:[self _stringFromPointer:obj]]; 125 | }]; 126 | 127 | NSArray *touchKeys = dict.allKeys; 128 | [touchKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 129 | if (![liveTouchStrings containsObject:obj]) { 130 | [self _hideAndReleaseTouch:(id) obj]; 131 | } 132 | }]; 133 | 134 | NSArray *touchLayers = dict.allValues; 135 | [[[self _overlayWindow].rootViewController.view.layer.sublayers copy] enumerateObjectsUsingBlock:^(CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 136 | if (![touchLayers containsObject:obj]) { 137 | [obj removeFromSuperlayer]; 138 | } 139 | }]; 140 | } 141 | 142 | - (void)tch_enableTouchHintsWithImage:(UIImage *)image { 143 | static dispatch_once_t onceToken; 144 | dispatch_once(&onceToken, ^{ 145 | objc_setAssociatedObject(self, &kHintsImageKey, image, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 146 | 147 | NSMutableDictionary *touches = [NSMutableDictionary dictionary]; 148 | objc_setAssociatedObject(self, &kTouchesDictKey, touches, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 149 | 150 | UIWindow *overlayWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 151 | overlayWindow.userInteractionEnabled = NO; 152 | objc_setAssociatedObject(self, &kOverlayWindowKey, overlayWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 153 | 154 | UIViewController *viewController = [[UIViewController alloc] init]; 155 | viewController.view.userInteractionEnabled = NO; 156 | 157 | overlayWindow.rootViewController = viewController; 158 | [overlayWindow makeKeyAndVisible]; 159 | }); 160 | } 161 | 162 | - (void)tch_sendEvent:(UIEvent *)event { 163 | if (event.type == UIEventTypeTouches) { 164 | [event.allTouches enumerateObjectsUsingBlock:^(UITouch * _Nonnull obj, BOOL * _Nonnull stop) { 165 | if (obj.phase == UITouchPhaseBegan) { 166 | [self _createAndShowTouch:obj]; 167 | } 168 | else if (obj.phase == UITouchPhaseMoved || obj.phase == UITouchPhaseStationary) { 169 | [self _moveTouch:obj]; 170 | } 171 | else if (obj.phase == UITouchPhaseEnded || obj.phase == UITouchPhaseCancelled) { 172 | [self _hideAndReleaseTouch:obj]; 173 | } 174 | [self _clearInvalidTouches:event.allTouches]; 175 | }]; 176 | } 177 | 178 | [self tch_sendEvent:event]; 179 | } 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /Images/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/CVTouchHints/296db4dff4d58052c9d154a8a8b773c2f7edeed4/Images/screencast.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cyandev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVTouchHints 2 | 3 | Add touch hints to your screencasts in a **super easy way**. 4 | 5 | Since we know, in Android's developer settings, we can toggle on a option that while we are touching the screen, system will display a cute dot to indicate the location we touch. But upsetly, iOS doesn't provide such feature. And now, with this library, you can do it even better! 6 | 7 | ## Screencast 8 | ![](https://github.com/unixzii/CVTouchHints/raw/master/Images/screencast.gif) 9 | 10 | ## Features 11 | * Supports multitouch. 12 | * Supports customized hint image. 13 | 14 | ## Usage 15 | **Step 0.** Before using it, don't forget to make a hint image. Of course, I had included my crafty image in this repo (see `Resources` directory), you can directly use it. 16 | 17 | > Attention: The image should be **64px * 64px**. 18 | 19 | **Step 1.** Drop the `UIApplication+TouchHints.m` and `UIApplication+TouchHints.h` to your project. 20 | 21 | **Step 2.** Add below line of code to `AppDelegate.m`: 22 | ```objective-c 23 | #import "UIApplication+TouchHints.h" 24 | ``` 25 | 26 | **Step 3.** Call `tch_enableTouchHintsWithImage:` in your delegate method, just like this: 27 | ```objective-c 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 29 | // Override point for customization after application launch. 30 | 31 | // ... 32 | [[UIApplication sharedApplication] tch_enableTouchHintsWithImage:[UIImage imageNamed:@"touch"]]; 33 | 34 | return YES; 35 | } 36 | ``` 37 | The only argument you need to pass is the `UIImage` you want to use. 38 | 39 | OK, just start your wonderful demonstration! 40 | 41 | ## License 42 | The project is available under the MIT license. See the LICENSE file for more info. 43 | -------------------------------------------------------------------------------- /Resources/touch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/CVTouchHints/296db4dff4d58052c9d154a8a8b773c2f7edeed4/Resources/touch.png -------------------------------------------------------------------------------- /Resources/touch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/CVTouchHints/296db4dff4d58052c9d154a8a8b773c2f7edeed4/Resources/touch@2x.png --------------------------------------------------------------------------------