// // YYTextMagnifier.m // YYKit // // Created by ibireme on 15/2/25. // Copyright (c) 2015 ibireme. // // This source code is licensed under the MIT-style license found in the // LICENSE file in the root directory of this source tree. // #import "YYTextMagnifier.h" #import "YYCGUtilities.h" #define kCaptureDisableFadeTime 0.1 @interface _YYTextMagnifierCaret : YYTextMagnifier { UIImageView *_contentView; UIImageView *_coverView; } @end @implementation _YYTextMagnifierCaret #define kMultiple 1.2 #define kDiameter 113.0 #define kPadding 7.0 #define kSize CGSizeMake(kDiameter + kPadding * 2, kDiameter + kPadding * 2) - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; _contentView = [UIImageView new]; _contentView.frame = CGRectMake(kPadding, kPadding, kDiameter, kDiameter); _contentView.layer.cornerRadius = kDiameter / 2; _contentView.clipsToBounds = YES; [self addSubview:_contentView]; _coverView = [UIImageView new]; _coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize}; _coverView.image = [self.class coverImage]; [self addSubview:_coverView]; return self; } - (instancetype)init { self = [self initWithFrame:CGRectZero]; self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]}; return self; } - (YYTextMagnifierType)type { return YYTextMagnifierTypeCaret; } - (CGSize)sizeThatFits:(CGSize)size { return kSize; } - (void)setSnapshot:(UIImage *)snapshot { if (self.captureFadeAnimation) { [_contentView.layer removeAnimationForKey:@"contents"]; CABasicAnimation *animation = [CABasicAnimation animation]; animation.duration = kCaptureDisableFadeTime; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; [_contentView.layer addAnimation:animation forKey:@"contents"]; } _contentView.image = snapshot; } - (UIImage *)snapshot { return _contentView.image; } - (CGSize)snapshotSize { CGFloat length = floor(kDiameter / 1.2); return CGSizeMake(length, length); } - (CGSize)fitSize { return [self sizeThatFits:CGSizeZero]; } + (UIImage *)coverImage { static UIImage *image; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CGSize size = kSize; CGRect rect = (CGRect) {.size = size, .origin = CGPointZero}; rect = CGRectInset(rect, kPadding, kPadding); UIGraphicsBeginImageContextWithOptions(size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); CGPathRef boxPath = CGPathCreateWithRect(CGRectMake(0, 0, size.width, size.height), NULL); CGPathRef fillPath = CGPathCreateWithEllipseInRect(rect, NULL); CGPathRef strokePath = CGPathCreateWithEllipseInRect(CGRectPixelHalf(rect), NULL); // inner shadow CGContextSaveGState(context); { CGFloat blurRadius = 25; CGSize offset = CGSizeMake(0, 15); CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor; CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0); CGContextAddPath(context, fillPath); CGContextClip(context); CGContextSetAlpha(context, CGColorGetAlpha(shadowColor)); CGContextBeginTransparencyLayer(context, NULL); { CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor); CGContextSetBlendMode(context, kCGBlendModeSourceOut); CGContextSetFillColorWithColor(context, opaqueShadowColor); CGContextAddPath(context, fillPath); CGContextFillPath(context); } CGContextEndTransparencyLayer(context); CGColorRelease(opaqueShadowColor); } CGContextRestoreGState(context); // outer shadow CGContextSaveGState(context); { CGContextAddPath(context, boxPath); CGContextAddPath(context, fillPath); CGContextEOClip(context); CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor; CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor); CGContextBeginTransparencyLayer(context, NULL); { CGContextAddPath(context, fillPath); [[UIColor colorWithWhite:0.7 alpha:1.000] setFill]; CGContextFillPath(context); } CGContextEndTransparencyLayer(context); } CGContextRestoreGState(context); // stroke CGContextSaveGState(context); { CGContextAddPath(context, strokePath); [[UIColor colorWithWhite:0.6 alpha:1] setStroke]; CGContextSetLineWidth(context, CGFloatFromPixel(1)); CGContextStrokePath(context); } CGContextRestoreGState(context); CFRelease(boxPath); CFRelease(fillPath); CFRelease(strokePath); image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); }); return image; } #undef kMultiple #undef kDiameter #undef kPadding #undef kSize @end @interface _YYTextMagnifierRanged : YYTextMagnifier { UIImageView *_contentView; UIImageView *_coverView; } @end @implementation _YYTextMagnifierRanged #define kMultiple 1.2 #define kSize CGSizeMake(141, 60) #define kPadding CGFloatPixelHalf(6.0) #define kRadius 6.0 #define kHeight 32.0 #define kArrow 14.0 - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; _contentView = [UIImageView new]; _contentView.frame = CGRectMake(kPadding, kPadding, kSize.width - 2 * kPadding, kHeight); _contentView.layer.cornerRadius = kRadius; _contentView.clipsToBounds = YES; [self addSubview:_contentView]; _coverView = [UIImageView new]; _coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize}; _coverView.image = [self.class coverImage]; [self addSubview:_coverView]; self.layer.anchorPoint = CGPointMake(0.5, 1.2); return self; } - (instancetype)init { self = [self initWithFrame:CGRectZero]; self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]}; return self; } - (YYTextMagnifierType)type { return YYTextMagnifierTypeRanged; } - (CGSize)sizeThatFits:(CGSize)size { return kSize; } - (void)setSnapshot:(UIImage *)snapshot { if (self.captureFadeAnimation) { [_contentView.layer removeAnimationForKey:@"contents"]; CABasicAnimation *animation = [CABasicAnimation animation]; animation.duration = kCaptureDisableFadeTime; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; [_contentView.layer addAnimation:animation forKey:@"contents"]; } _contentView.image = snapshot; } - (UIImage *)snapshot { return _contentView.image; } - (CGSize)snapshotSize { CGSize size; size.width = floor((kSize.width - 2 * kPadding) / kMultiple); size.height = floor(kHeight / kMultiple); return size; } - (CGSize)fitSize { return [self sizeThatFits:CGSizeZero]; } + (UIImage *)coverImage { static UIImage *image; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ CGSize size = kSize; CGRect rect = (CGRect) {.size = size, .origin = CGPointZero}; UIGraphicsBeginImageContextWithOptions(size, NO, 0); CGContextRef context = UIGraphicsGetCurrentContext(); CGPathRef boxPath = CGPathCreateWithRect(rect, NULL); CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, kPadding + kRadius, kPadding); CGPathAddLineToPoint(path, NULL, size.width - kPadding - kRadius, kPadding); CGPathAddQuadCurveToPoint(path, NULL, size.width - kPadding, kPadding, size.width - kPadding, kPadding + kRadius); CGPathAddLineToPoint(path, NULL, size.width - kPadding, kHeight); CGPathAddCurveToPoint(path, NULL, size.width - kPadding, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight); CGPathAddLineToPoint(path, NULL, size.width / 2 + kArrow, kPadding + kHeight); CGPathAddLineToPoint(path, NULL, size.width / 2, kPadding + kHeight + kArrow); CGPathAddLineToPoint(path, NULL, size.width / 2 - kArrow, kPadding + kHeight); CGPathAddLineToPoint(path, NULL, kPadding + kRadius, kPadding + kHeight); CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding + kHeight, kPadding, kHeight); CGPathAddLineToPoint(path, NULL, kPadding, kPadding + kRadius); CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding, kPadding + kRadius, kPadding); CGPathCloseSubpath(path); CGMutablePathRef arrowPath = CGPathCreateMutable(); CGPathMoveToPoint(arrowPath, NULL, size.width / 2 - kArrow, CGFloatPixelFloor(kPadding) + kHeight); CGPathAddLineToPoint(arrowPath, NULL, size.width / 2 + kArrow, CGFloatPixelFloor(kPadding) + kHeight); CGPathAddLineToPoint(arrowPath, NULL, size.width / 2, kPadding + kHeight + kArrow); CGPathCloseSubpath(arrowPath); // inner shadow CGContextSaveGState(context); { CGFloat blurRadius = 25; CGSize offset = CGSizeMake(0, 15); CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor; CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0); CGContextAddPath(context, path); CGContextClip(context); CGContextSetAlpha(context, CGColorGetAlpha(shadowColor)); CGContextBeginTransparencyLayer(context, NULL); { CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor); CGContextSetBlendMode(context, kCGBlendModeSourceOut); CGContextSetFillColorWithColor(context, opaqueShadowColor); CGContextAddPath(context, path); CGContextFillPath(context); } CGContextEndTransparencyLayer(context); CGColorRelease(opaqueShadowColor); } CGContextRestoreGState(context); // outer shadow CGContextSaveGState(context); { CGContextAddPath(context, boxPath); CGContextAddPath(context, path); CGContextEOClip(context); CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor; CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor); CGContextBeginTransparencyLayer(context, NULL); { CGContextAddPath(context, path); [[UIColor colorWithWhite:0.7 alpha:1.000] setFill]; CGContextFillPath(context); } CGContextEndTransparencyLayer(context); } CGContextRestoreGState(context); // arrow CGContextSaveGState(context); { CGContextAddPath(context, arrowPath); [[UIColor colorWithWhite:1 alpha:0.95] set]; CGContextFillPath(context); } CGContextRestoreGState(context); // stroke CGContextSaveGState(context); { CGContextAddPath(context, path); [[UIColor colorWithWhite:0.6 alpha:1] setStroke]; CGContextSetLineWidth(context, CGFloatFromPixel(1)); CGContextStrokePath(context); } CGContextRestoreGState(context); CFRelease(boxPath); CFRelease(path); CFRelease(arrowPath); image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); }); return image; } #undef kMultiple #undef kSize #undef kPadding #undef kRadius #undef kHeight #undef kArrow @end @implementation YYTextMagnifier + (id)magnifierWithType:(YYTextMagnifierType)type { switch (type) { case YYTextMagnifierTypeCaret :return [_YYTextMagnifierCaret new]; case YYTextMagnifierTypeRanged :return [_YYTextMagnifierRanged new]; } return nil; } - (id)initWithFrame:(CGRect)frame { // class cluster if ([self isMemberOfClass:[YYTextMagnifier class]]) { @throw [NSException exceptionWithName:NSStringFromClass([self class]) reason:@"Attempting to instantiate an abstract class. Use a concrete subclass instead." userInfo:nil]; return nil; } self = [super initWithFrame:frame]; return self; } @end