YYTextMagnifier.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. //
  2. // YYTextMagnifier.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 "YYTextMagnifier.h"
  12. #import "YYCGUtilities.h"
  13. #define kCaptureDisableFadeTime 0.1
  14. @interface _YYTextMagnifierCaret : YYTextMagnifier {
  15. UIImageView *_contentView;
  16. UIImageView *_coverView;
  17. }
  18. @end
  19. @implementation _YYTextMagnifierCaret
  20. #define kMultiple 1.2
  21. #define kDiameter 113.0
  22. #define kPadding 7.0
  23. #define kSize CGSizeMake(kDiameter + kPadding * 2, kDiameter + kPadding * 2)
  24. - (instancetype)initWithFrame:(CGRect)frame {
  25. self = [super initWithFrame:frame];
  26. _contentView = [UIImageView new];
  27. _contentView.frame = CGRectMake(kPadding, kPadding, kDiameter, kDiameter);
  28. _contentView.layer.cornerRadius = kDiameter / 2;
  29. _contentView.clipsToBounds = YES;
  30. [self addSubview:_contentView];
  31. _coverView = [UIImageView new];
  32. _coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize};
  33. _coverView.image = [self.class coverImage];
  34. [self addSubview:_coverView];
  35. return self;
  36. }
  37. - (instancetype)init {
  38. self = [self initWithFrame:CGRectZero];
  39. self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]};
  40. return self;
  41. }
  42. - (YYTextMagnifierType)type {
  43. return YYTextMagnifierTypeCaret;
  44. }
  45. - (CGSize)sizeThatFits:(CGSize)size {
  46. return kSize;
  47. }
  48. - (void)setSnapshot:(UIImage *)snapshot {
  49. if (self.captureFadeAnimation) {
  50. [_contentView.layer removeAnimationForKey:@"contents"];
  51. CABasicAnimation *animation = [CABasicAnimation animation];
  52. animation.duration = kCaptureDisableFadeTime;
  53. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  54. [_contentView.layer addAnimation:animation forKey:@"contents"];
  55. }
  56. _contentView.image = snapshot;
  57. }
  58. - (UIImage *)snapshot {
  59. return _contentView.image;
  60. }
  61. - (CGSize)snapshotSize {
  62. CGFloat length = floor(kDiameter / 1.2);
  63. return CGSizeMake(length, length);
  64. }
  65. - (CGSize)fitSize {
  66. return [self sizeThatFits:CGSizeZero];
  67. }
  68. + (UIImage *)coverImage {
  69. static UIImage *image;
  70. static dispatch_once_t onceToken;
  71. dispatch_once(&onceToken, ^{
  72. CGSize size = kSize;
  73. CGRect rect = (CGRect) {.size = size, .origin = CGPointZero};
  74. rect = CGRectInset(rect, kPadding, kPadding);
  75. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  76. CGContextRef context = UIGraphicsGetCurrentContext();
  77. CGPathRef boxPath = CGPathCreateWithRect(CGRectMake(0, 0, size.width, size.height), NULL);
  78. CGPathRef fillPath = CGPathCreateWithEllipseInRect(rect, NULL);
  79. CGPathRef strokePath = CGPathCreateWithEllipseInRect(CGRectPixelHalf(rect), NULL);
  80. // inner shadow
  81. CGContextSaveGState(context); {
  82. CGFloat blurRadius = 25;
  83. CGSize offset = CGSizeMake(0, 15);
  84. CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor;
  85. CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);
  86. CGContextAddPath(context, fillPath);
  87. CGContextClip(context);
  88. CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
  89. CGContextBeginTransparencyLayer(context, NULL); {
  90. CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
  91. CGContextSetBlendMode(context, kCGBlendModeSourceOut);
  92. CGContextSetFillColorWithColor(context, opaqueShadowColor);
  93. CGContextAddPath(context, fillPath);
  94. CGContextFillPath(context);
  95. } CGContextEndTransparencyLayer(context);
  96. CGColorRelease(opaqueShadowColor);
  97. } CGContextRestoreGState(context);
  98. // outer shadow
  99. CGContextSaveGState(context); {
  100. CGContextAddPath(context, boxPath);
  101. CGContextAddPath(context, fillPath);
  102. CGContextEOClip(context);
  103. CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor;
  104. CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor);
  105. CGContextBeginTransparencyLayer(context, NULL); {
  106. CGContextAddPath(context, fillPath);
  107. [[UIColor colorWithWhite:0.7 alpha:1.000] setFill];
  108. CGContextFillPath(context);
  109. } CGContextEndTransparencyLayer(context);
  110. } CGContextRestoreGState(context);
  111. // stroke
  112. CGContextSaveGState(context); {
  113. CGContextAddPath(context, strokePath);
  114. [[UIColor colorWithWhite:0.6 alpha:1] setStroke];
  115. CGContextSetLineWidth(context, CGFloatFromPixel(1));
  116. CGContextStrokePath(context);
  117. } CGContextRestoreGState(context);
  118. CFRelease(boxPath);
  119. CFRelease(fillPath);
  120. CFRelease(strokePath);
  121. image = UIGraphicsGetImageFromCurrentImageContext();
  122. UIGraphicsEndImageContext();
  123. });
  124. return image;
  125. }
  126. #undef kMultiple
  127. #undef kDiameter
  128. #undef kPadding
  129. #undef kSize
  130. @end
  131. @interface _YYTextMagnifierRanged : YYTextMagnifier {
  132. UIImageView *_contentView;
  133. UIImageView *_coverView;
  134. }
  135. @end
  136. @implementation _YYTextMagnifierRanged
  137. #define kMultiple 1.2
  138. #define kSize CGSizeMake(141, 60)
  139. #define kPadding CGFloatPixelHalf(6.0)
  140. #define kRadius 6.0
  141. #define kHeight 32.0
  142. #define kArrow 14.0
  143. - (instancetype)initWithFrame:(CGRect)frame {
  144. self = [super initWithFrame:frame];
  145. _contentView = [UIImageView new];
  146. _contentView.frame = CGRectMake(kPadding, kPadding, kSize.width - 2 * kPadding, kHeight);
  147. _contentView.layer.cornerRadius = kRadius;
  148. _contentView.clipsToBounds = YES;
  149. [self addSubview:_contentView];
  150. _coverView = [UIImageView new];
  151. _coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize};
  152. _coverView.image = [self.class coverImage];
  153. [self addSubview:_coverView];
  154. self.layer.anchorPoint = CGPointMake(0.5, 1.2);
  155. return self;
  156. }
  157. - (instancetype)init {
  158. self = [self initWithFrame:CGRectZero];
  159. self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]};
  160. return self;
  161. }
  162. - (YYTextMagnifierType)type {
  163. return YYTextMagnifierTypeRanged;
  164. }
  165. - (CGSize)sizeThatFits:(CGSize)size {
  166. return kSize;
  167. }
  168. - (void)setSnapshot:(UIImage *)snapshot {
  169. if (self.captureFadeAnimation) {
  170. [_contentView.layer removeAnimationForKey:@"contents"];
  171. CABasicAnimation *animation = [CABasicAnimation animation];
  172. animation.duration = kCaptureDisableFadeTime;
  173. animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  174. [_contentView.layer addAnimation:animation forKey:@"contents"];
  175. }
  176. _contentView.image = snapshot;
  177. }
  178. - (UIImage *)snapshot {
  179. return _contentView.image;
  180. }
  181. - (CGSize)snapshotSize {
  182. CGSize size;
  183. size.width = floor((kSize.width - 2 * kPadding) / kMultiple);
  184. size.height = floor(kHeight / kMultiple);
  185. return size;
  186. }
  187. - (CGSize)fitSize {
  188. return [self sizeThatFits:CGSizeZero];
  189. }
  190. + (UIImage *)coverImage {
  191. static UIImage *image;
  192. static dispatch_once_t onceToken;
  193. dispatch_once(&onceToken, ^{
  194. CGSize size = kSize;
  195. CGRect rect = (CGRect) {.size = size, .origin = CGPointZero};
  196. UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  197. CGContextRef context = UIGraphicsGetCurrentContext();
  198. CGPathRef boxPath = CGPathCreateWithRect(rect, NULL);
  199. CGMutablePathRef path = CGPathCreateMutable();
  200. CGPathMoveToPoint(path, NULL, kPadding + kRadius, kPadding);
  201. CGPathAddLineToPoint(path, NULL, size.width - kPadding - kRadius, kPadding);
  202. CGPathAddQuadCurveToPoint(path, NULL, size.width - kPadding, kPadding, size.width - kPadding, kPadding + kRadius);
  203. CGPathAddLineToPoint(path, NULL, size.width - kPadding, kHeight);
  204. CGPathAddCurveToPoint(path, NULL, size.width - kPadding, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight);
  205. CGPathAddLineToPoint(path, NULL, size.width / 2 + kArrow, kPadding + kHeight);
  206. CGPathAddLineToPoint(path, NULL, size.width / 2, kPadding + kHeight + kArrow);
  207. CGPathAddLineToPoint(path, NULL, size.width / 2 - kArrow, kPadding + kHeight);
  208. CGPathAddLineToPoint(path, NULL, kPadding + kRadius, kPadding + kHeight);
  209. CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding + kHeight, kPadding, kHeight);
  210. CGPathAddLineToPoint(path, NULL, kPadding, kPadding + kRadius);
  211. CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding, kPadding + kRadius, kPadding);
  212. CGPathCloseSubpath(path);
  213. CGMutablePathRef arrowPath = CGPathCreateMutable();
  214. CGPathMoveToPoint(arrowPath, NULL, size.width / 2 - kArrow, CGFloatPixelFloor(kPadding) + kHeight);
  215. CGPathAddLineToPoint(arrowPath, NULL, size.width / 2 + kArrow, CGFloatPixelFloor(kPadding) + kHeight);
  216. CGPathAddLineToPoint(arrowPath, NULL, size.width / 2, kPadding + kHeight + kArrow);
  217. CGPathCloseSubpath(arrowPath);
  218. // inner shadow
  219. CGContextSaveGState(context); {
  220. CGFloat blurRadius = 25;
  221. CGSize offset = CGSizeMake(0, 15);
  222. CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor;
  223. CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);
  224. CGContextAddPath(context, path);
  225. CGContextClip(context);
  226. CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
  227. CGContextBeginTransparencyLayer(context, NULL); {
  228. CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
  229. CGContextSetBlendMode(context, kCGBlendModeSourceOut);
  230. CGContextSetFillColorWithColor(context, opaqueShadowColor);
  231. CGContextAddPath(context, path);
  232. CGContextFillPath(context);
  233. } CGContextEndTransparencyLayer(context);
  234. CGColorRelease(opaqueShadowColor);
  235. } CGContextRestoreGState(context);
  236. // outer shadow
  237. CGContextSaveGState(context); {
  238. CGContextAddPath(context, boxPath);
  239. CGContextAddPath(context, path);
  240. CGContextEOClip(context);
  241. CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor;
  242. CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor);
  243. CGContextBeginTransparencyLayer(context, NULL); {
  244. CGContextAddPath(context, path);
  245. [[UIColor colorWithWhite:0.7 alpha:1.000] setFill];
  246. CGContextFillPath(context);
  247. } CGContextEndTransparencyLayer(context);
  248. } CGContextRestoreGState(context);
  249. // arrow
  250. CGContextSaveGState(context); {
  251. CGContextAddPath(context, arrowPath);
  252. [[UIColor colorWithWhite:1 alpha:0.95] set];
  253. CGContextFillPath(context);
  254. } CGContextRestoreGState(context);
  255. // stroke
  256. CGContextSaveGState(context); {
  257. CGContextAddPath(context, path);
  258. [[UIColor colorWithWhite:0.6 alpha:1] setStroke];
  259. CGContextSetLineWidth(context, CGFloatFromPixel(1));
  260. CGContextStrokePath(context);
  261. } CGContextRestoreGState(context);
  262. CFRelease(boxPath);
  263. CFRelease(path);
  264. CFRelease(arrowPath);
  265. image = UIGraphicsGetImageFromCurrentImageContext();
  266. UIGraphicsEndImageContext();
  267. });
  268. return image;
  269. }
  270. #undef kMultiple
  271. #undef kSize
  272. #undef kPadding
  273. #undef kRadius
  274. #undef kHeight
  275. #undef kArrow
  276. @end
  277. @implementation YYTextMagnifier
  278. + (id)magnifierWithType:(YYTextMagnifierType)type {
  279. switch (type) {
  280. case YYTextMagnifierTypeCaret :return [_YYTextMagnifierCaret new];
  281. case YYTextMagnifierTypeRanged :return [_YYTextMagnifierRanged new];
  282. }
  283. return nil;
  284. }
  285. - (id)initWithFrame:(CGRect)frame {
  286. // class cluster
  287. if ([self isMemberOfClass:[YYTextMagnifier class]]) {
  288. @throw [NSException exceptionWithName:NSStringFromClass([self class]) reason:@"Attempting to instantiate an abstract class. Use a concrete subclass instead." userInfo:nil];
  289. return nil;
  290. }
  291. self = [super initWithFrame:frame];
  292. return self;
  293. }
  294. @end