| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/credential_provider_extension/ui/tooltip_view.h" |
| |
| #import "base/apple/foundation_util.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| |
| namespace { |
| |
| CGFloat kTooltipTailHeight = 8; |
| CGFloat kTooltipTailWidth = 16; |
| CGFloat kTooltipHorizontalPadding = 16.0f; |
| CGFloat kTooltipVerticalPadding = 10.0f; |
| CGFloat kTooltipCornerRadius = 8.0f; |
| CGFloat kTooltipFadeInTime = 0.2f; |
| |
| } // namespace |
| |
| @implementation TooltipView { |
| __weak UIView* _keyWindow; |
| __weak NSObject* _target; |
| SEL _action; |
| UITapGestureRecognizer* _tapBehindGesture; |
| } |
| |
| static __weak TooltipView* _active; |
| |
| - (instancetype)initWithKeyWindow:(UIView*)keyWindow |
| target:(NSObject*)target |
| action:(SEL)action { |
| self = [super init]; |
| if (self) { |
| _keyWindow = keyWindow; |
| _target = target; |
| _action = action; |
| |
| _tapBehindGesture = |
| [[UITapGestureRecognizer alloc] initWithTarget:self |
| action:@selector(checkTap:)]; |
| [_tapBehindGesture setNumberOfTapsRequired:1]; |
| [_tapBehindGesture setCancelsTouchesInView:NO]; |
| } |
| return self; |
| } |
| |
| - (void)showMessage:(NSString*)message atBottomOf:(UIView*)view { |
| if (_active) { |
| [_active hide]; |
| } |
| _active = self; |
| |
| [_keyWindow addGestureRecognizer:_tapBehindGesture]; |
| |
| CGSize labelSize = [message sizeWithAttributes:@{ |
| NSFontAttributeName : |
| [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline] |
| }]; |
| |
| UILabel* label = [[UILabel alloc] |
| initWithFrame:CGRectMake(kTooltipHorizontalPadding, |
| kTooltipTailHeight + kTooltipVerticalPadding, |
| labelSize.width, labelSize.height)]; |
| label.textAlignment = NSTextAlignmentLeft; |
| label.text = message; |
| label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline]; |
| label.textColor = [UIColor colorNamed:kBackgroundColor]; |
| |
| CGFloat width = labelSize.width + 2 * kTooltipHorizontalPadding; |
| CGFloat height = labelSize.height + 2 * kTooltipVerticalPadding; |
| |
| CGPoint anchor = |
| CGPointMake(view.frame.size.width / 2, view.frame.size.height); |
| anchor = [_keyWindow convertPoint:anchor fromView:view]; |
| |
| self.frame = CGRectMake(0.0, 0.0, width, kTooltipTailHeight + height); |
| self.center = CGPointMake( |
| MIN(_keyWindow.frame.size.width - width / 2 - kTooltipHorizontalPadding, |
| MAX(kTooltipHorizontalPadding + width / 2, anchor.x)), |
| anchor.y + kTooltipTailHeight + height / 2.0); |
| self.translatesAutoresizingMaskIntoConstraints = NO; |
| |
| CGRect tooltipRect = CGRectMake(0.0, kTooltipTailHeight, width, height); |
| CGFloat centerX = tooltipRect.size.width / 2.0; |
| CGFloat halfTailWidth = kTooltipTailWidth / 2.0; |
| |
| UIBezierPath* path = |
| [UIBezierPath bezierPathWithRoundedRect:tooltipRect |
| cornerRadius:kTooltipCornerRadius]; |
| [path moveToPoint:CGPointMake(centerX, 0)]; |
| [path addLineToPoint:CGPointMake(centerX + halfTailWidth, |
| kTooltipTailHeight + 1)]; |
| [path addLineToPoint:CGPointMake(centerX - halfTailWidth, |
| kTooltipTailHeight + 1)]; |
| [path closePath]; |
| |
| self.backgroundLayer.path = path.CGPath; |
| self.backgroundLayer.fillColor = |
| [UIColor colorNamed:kTextPrimaryColor].CGColor; |
| |
| [self addSubview:label]; |
| [_keyWindow addSubview:self]; |
| |
| self.alpha = 0.0; |
| [UIView animateWithDuration:kTooltipFadeInTime |
| delay:0.0 |
| options:UIViewAnimationOptionCurveEaseOut |
| animations:^{ |
| self.alpha = 1.0; |
| } |
| completion:nil]; |
| } |
| |
| - (void)hide { |
| [_keyWindow removeGestureRecognizer:_tapBehindGesture]; |
| if (self == _active) { |
| _active = nil; |
| } |
| [self.delegate tooltipViewWillDismiss:self]; |
| [UIView animateWithDuration:kTooltipFadeInTime |
| delay:0.0 |
| options:UIViewAnimationOptionCurveEaseOut |
| animations:^{ |
| self.alpha = 0.0; |
| } |
| completion:^(BOOL finished) { |
| [self removeFromSuperview]; |
| }]; |
| } |
| |
| #pragma mark - Private |
| |
| - (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| if ([self.traitCollection |
| hasDifferentColorAppearanceComparedToTraitCollection: |
| previousTraitCollection]) { |
| self.backgroundLayer.fillColor = |
| [UIColor colorNamed:kTextPrimaryColor].CGColor; |
| } |
| } |
| |
| - (void)checkTap:(UITapGestureRecognizer*)sender { |
| if (sender.state == UIGestureRecognizerStateEnded) { |
| CGPoint location = [sender locationInView:self]; |
| if ([self pointInside:location withEvent:nil]) { |
| #pragma clang diagnostic push |
| #pragma clang diagnostic ignored "-Warc-performSelector-leaks" |
| [_target performSelector:_action]; |
| #pragma clang diagnostic pop |
| sender.state = UIGestureRecognizerStateCancelled; |
| } |
| [self hide]; |
| } |
| } |
| |
| #pragma mark - UIView overrides |
| |
| + (Class)layerClass { |
| return [CAShapeLayer class]; |
| } |
| |
| - (CAShapeLayer*)backgroundLayer { |
| return base::apple::ObjCCastStrict<CAShapeLayer>(self.layer); |
| } |
| |
| @end |