123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- //
- // YYTextSelectionView.m
- // YYKit <https://github.com/ibireme/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 "YYTextSelectionView.h"
- #import "YYCGUtilities.h"
- #import "YYWeakProxy.h"
- #define kMarkAlpha 0.2
- #define kLineWidth 2.0
- #define kBlinkDuration 0.5
- #define kBlinkFadeDuration 0.2
- #define kBlinkFirstDelay 0.1
- #define kTouchTestExtend 14.0
- #define kTouchDotExtend 7.0
- @implementation YYSelectionGrabberDot
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (!self) return nil;
- self.userInteractionEnabled = NO;
- self.mirror = [UIView new];
- return self;
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
- CGFloat length = MIN(self.bounds.size.width, self.bounds.size.height);
- self.layer.cornerRadius = length * 0.5;
- self.mirror.bounds = self.bounds;
- self.mirror.layer.cornerRadius = self.layer.cornerRadius;
- }
- - (void)setBackgroundColor:(UIColor *)backgroundColor {
- [super setBackgroundColor:backgroundColor];
- _mirror.backgroundColor = backgroundColor;
- }
- @end
- @implementation YYSelectionGrabber
- - (instancetype) initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (!self) return nil;
- _dot = [[YYSelectionGrabberDot alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
- return self;
- }
- - (void)setDotDirection:(YYTextDirection)dotDirection {
- _dotDirection = dotDirection;
- [self addSubview:_dot];
- CGRect frame = _dot.frame;
- CGFloat ofs = 0.5;
- if (dotDirection == YYTextDirectionTop) {
- frame.origin.y = -frame.size.height + ofs;
- frame.origin.x = (self.bounds.size.width - frame.size.width) / 2;
- } else if (dotDirection == YYTextDirectionRight) {
- frame.origin.x = self.bounds.size.width - ofs;
- frame.origin.y = (self.bounds.size.height - frame.size.height) / 2;
- } else if (dotDirection == YYTextDirectionBottom) {
- frame.origin.y = self.bounds.size.height - ofs;
- frame.origin.x = (self.bounds.size.width - frame.size.width) / 2;
- } else if (dotDirection == YYTextDirectionLeft) {
- frame.origin.x = -frame.size.width + ofs;
- frame.origin.y = (self.bounds.size.height - frame.size.height) / 2;
- } else {
- [_dot removeFromSuperview];
- }
- _dot.frame = frame;
- }
- - (void)setColor:(UIColor *)color {
- self.backgroundColor = color;
- _dot.backgroundColor = color;
- _color = color;
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
- [self setDotDirection:_dotDirection];
- }
- - (CGRect)touchRect {
- CGRect rect = CGRectInset(self.frame, -kTouchTestExtend, -kTouchTestExtend);
- UIEdgeInsets insets = {0};
- if (_dotDirection == YYTextDirectionTop) {
- insets.top = -kTouchDotExtend;
- } else if (_dotDirection == YYTextDirectionRight) {
- insets.right = -kTouchDotExtend;
- } else if (_dotDirection == YYTextDirectionBottom) {
- insets.bottom = -kTouchDotExtend;
- } else if (_dotDirection == YYTextDirectionLeft) {
- insets.left = -kTouchDotExtend;
- }
- rect = UIEdgeInsetsInsetRect(rect, insets);
- return rect;
- }
- @end
- @interface YYTextSelectionView ()
- @property (nonatomic, strong) NSTimer *caretTimer;
- @property (nonatomic, strong) UIView *caretView;
- @property (nonatomic, strong) YYSelectionGrabber *startGrabber;
- @property (nonatomic, strong) YYSelectionGrabber *endGrabber;
- @property (nonatomic, strong) NSMutableArray *markViews;
- @end
- @implementation YYTextSelectionView
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (!self) return nil;
-
- self.userInteractionEnabled = NO;
- self.clipsToBounds = NO;
- _markViews = [NSMutableArray array];
- _caretView = [UIView new];
- _caretView.hidden = YES;
- _startGrabber = [YYSelectionGrabber new];
- _startGrabber.dotDirection = YYTextDirectionTop;
- _startGrabber.hidden = YES;
- _endGrabber = [YYSelectionGrabber new];
- _endGrabber.dotDirection = YYTextDirectionBottom;
- _endGrabber.hidden = YES;
-
- [self addSubview:_startGrabber];
- [self addSubview:_endGrabber];
- [self addSubview:_caretView];
-
- return self;
- }
- - (void)dealloc {
- [_caretTimer invalidate];
- }
- - (void)setColor:(UIColor *)color {
- _color = color;
- self.caretView.backgroundColor = color;
- self.startGrabber.color = color;
- self.endGrabber.color = color;
- [self.markViews enumerateObjectsUsingBlock: ^(UIView *v, NSUInteger idx, BOOL *stop) {
- v.backgroundColor = color;
- }];
- }
- - (void)setCaretBlinks:(BOOL)caretBlinks {
- if (_caretBlinks != caretBlinks) {
- _caretView.alpha = 1;
- [self.class cancelPreviousPerformRequestsWithTarget:self selector:@selector(_startBlinks) object:nil];
- if (caretBlinks) {
- [self performSelector:@selector(_startBlinks) withObject:nil afterDelay:kBlinkFirstDelay];
- } else {
- [_caretTimer invalidate];
- _caretTimer = nil;
- }
- _caretBlinks = caretBlinks;
- }
- }
- - (void)_startBlinks {
- [_caretTimer invalidate];
- if (_caretVisible) {
- _caretTimer = [NSTimer timerWithTimeInterval:kBlinkDuration target:[YYWeakProxy proxyWithTarget:self] selector:@selector(_doBlink) userInfo:nil repeats:YES];
- [[NSRunLoop currentRunLoop] addTimer:_caretTimer forMode:NSDefaultRunLoopMode];
- } else {
- _caretView.alpha = 1;
- }
- }
- - (void)_doBlink {
- [UIView animateWithDuration:kBlinkFadeDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
- if (_caretView.alpha == 1) _caretView.alpha = 0;
- else _caretView.alpha = 1;
- } completion:NULL];
- }
- - (void)setCaretVisible:(BOOL)caretVisible {
- _caretVisible = caretVisible;
- self.caretView.hidden = !caretVisible;
- _caretView.alpha = 1;
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_startBlinks) object:nil];
- if (_caretBlinks) {
- [self performSelector:@selector(_startBlinks) withObject:nil afterDelay:kBlinkFirstDelay];
- }
- }
- - (void)setVerticalForm:(BOOL)verticalForm {
- if (_verticalForm != verticalForm) {
- _verticalForm = verticalForm;
- [self setCaretRect:_caretRect];
- self.startGrabber.dotDirection = verticalForm ? YYTextDirectionRight : YYTextDirectionTop;
- self.endGrabber.dotDirection = verticalForm ? YYTextDirectionLeft : YYTextDirectionBottom;
- }
- }
- - (CGRect)_standardCaretRect:(CGRect)caretRect {
- caretRect = CGRectStandardize(caretRect);
- if (_verticalForm) {
- if (caretRect.size.height == 0) {
- caretRect.size.height = kLineWidth;
- caretRect.origin.y -= kLineWidth * 0.5;
- }
- if (caretRect.origin.y < 0) {
- caretRect.origin.y = 0;
- } else if (caretRect.origin.y + caretRect.size.height > self.bounds.size.height) {
- caretRect.origin.y = self.bounds.size.height - caretRect.size.height;
- }
- } else {
- if (caretRect.size.width == 0) {
- caretRect.size.width = kLineWidth;
- caretRect.origin.x -= kLineWidth * 0.5;
- }
- if (caretRect.origin.x < 0) {
- caretRect.origin.x = 0;
- } else if (caretRect.origin.x + caretRect.size.width > self.bounds.size.width) {
- caretRect.origin.x = self.bounds.size.width - caretRect.size.width;
- }
- }
- caretRect = CGRectPixelRound(caretRect);
- if (isnan(caretRect.origin.x) || isinf(caretRect.origin.x)) caretRect.origin.x = 0;
- if (isnan(caretRect.origin.y) || isinf(caretRect.origin.y)) caretRect.origin.y = 0;
- if (isnan(caretRect.size.width) || isinf(caretRect.size.width)) caretRect.size.width = 0;
- if (isnan(caretRect.size.height) || isinf(caretRect.size.height)) caretRect.size.height = 0;
- return caretRect;
- }
- - (void)setCaretRect:(CGRect)caretRect {
- _caretRect = caretRect;
- self.caretView.frame = [self _standardCaretRect:caretRect];
- CGFloat minWidth = MIN(self.caretView.bounds.size.width, self.caretView.bounds.size.height);
- self.caretView.layer.cornerRadius = minWidth / 2;
- }
- - (void)setSelectionRects:(NSArray *)selectionRects {
- _selectionRects = selectionRects.copy;
- [self.markViews enumerateObjectsUsingBlock: ^(UIView *v, NSUInteger idx, BOOL *stop) {
- [v removeFromSuperview];
- }];
- [self.markViews removeAllObjects];
- self.startGrabber.hidden = YES;
- self.endGrabber.hidden = YES;
-
- [selectionRects enumerateObjectsUsingBlock: ^(YYTextSelectionRect *r, NSUInteger idx, BOOL *stop) {
- CGRect rect = r.rect;
- rect = CGRectStandardize(rect);
- rect = CGRectPixelRound(rect);
- if (r.containsStart || r.containsEnd) {
- rect = [self _standardCaretRect:rect];
- if (r.containsStart) {
- self.startGrabber.hidden = NO;
- self.startGrabber.frame = rect;
- }
- if (r.containsEnd) {
- self.endGrabber.hidden = NO;
- self.endGrabber.frame = rect;
- }
- } else {
- if (rect.size.width > 0 && rect.size.height > 0) {
- UIView *mark = [[UIView alloc] initWithFrame:rect];
- mark.backgroundColor = _color;
- mark.alpha = kMarkAlpha;
- [self insertSubview:mark atIndex:0];
- [self.markViews addObject:mark];
- }
- }
- }];
- }
- - (BOOL)isGrabberContainsPoint:(CGPoint)point {
- return [self isStartGrabberContainsPoint:point] || [self isEndGrabberContainsPoint:point];
- }
- - (BOOL)isStartGrabberContainsPoint:(CGPoint)point {
- if (_startGrabber.hidden) return NO;
- CGRect startRect = [_startGrabber touchRect];
- CGRect endRect = [_endGrabber touchRect];
- if (CGRectIntersectsRect(startRect, endRect)) {
- CGFloat distStart = CGPointGetDistanceToPoint(point, CGRectGetCenter(startRect));
- CGFloat distEnd = CGPointGetDistanceToPoint(point, CGRectGetCenter(endRect));
- if (distEnd <= distStart) return NO;
- }
- return CGRectContainsPoint(startRect, point);
- }
- - (BOOL)isEndGrabberContainsPoint:(CGPoint)point {
- if (_endGrabber.hidden) return NO;
- CGRect startRect = [_startGrabber touchRect];
- CGRect endRect = [_endGrabber touchRect];
- if (CGRectIntersectsRect(startRect, endRect)) {
- CGFloat distStart = CGPointGetDistanceToPoint(point, CGRectGetCenter(startRect));
- CGFloat distEnd = CGPointGetDistanceToPoint(point, CGRectGetCenter(endRect));
- if (distEnd > distStart) return NO;
- }
- return CGRectContainsPoint(endRect, point);
- }
- - (BOOL)isCaretContainsPoint:(CGPoint)point {
- if (_caretVisible) {
- CGRect rect = CGRectInset(_caretRect, -kTouchTestExtend, -kTouchTestExtend);
- return CGRectContainsPoint(rect, point);
- }
- return NO;
- }
- - (BOOL)isSelectionRectsContainsPoint:(CGPoint)point {
- if (_selectionRects.count == 0) return NO;
- for (YYTextSelectionRect *rect in _selectionRects) {
- if (CGRectContainsPoint(rect.rect, point)) return YES;
- }
- return NO;
- }
- @end
|