Last active
December 23, 2023 11:05
-
-
Save steventroughtonsmith/bbdb02d1a5c118cd66bf68a51cbb00e7 to your computer and use it in GitHub Desktop.
WIP tooltips for Mac Catalyst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIView+Tooltips.h | |
// Crossword | |
// | |
// Created by Steven Troughton-Smith on 13/09/2019. | |
// Copyright © 2019 Steven Troughton-Smith. All rights reserved. | |
// | |
#import <UIKit/UIKit.h> | |
NS_ASSUME_NONNULL_BEGIN | |
@interface UITooltipView : UIView | |
@property BOOL visible; | |
+ (instancetype)sharedInstance; | |
-(void)showFromRect:(CGRect)windowRect; | |
-(void)hide; | |
@property UILabel *tooltipLabel; | |
/* Private */ | |
-(void)_sizeToFit; | |
@end | |
@interface UIView (Tooltips) | |
@property NSString *toolTip; | |
/* Private */ | |
@property BOOL _tooltipMouseOver; | |
@property UIHoverGestureRecognizer *_tooltipRecognizer; | |
@end | |
NS_ASSUME_NONNULL_END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIView+Tooltips.m | |
// Crossword | |
// | |
// Created by Steven Troughton-Smith on 13/09/2019. | |
// Copyright © 2019 Steven Troughton-Smith. All rights reserved. | |
// | |
#import "UIView+Tooltips.h" | |
#import <objc/runtime.h> | |
@implementation UITooltipView | |
+ (instancetype)sharedInstance | |
{ | |
static UITooltipView *sharedInstance = nil; | |
static dispatch_once_t onceToken; // onceToken = 0 | |
dispatch_once(&onceToken, ^{ | |
sharedInstance = [[UITooltipView alloc] init]; | |
}); | |
return sharedInstance; | |
} | |
- (instancetype)init | |
{ | |
self = [super init]; | |
if (self) { | |
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleSystemMaterial]; | |
UIVisualEffectView *blurView = [[UIVisualEffectView alloc] initWithEffect:blurEffect]; | |
blurView.bounds = self.bounds; | |
blurView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; | |
[self addSubview:blurView]; | |
self.tooltipLabel = [[UILabel alloc] init]; | |
self.tooltipLabel.text = @""; | |
self.tooltipLabel.font = [UIFont systemFontOfSize:14]; | |
self.tooltipLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; | |
self.tooltipLabel.lineBreakMode = NSLineBreakByClipping; | |
[[blurView contentView] addSubview:self.tooltipLabel]; | |
self.layer.borderWidth = 1.0/[UIScreen mainScreen].scale; | |
self.layer.borderColor = [UIColor secondarySystemFillColor].CGColor; | |
self.layer.shadowColor = [UIColor blackColor].CGColor; | |
self.layer.shadowRadius = 6; | |
self.layer.shadowOffset = CGSizeMake(0, 0); | |
self.layer.shadowOpacity = 0.3; | |
} | |
return self; | |
} | |
-(void)_sizeToFit | |
{ | |
[self.tooltipLabel sizeToFit]; | |
self.frame = CGRectInset(self.tooltipLabel.bounds, -5, -3); | |
self.tooltipLabel.frame = CGRectInset(self.bounds, 5, 3); | |
} | |
-(void)showFromRect:(CGRect)convertedRect | |
{ | |
[CATransaction setDisableActions:YES]; | |
[self _sizeToFit]; | |
CGRect labelRect = self.bounds; | |
self.alpha = 0; | |
self.frame = CGRectMake(convertedRect.origin.x+convertedRect.size.width/2-labelRect.size.width/2, convertedRect.origin.y+convertedRect.size.height, self.frame.size.width, self.frame.size.height); | |
[CATransaction setDisableActions:NO]; | |
self.visible = YES; | |
[UIView animateWithDuration:0.1 animations:^{ | |
self.alpha = 1; | |
} completion:nil]; | |
} | |
-(void)hide | |
{ | |
[UIView animateWithDuration:0.1 animations:^{ | |
self.alpha = 0; | |
} completion:^(BOOL finished) { | |
self.visible = NO; | |
}]; | |
} | |
@end | |
@implementation UIView (Tooltips) | |
#pragma mark - Properties | |
-(void)set_tooltipMouseOver:(BOOL)tooltipMouseOver | |
{ | |
objc_setAssociatedObject(self, @selector(_tooltipMouseOver), @(tooltipMouseOver), OBJC_ASSOCIATION_ASSIGN); | |
} | |
- (BOOL)_tooltipMouseOver | |
{ | |
return [objc_getAssociatedObject(self, @selector(_tooltipMouseOver)) boolValue]; | |
} | |
-(void)set_tooltipRecognizer:(UIHoverGestureRecognizer *)obj | |
{ | |
objc_setAssociatedObject(self, @selector(_tooltipRecognizer), obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
} | |
- (UIHoverGestureRecognizer *)_tooltipRecognizer | |
{ | |
return objc_getAssociatedObject(self, @selector(_tooltipRecognizer)); | |
} | |
-(void)setToolTip:(NSString *)obj | |
{ | |
objc_setAssociatedObject(self, @selector(toolTip), obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
if (!self._tooltipRecognizer && obj && [obj length]) | |
[self _installTooltipRecognizer]; | |
} | |
- (NSString *)toolTip | |
{ | |
return objc_getAssociatedObject(self, @selector(toolTip)); | |
} | |
#pragma mark - Recognizer | |
-(void)_installTooltipRecognizer | |
{ | |
self._tooltipRecognizer = [[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(tooltipHover:)]; | |
[self addGestureRecognizer:self._tooltipRecognizer]; | |
} | |
-(void)_beginTooltipTimer | |
{ | |
CGFloat time = 2.0; | |
if ([UITooltipView sharedInstance].visible) | |
time = 0.0; | |
[NSTimer scheduledTimerWithTimeInterval:time repeats:NO block:^(NSTimer * _Nonnull timer) { | |
if (self._tooltipMouseOver) | |
{ | |
CGRect convertedRect = [self convertRect:self.bounds toView:self.window]; | |
[UITooltipView sharedInstance].tooltipLabel.text = self.toolTip; | |
[self.window addSubview:[UITooltipView sharedInstance]]; | |
[[UITooltipView sharedInstance] showFromRect:convertedRect]; | |
} | |
}]; | |
} | |
-(void)tooltipHover:(UIHoverGestureRecognizer *)recognizer | |
{ | |
switch (recognizer.state) | |
{ | |
case UIGestureRecognizerStateBegan: | |
{ | |
self._tooltipMouseOver = YES; | |
[self _beginTooltipTimer]; | |
break; | |
} | |
case UIGestureRecognizerStateChanged: | |
{ | |
break; | |
} | |
case UIGestureRecognizerStateEnded: | |
case UIGestureRecognizerStateCancelled: | |
case UIGestureRecognizerStateFailed: | |
{ | |
self._tooltipMouseOver = NO; | |
[[UITooltipView sharedInstance] hide]; | |
break; | |
} | |
default: | |
break; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for sharing this, @steventroughtonsmith!
Here's a rough Swift conversion, for anyone who might find it useful.