YYTextEffectWindow.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. //
  2. // YYTextEffectWindow.m
  3. // YYKit <https://github.com/ibireme/YYKit>
  4. //
  5. // Created by ibireme on 15/2/25.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYTextEffectWindow.h"
  12. #import "YYTextKeyboardManager.h"
  13. #import "YYKitMacro.h"
  14. #import "YYCGUtilities.h"
  15. #import "UIView+YYAdd.h"
  16. #import "UIApplication+YYAdd.h"
  17. @implementation YYTextEffectWindow
  18. + (instancetype)sharedWindow {
  19. static YYTextEffectWindow *one = nil;
  20. if (one == nil) {
  21. // iOS 9 compatible
  22. NSString *mode = [NSRunLoop currentRunLoop].currentMode;
  23. if (mode.length == 27 &&
  24. [mode hasPrefix:@"UI"] &&
  25. [mode hasSuffix:@"InitializationRunLoopMode"]) {
  26. return nil;
  27. }
  28. }
  29. static dispatch_once_t onceToken;
  30. dispatch_once(&onceToken, ^{
  31. if (![UIApplication isAppExtension]) {
  32. one = [self new];
  33. one.frame = (CGRect){.size = kScreenSize};
  34. one.userInteractionEnabled = NO;
  35. one.windowLevel = UIWindowLevelStatusBar + 1;
  36. one.hidden = NO;
  37. // for iOS 9:
  38. one.opaque = NO;
  39. one.backgroundColor = [UIColor clearColor];
  40. one.layer.backgroundColor = [UIColor clearColor].CGColor;
  41. }
  42. });
  43. return one;
  44. }
  45. // stop self from becoming the KeyWindow
  46. - (void)becomeKeyWindow {
  47. [[[UIApplication sharedExtensionApplication].delegate window] makeKeyWindow];
  48. }
  49. - (UIViewController *)rootViewController {
  50. for (UIWindow *window in [[UIApplication sharedExtensionApplication] windows]) {
  51. if (self == window) continue;
  52. if (window.hidden) continue;
  53. UIViewController *topViewController = window.rootViewController;
  54. if (topViewController) return topViewController;
  55. }
  56. UIViewController *viewController = [super rootViewController];
  57. if (!viewController) {
  58. viewController = [UIViewController new];
  59. [super setRootViewController:viewController];
  60. }
  61. return viewController;
  62. }
  63. // Bring self to front
  64. - (void)_updateWindowLevel {
  65. UIApplication *app = [UIApplication sharedExtensionApplication];
  66. if (!app) return;
  67. UIWindow *top = app.windows.lastObject;
  68. UIWindow *key = app.keyWindow;
  69. if (key && key.windowLevel > top.windowLevel) top = key;
  70. if (top == self) return;
  71. self.windowLevel = top.windowLevel + 1;
  72. }
  73. - (YYTextDirection)_keyboardDirection {
  74. CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
  75. keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
  76. if (CGRectIsNull(keyboardFrame) || CGRectIsEmpty(keyboardFrame)) return YYTextDirectionNone;
  77. if (CGRectGetMinY(keyboardFrame) == 0 &&
  78. CGRectGetMinX(keyboardFrame) == 0 &&
  79. CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
  80. return YYTextDirectionTop;
  81. if (CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame) &&
  82. CGRectGetMinY(keyboardFrame) == 0 &&
  83. CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
  84. return YYTextDirectionRight;
  85. if (CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame) &&
  86. CGRectGetMinX(keyboardFrame) == 0 &&
  87. CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
  88. return YYTextDirectionBottom;
  89. if (CGRectGetMinX(keyboardFrame) == 0 &&
  90. CGRectGetMinY(keyboardFrame) == 0 &&
  91. CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
  92. return YYTextDirectionLeft;
  93. return YYTextDirectionNone;
  94. }
  95. - (CGPoint)_correctedCaptureCenter:(CGPoint)center{
  96. CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
  97. keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
  98. if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
  99. YYTextDirection direction = [self _keyboardDirection];
  100. switch (direction) {
  101. case YYTextDirectionTop: {
  102. if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
  103. } break;
  104. case YYTextDirectionRight: {
  105. if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
  106. } break;
  107. case YYTextDirectionBottom: {
  108. if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
  109. } break;
  110. case YYTextDirectionLeft: {
  111. if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
  112. } break;
  113. default: break;
  114. }
  115. }
  116. return center;
  117. }
  118. - (CGPoint)_correctedCenter:(CGPoint)center forMagnifier:(YYTextMagnifier *)mag rotation:(CGFloat)rotation {
  119. CGFloat degree = RadiansToDegrees(rotation);
  120. degree /= 45.0;
  121. if (degree < 0) degree += (int)(-degree/8.0 + 1) * 8;
  122. if (degree > 8) degree -= (int)(degree/8.0) * 8;
  123. CGFloat caretExt = 10;
  124. if (degree <= 1 || degree >= 7) { //top
  125. if (mag.type == YYTextMagnifierTypeCaret) {
  126. if (center.y < caretExt)
  127. center.y = caretExt;
  128. } else if (mag.type == YYTextMagnifierTypeRanged) {
  129. if (center.y < mag.bounds.size.height)
  130. center.y = mag.bounds.size.height;
  131. }
  132. } else if (1 < degree && degree < 3) { // right
  133. if (mag.type == YYTextMagnifierTypeCaret) {
  134. if (center.x > self.bounds.size.width - caretExt)
  135. center.x = self.bounds.size.width - caretExt;
  136. } else if (mag.type == YYTextMagnifierTypeRanged) {
  137. if (center.x > self.bounds.size.width - mag.bounds.size.height)
  138. center.x = self.bounds.size.width - mag.bounds.size.height;
  139. }
  140. } else if (3 <= degree && degree <= 5) { // bottom
  141. if (mag.type == YYTextMagnifierTypeCaret) {
  142. if (center.y > self.bounds.size.height - caretExt)
  143. center.y = self.bounds.size.height - caretExt;
  144. } else if (mag.type == YYTextMagnifierTypeRanged) {
  145. if (center.y > mag.bounds.size.height)
  146. center.y = mag.bounds.size.height;
  147. }
  148. } else if (5 < degree && degree < 7) { // left
  149. if (mag.type == YYTextMagnifierTypeCaret) {
  150. if (center.x < caretExt)
  151. center.x = caretExt;
  152. } else if (mag.type == YYTextMagnifierTypeRanged) {
  153. if (center.x < mag.bounds.size.height)
  154. center.x = mag.bounds.size.height;
  155. }
  156. }
  157. CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
  158. keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
  159. if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
  160. YYTextDirection direction = [self _keyboardDirection];
  161. switch (direction) {
  162. case YYTextDirectionTop: {
  163. if (mag.type == YYTextMagnifierTypeCaret) {
  164. if (center.y - mag.bounds.size.height / 2 < CGRectGetMaxY(keyboardFrame))
  165. center.y = CGRectGetMaxY(keyboardFrame) + mag.bounds.size.height / 2;
  166. } else if (mag.type == YYTextMagnifierTypeRanged) {
  167. if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
  168. }
  169. } break;
  170. case YYTextDirectionRight: {
  171. if (mag.type == YYTextMagnifierTypeCaret) {
  172. if (center.x + mag.bounds.size.height / 2 > CGRectGetMinX(keyboardFrame))
  173. center.x = CGRectGetMinX(keyboardFrame) - mag.bounds.size.width / 2;
  174. } else if (mag.type == YYTextMagnifierTypeRanged) {
  175. if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
  176. }
  177. } break;
  178. case YYTextDirectionBottom: {
  179. if (mag.type == YYTextMagnifierTypeCaret) {
  180. if (center.y + mag.bounds.size.height / 2 > CGRectGetMinY(keyboardFrame))
  181. center.y = CGRectGetMinY(keyboardFrame) - mag.bounds.size.height / 2;
  182. } else if (mag.type == YYTextMagnifierTypeRanged) {
  183. if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
  184. }
  185. } break;
  186. case YYTextDirectionLeft: {
  187. if (mag.type == YYTextMagnifierTypeCaret) {
  188. if (center.x - mag.bounds.size.height / 2 < CGRectGetMaxX(keyboardFrame))
  189. center.x = CGRectGetMaxX(keyboardFrame) + mag.bounds.size.width / 2;
  190. } else if (mag.type == YYTextMagnifierTypeRanged) {
  191. if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
  192. }
  193. } break;
  194. default: break;
  195. }
  196. }
  197. return center;
  198. }
  199. /**
  200. Capture screen snapshot and set it to magnifier.
  201. @return Magnifier rotation radius.
  202. */
  203. - (CGFloat)_updateMagnifier:(YYTextMagnifier *)mag {
  204. UIApplication *app = [UIApplication sharedExtensionApplication];
  205. if (!app) return 0;
  206. UIView *hostView = mag.hostView;
  207. UIWindow *hostWindow = [hostView isKindOfClass:[UIWindow class]] ? (id)hostView : hostView.window;
  208. if (!hostView || !hostWindow) return 0;
  209. CGPoint captureCenter = [self convertPoint:mag.hostCaptureCenter fromViewOrWindow:hostView];
  210. captureCenter = [self _correctedCaptureCenter:captureCenter];
  211. CGRect captureRect = {.size = mag.snapshotSize};
  212. captureRect.origin.x = captureCenter.x - captureRect.size.width / 2;
  213. captureRect.origin.y = captureCenter.y - captureRect.size.height / 2;
  214. CGAffineTransform trans = YYCGAffineTransformGetFromViews(hostView, self);
  215. CGFloat rotation = CGAffineTransformGetRotation(trans);
  216. if (mag.captureDisabled) {
  217. if (!mag.snapshot || mag.snapshot.size.width > 1) {
  218. static UIImage *placeholder;
  219. static dispatch_once_t onceToken;
  220. dispatch_once(&onceToken, ^{
  221. CGRect rect = CGRectMake(0, 0, mag.width, mag.height);
  222. UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
  223. CGContextRef context = UIGraphicsGetCurrentContext();
  224. [[UIColor colorWithWhite:1 alpha:0.8] set];
  225. CGContextFillRect(context, rect);
  226. placeholder = UIGraphicsGetImageFromCurrentImageContext();
  227. UIGraphicsEndImageContext();
  228. });
  229. mag.captureFadeAnimation = YES;
  230. mag.snapshot = placeholder;
  231. mag.captureFadeAnimation = NO;
  232. }
  233. return rotation;
  234. }
  235. UIGraphicsBeginImageContextWithOptions(captureRect.size, NO, 0);
  236. CGContextRef context = UIGraphicsGetCurrentContext();
  237. if (!context) return rotation;
  238. CGPoint tp = CGPointMake(captureRect.size.width / 2, captureRect.size.height / 2);
  239. tp = CGPointApplyAffineTransform(tp, CGAffineTransformMakeRotation(rotation));
  240. CGContextRotateCTM(context, -rotation);
  241. CGContextTranslateCTM(context, tp.x - captureCenter.x, tp.y - captureCenter.y);
  242. NSMutableArray *windows = app.windows.mutableCopy;
  243. UIWindow *keyWindow = app.keyWindow;
  244. if (![windows containsObject:keyWindow]) [windows addObject:keyWindow];
  245. [windows sortUsingComparator:^NSComparisonResult(UIWindow *w1, UIWindow *w2) {
  246. if (w1.windowLevel < w2.windowLevel) return NSOrderedAscending;
  247. else if (w1.windowLevel > w2.windowLevel) return NSOrderedDescending;
  248. return NSOrderedSame;
  249. }];
  250. UIScreen *mainScreen = [UIScreen mainScreen];
  251. for (UIWindow *window in windows) {
  252. if (window.hidden || window.alpha <= 0.01) continue;
  253. if (window.screen != mainScreen) continue;
  254. if ([window isKindOfClass:self.class]) break; //don't capture window above self
  255. CGContextSaveGState(context);
  256. CGContextConcatCTM(context, YYCGAffineTransformGetFromViews(window, self));
  257. [window.layer renderInContext:context]; //render
  258. //[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO]; //slower when capture whole window
  259. CGContextRestoreGState(context);
  260. }
  261. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  262. UIGraphicsEndImageContext();
  263. if (mag.snapshot.size.width == 1) {
  264. mag.captureFadeAnimation = YES;
  265. }
  266. mag.snapshot = image;
  267. mag.captureFadeAnimation = NO;
  268. return rotation;
  269. }
  270. - (void)showMagnifier:(YYTextMagnifier *)mag {
  271. if (!mag) return;
  272. if (mag.superview != self) [self addSubview:mag];
  273. [self _updateWindowLevel];
  274. CGFloat rotation = [self _updateMagnifier:mag];
  275. CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
  276. CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
  277. trans = CGAffineTransformScale(trans, 0.3, 0.3);
  278. mag.transform = trans;
  279. mag.center = center;
  280. if (mag.type == YYTextMagnifierTypeRanged) {
  281. mag.alpha = 0;
  282. }
  283. NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.08 : 0.1;
  284. [UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
  285. if (mag.type == YYTextMagnifierTypeCaret) {
  286. CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
  287. newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
  288. newCenter.x += center.x;
  289. newCenter.y += center.y;
  290. mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
  291. } else {
  292. mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
  293. }
  294. mag.transform = CGAffineTransformMakeRotation(rotation);
  295. mag.alpha = 1;
  296. } completion:^(BOOL finished) {
  297. }];
  298. }
  299. - (void)moveMagnifier:(YYTextMagnifier *)mag {
  300. if (!mag) return;
  301. [self _updateWindowLevel];
  302. CGFloat rotation = [self _updateMagnifier:mag];
  303. CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
  304. if (mag.type == YYTextMagnifierTypeCaret) {
  305. CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
  306. newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
  307. newCenter.x += center.x;
  308. newCenter.y += center.y;
  309. mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
  310. } else {
  311. mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
  312. }
  313. mag.transform = CGAffineTransformMakeRotation(rotation);
  314. }
  315. - (void)hideMagnifier:(YYTextMagnifier *)mag {
  316. if (!mag) return;
  317. if (mag.superview != self) return;
  318. CGFloat rotation = [self _updateMagnifier:mag];
  319. CGPoint center = [self convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
  320. NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.20 : 0.15;
  321. [UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
  322. CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
  323. trans = CGAffineTransformScale(trans, 0.01, 0.01);
  324. mag.transform = trans;
  325. if (mag.type == YYTextMagnifierTypeCaret) {
  326. CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
  327. newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
  328. newCenter.x += center.x;
  329. newCenter.y += center.y;
  330. mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
  331. } else {
  332. mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
  333. mag.alpha = 0;
  334. }
  335. } completion:^(BOOL finished) {
  336. if (finished) {
  337. [mag removeFromSuperview];
  338. mag.transform = CGAffineTransformIdentity;
  339. mag.alpha = 1;
  340. }
  341. }];
  342. }
  343. - (void)_updateSelectionGrabberDot:(YYSelectionGrabberDot *)dot selection:(YYTextSelectionView *)selection{
  344. dot.mirror.hidden = YES;
  345. if (selection.hostView.clipsToBounds == YES && dot.visibleAlpha > 0.1) {
  346. CGRect dotRect = [dot convertRect:dot.bounds toViewOrWindow:self];
  347. BOOL dotInKeyboard = NO;
  348. CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
  349. keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
  350. if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
  351. CGRect inter = CGRectIntersection(dotRect, keyboardFrame);
  352. if (!CGRectIsNull(inter) && (inter.size.width > 1 || inter.size.height > 1)) {
  353. dotInKeyboard = YES;
  354. }
  355. }
  356. if (!dotInKeyboard) {
  357. CGRect hostRect = [selection.hostView convertRect:selection.hostView.bounds toView:self];
  358. CGRect intersection = CGRectIntersection(dotRect, hostRect);
  359. if (CGRectGetArea(intersection) < CGRectGetArea(dotRect)) {
  360. CGFloat dist = CGPointGetDistanceToRect(CGRectGetCenter(dotRect), hostRect);
  361. if (dist < CGRectGetWidth(dot.frame) * 0.55) {
  362. dot.mirror.hidden = NO;
  363. }
  364. }
  365. }
  366. }
  367. CGPoint center = [dot convertPoint:CGPointMake(CGRectGetWidth(dot.frame) / 2, CGRectGetHeight(dot.frame) / 2) toViewOrWindow:self];
  368. if (isnan(center.x) || isnan(center.y) || isinf(center.x) || isinf(center.y)) {
  369. dot.mirror.hidden = YES;
  370. } else {
  371. dot.mirror.center = center;
  372. }
  373. }
  374. - (void)showSelectionDot:(YYTextSelectionView *)selection {
  375. if (!selection) return;
  376. [self _updateWindowLevel];
  377. [self insertSubview:selection.startGrabber.dot.mirror atIndex:0];
  378. [self insertSubview:selection.endGrabber.dot.mirror atIndex:0];
  379. [self _updateSelectionGrabberDot:selection.startGrabber.dot selection:selection];
  380. [self _updateSelectionGrabberDot:selection.endGrabber.dot selection:selection];
  381. }
  382. - (void)hideSelectionDot:(YYTextSelectionView *)selection {
  383. if (!selection) return;
  384. [selection.startGrabber.dot.mirror removeFromSuperview];
  385. [selection.endGrabber.dot.mirror removeFromSuperview];
  386. }
  387. @end