//
|
// YYTextMagnifier.m
|
// YYText <https://github.com/ibireme/YYText>
|
//
|
// 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 "YYTextUtilities.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(YYTextCGRectPixelHalf(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, YYTextCGFloatFromPixel(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 YYTextCGFloatPixelHalf(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, YYTextCGFloatPixelFloor(kPadding) + kHeight);
|
CGPathAddLineToPoint(arrowPath, NULL, size.width / 2 + kArrow, YYTextCGFloatPixelFloor(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, YYTextCGFloatFromPixel(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
|