#import "TGTooltipView.h" #import "LegacyComponentsInternal.h" #import "TGFont.h" #import "TGImageUtils.h" #import @interface TGTooltipView () { UIImageView *_backgroundView; UIImageView *_arrowView; UILabel *_textLabel; CGFloat _arrowLocation; } @property (nonatomic, strong) ASHandle *watcherHandle; @end @implementation TGTooltipView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.alpha = 0.0f; self.transform = CGAffineTransformMakeScale(0.1f, 0.1f); _numberOfLines = 1; _maxWidth = 320.0f; _backgroundView = [[UIImageView alloc] initWithImage:[TGTintedImage(TGComponentsImageNamed(@"TooltipBackground"), UIColorRGBA(0x252525, 0.96f)) stretchableImageWithLeftCapWidth:9.0f topCapHeight:9.0f]]; [self addSubview:_backgroundView]; _arrowView = [[UIImageView alloc] initWithImage:TGTintedImage(TGComponentsImageNamed(@"TooltipArrow"), UIColorRGBA(0x252525, 0.96f))]; [self addSubview:_arrowView]; _arrowLocation = 50; } return self; } - (void)setForceArrowOnTop:(bool)forceArrowOnTop { _forceArrowOnTop = forceArrowOnTop; _arrowView.image = [UIImage imageWithCGImage:_arrowView.image.CGImage scale:_arrowView.image.scale orientation:UIImageOrientationDown]; } - (void)setText:(NSString *)text { [self setText:text animated:false]; } - (void)setText:(NSString *)text animated:(bool)animated { if (_textLabel == nil) { _textLabel = [[UILabel alloc] init]; _textLabel.backgroundColor = [UIColor clearColor]; _textLabel.font = TGSystemFontOfSize(14); _textLabel.textColor = [UIColor whiteColor]; _textLabel.userInteractionEnabled = false; _textLabel.textAlignment = NSTextAlignmentCenter; [self addSubview:_textLabel]; } _textLabel.numberOfLines = _numberOfLines; if (animated) { [UIView transitionWithView:_textLabel duration:0.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^ { _textLabel.text = text; } completion:nil]; [UIView animateWithDuration:0.2 animations:^ { [self sizeToFit]; } completion:^(__unused BOOL finished) { }]; } else { _textLabel.text = text; if (_numberOfLines == 1) { [_textLabel sizeToFit]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGSize textSize = [_textLabel.text sizeWithFont:_textLabel.font constrainedToSize:CGSizeMake(_maxWidth - 20.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping]; #pragma clang diagnostic pop textSize.width = CGCeil(textSize.width); textSize.height = CGCeil(textSize.height); _textLabel.frame = CGRectMake(_textLabel.frame.origin.x, _textLabel.frame.origin.y, textSize.width, textSize.height); } } } - (void)sizeToFit { CGAffineTransform transform = self.transform; self.transform = CGAffineTransformIdentity; CGFloat maxWidth = _maxWidth; CGFloat inset = 9.0f; if (_numberOfLines == 1) { [_textLabel sizeToFit]; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGSize textSize = [_textLabel.text sizeWithFont:_textLabel.font constrainedToSize:CGSizeMake(maxWidth - 20.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping]; #pragma clang diagnostic pop textSize.width = CGCeil(textSize.width); textSize.height = CGCeil(textSize.height); _textLabel.frame = CGRectMake(_textLabel.frame.origin.x, _textLabel.frame.origin.y, textSize.width, textSize.height); } CGFloat minArrowX = 10.0f; CGFloat maxArrowX = self.frame.size.width - 10.0f; CGFloat arrowX = CGFloor(_arrowLocation - _arrowView.frame.size.width / 2); arrowX = MIN(MAX(minArrowX, arrowX), maxArrowX); _arrowView.frame = CGRectMake(arrowX + TGScreenPixel, _forceArrowOnTop ? 0.0f : 38.0f - TGScreenPixel, _arrowView.frame.size.width, _arrowView.frame.size.height); CGFloat backgroundOffset = 19.0f + _textLabel.frame.size.height - 36.0f; CGFloat backgroundWidth = MIN(maxWidth, _textLabel.frame.size.width + inset * 2.0f); CGFloat labelWidth = backgroundWidth - inset * 2.0f; if (_numberOfLines != 1) { labelWidth = _textLabel.frame.size.width; backgroundOffset += 1.0f; } CGFloat x = arrowX - (backgroundWidth - _arrowView.frame.size.width) / 2.0f; x = MAX(4.0f, MIN(x, self.frame.size.width - backgroundWidth - 4.0f)); _backgroundView.frame = CGRectMake(x, 2.0f - backgroundOffset + (_forceArrowOnTop ? 7.0f : 0.0f), backgroundWidth, 36.0f + backgroundOffset); _textLabel.frame = CGRectMake(_backgroundView.frame.origin.x + inset, 12.0f - TGScreenPixel - backgroundOffset + (_forceArrowOnTop ? 7.0f : 0.0f), labelWidth, _textLabel.frame.size.height); if (_forceArrowOnTop) { _arrowView.frame = CGRectMake(_arrowView.frame.origin.x, _backgroundView.frame.origin.y - _arrowView.frame.size.height, _arrowView.frame.size.width, _arrowView.frame.size.height); } self.transform = transform; } - (void)showInView:(UIView *)view fromRect:(CGRect)rect { [self showInView:view fromRect:rect animated:true]; } - (void)showInView:(UIView *)__unused view fromRect:(CGRect)rect animated:(bool)animated { CGAffineTransform transform = self.transform; self.transform = CGAffineTransformIdentity; CGRect frame = self.frame; if (_forceArrowOnTop) frame.origin.y = rect.origin.y + 24.0f; else frame.origin.y = rect.origin.y - frame.size.height - 14.0f; _arrowLocation = CGFloor(rect.origin.x + rect.size.width / 2) - frame.origin.x; self.frame = frame; [self sizeToFit]; self.transform = transform; self.layer.rasterizationScale = [[UIScreen mainScreen] scale]; self.layer.shouldRasterize = true; self.alpha = 1.0f; if (animated) { [UIView animateWithDuration:0.142 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^ { self.transform = CGAffineTransformMakeScale(1.07f, 1.07f); } completion:^(BOOL finished) { if(finished) { [UIView animateWithDuration:0.08 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { self.transform = CGAffineTransformMakeScale(0.967f, 0.967f); } completion:^(BOOL finished) { if (finished) { [UIView animateWithDuration:0.06 delay:0 options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState animations:^ { self.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { if (finished) { self.layer.shouldRasterize = false; } }]; } }]; } }]; } else { self.transform = CGAffineTransformIdentity; self.alpha = 0.0f; [UIView animateWithDuration:0.3 animations:^ { self.alpha = 1.0f; }]; } } - (void)hide:(dispatch_block_t)completion { [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { self.alpha = 0.0f; } completion:^(BOOL finished) { if (finished) { self.transform = CGAffineTransformMakeScale(0.1f, 0.1f); if (completion) completion(); } }]; } @end #pragma mark - @interface TGTooltipContainerView () { bool _skipHitTest; } @end @implementation TGTooltipContainerView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self != nil) { _tooltipView = [[TGTooltipView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width, 41.0f)]; [self addSubview:_tooltipView]; } return self; } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (_skipHitTest) return nil; UIView *result = [super hitTest:point withEvent:event]; UIView *superViewResult = nil; if (self.tooltipView.sourceView != nil) { _skipHitTest = true; superViewResult = [self.superview hitTest:[self convertPoint:point toView:self.superview] withEvent:event]; _skipHitTest = false; } if (self.tooltipView.sourceView != nil && superViewResult == self.tooltipView.sourceView) return nil; if (result == self || result == nil) { [self hideTooltip]; return nil; } return result; } - (void)showTooltipFromRect:(CGRect)rect { [self showTooltipFromRect:rect animated:true]; } - (void)showTooltipFromRect:(CGRect)rect animated:(bool)animated { _isShowingTooltip = true; _showingTooltipFromRect = rect; [_tooltipView showInView:self fromRect:rect animated:animated]; } - (void)setFrame:(CGRect)frame { if (!CGSizeEqualToSize(frame.size, self.frame.size)) [self hideTooltip]; [super setFrame:frame]; } - (void)hideTooltip { if (_isShowingTooltip) { _isShowingTooltip = false; _showingTooltipFromRect = CGRectZero; [_tooltipView.watcherHandle requestAction:@"tooltipWillHide" options:nil]; [_tooltipView hide:^ { [self removeFromSuperview]; }]; } } @end