123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- //
- // MISFloatingBall.m
- // MISFloatingBall
- //
- // Created by Mistletoe on 2017/4/22.
- // Copyright © 2017年 Mistletoe. All rights reserved.
- //
- #import "MISFloatingBall.h"
- #include <objc/runtime.h>
- #pragma mark - MISFloatingBallWindow
- @interface MISFloatingBallWindow : UIWindow
- @end
- @implementation MISFloatingBallWindow
- - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
- __block MISFloatingBall *floatingBall = nil;
- [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- if ([obj isKindOfClass:[MISFloatingBall class]]) {
- floatingBall = (MISFloatingBall *)obj;
- *stop = YES;
- }
- }];
-
- if (CGRectContainsPoint(floatingBall.bounds,
- [floatingBall convertPoint:point fromView:self])) {
- return [super pointInside:point withEvent:event];
- }
-
- return NO;
- }
- @end
- #pragma mark - MISFloatingBallManager
- @interface MISFloatingBallManager : NSObject
- @property (nonatomic, assign) BOOL canRuntime;
- @property (nonatomic, weak) UIView *superView;
- @end
- @implementation MISFloatingBallManager
- + (instancetype)shareManager {
- static MISFloatingBallManager *ballMgr = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- ballMgr = [[MISFloatingBallManager alloc] init];
- });
-
- return ballMgr;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- self.canRuntime = NO;
- }
- return self;
- }
- @end
- #pragma mark - UIView (MISAddSubview)
- @interface UIView (MISAddSubview)
- @end
- @implementation UIView (MISAddSubview)
- + (void)load {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- method_exchangeImplementations(class_getInstanceMethod(self, @selector(addSubview:)), class_getInstanceMethod(self, @selector(mis_addSubview:)));
- });
- }
- - (void)mis_addSubview:(UIView *)subview {
- [self mis_addSubview:subview];
-
- if ([MISFloatingBallManager shareManager].canRuntime) {
- if ([[MISFloatingBallManager shareManager].superView isEqual:self]) {
- [self.subviews enumerateObjectsUsingBlock:^(UIView * obj, NSUInteger idx, BOOL * _Nonnull stop) {
- if ([obj isKindOfClass:[MISFloatingBall class]]) {
- [self insertSubview:subview belowSubview:(MISFloatingBall *)obj];
- }
- }];
- }
- }
- }
- @end
- #pragma mark - MISFloatingBall
- @interface MISFloatingBall()
- @property (nonatomic, assign) CGPoint centerOffset;
- @property (nonatomic, copy) MISEdgeRetractConfig(^edgeRetractConfigHander)();
- @property (nonatomic, assign) NSTimeInterval autoEdgeOffsetDuration;
- @property (nonatomic, assign, getter=isAutoEdgeRetract) BOOL autoEdgeRetract;
- @property (nonatomic, strong) UIView *parentView;
- // content
- @property (nonatomic, strong) UIImageView *ballImageView;
- @property (nonatomic, strong) UILabel *ballLabel;
- @property (nonatomic, strong) UIView *ballCustomView;
- @property (nonatomic, assign) UIEdgeInsets effectiveEdgeInsets;
- @end
- static const NSInteger minUpDownLimits = 60 * 1.5f; // MISFloatingBallEdgePolicyAllEdge 下,悬浮球到达一个界限开始自动靠近上下边缘
- #ifndef __OPTIMIZE__
- #define MISLog(...) NSLog(__VA_ARGS__)
- #else
- #define MISLog(...) {}
- #endif
- @implementation MISFloatingBall
- #pragma mark - Life Cycle
- - (void)dealloc {
- MISLog(@"MISFloatingBall dealloc");
- [MISFloatingBallManager shareManager].canRuntime = NO;
- [MISFloatingBallManager shareManager].superView = nil;
- }
- - (instancetype)initWithFrame:(CGRect)frame {
- return [self initWithFrame:frame inSpecifiedView:nil effectiveEdgeInsets:UIEdgeInsetsZero];
- }
- - (instancetype)initWithFrame:(CGRect)frame inSpecifiedView:(UIView *)specifiedView {
- return [self initWithFrame:frame inSpecifiedView:specifiedView effectiveEdgeInsets:UIEdgeInsetsZero];
- }
- - (instancetype)initWithFrame:(CGRect)frame inSpecifiedView:(UIView *)specifiedView effectiveEdgeInsets:(UIEdgeInsets)effectiveEdgeInsets {
- self = [super initWithFrame:frame];
- if (self) {
- self.backgroundColor = [UIColor clearColor];
-
- _autoCloseEdge = NO;
- _autoEdgeRetract = NO;
- _edgePolicy = MISFloatingBallEdgePolicyAllEdge;
- _effectiveEdgeInsets = effectiveEdgeInsets;
-
- UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognizer:)];
- UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognizer:)];
-
- [self addGestureRecognizer:tapGesture];
- [self addGestureRecognizer:panGesture];
- [self configSpecifiedView:specifiedView];
- }
- return self;
- }
- - (void)configSpecifiedView:(UIView *)specifiedView {
- if (specifiedView) {
- _parentView = specifiedView;
- }
- else {
- UIWindow *window = [[MISFloatingBallWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
- window.windowLevel = CGFLOAT_MAX; //UIWindowLevelStatusBar - 1;
- window.rootViewController = [UIViewController new];
- window.rootViewController.view.backgroundColor = [UIColor clearColor];
- window.rootViewController.view.userInteractionEnabled = NO;
- [window makeKeyAndVisible];
-
- _parentView = window;
- }
-
- _parentView.hidden = YES;
- _centerOffset = CGPointMake(_parentView.bounds.size.width * 0.6, _parentView.bounds.size.height * 0.6);
-
- // setup ball manager
- [MISFloatingBallManager shareManager].canRuntime = YES;
- [MISFloatingBallManager shareManager].superView = specifiedView;
- }
- #pragma mark - Private Methods
- // 靠边
- - (void)autoCloseEdge {
- [UIView animateWithDuration:0.5f animations:^{
- // center
- self.center = [self calculatePoisitionWithEndOffset:CGPointZero];//center;
- } completion:^(BOOL finished) {
- // 靠边之后自动缩进边缘处
- if (self.isAutoEdgeRetract) {
- [self performSelector:@selector(autoEdgeOffset) withObject:nil afterDelay:self.autoEdgeOffsetDuration];
- }
- }];
- }
- - (void)autoEdgeOffset {
- MISEdgeRetractConfig config = self.edgeRetractConfigHander ? self.edgeRetractConfigHander() : MISEdgeOffsetConfigMake(CGPointMake(self.bounds.size.width * 0.3, self.bounds.size.height * 0.3), 0.8);
-
- [UIView animateWithDuration:0.5f animations:^{
- self.center = [self calculatePoisitionWithEndOffset:config.edgeRetractOffset];
- self.alpha = config.edgeRetractAlpha;
- }];
- }
- - (CGPoint)calculatePoisitionWithEndOffset:(CGPoint)offset {
- CGFloat ballHalfW = self.bounds.size.width * 0.5;
- CGFloat ballHalfH = self.bounds.size.height * 0.5;
- CGFloat parentViewW = self.parentView.bounds.size.width;
- CGFloat parentViewH = self.parentView.bounds.size.height;
- CGPoint center = self.center;
-
- if (MISFloatingBallEdgePolicyLeftRight == self.edgePolicy) {
- // 左右
- center.x = (center.x < self.parentView.bounds.size.width * 0.5) ? (ballHalfW - offset.x + self.effectiveEdgeInsets.left) : (parentViewW + offset.x - ballHalfW + self.effectiveEdgeInsets.right);
- }
- else if (MISFloatingBallEdgePolicyUpDown == self.edgePolicy) {
- center.y = (center.y < self.parentView.bounds.size.height * 0.5) ? (ballHalfH - offset.y + self.effectiveEdgeInsets.top) : (parentViewH + offset.y - ballHalfH + self.effectiveEdgeInsets.bottom);
- }
- else if (MISFloatingBallEdgePolicyAllEdge == self.edgePolicy) {
- if (center.y < minUpDownLimits) {
- center.y = ballHalfH - offset.y + self.effectiveEdgeInsets.top;
- }
- else if (center.y > parentViewH - minUpDownLimits) {
- center.y = parentViewH + offset.y - ballHalfH + self.effectiveEdgeInsets.bottom;
- }
- else {
- center.x = (center.x < self.parentView.bounds.size.width * 0.5) ? (ballHalfW - offset.x + self.effectiveEdgeInsets.left) : (parentViewW + offset.x - ballHalfW + self.effectiveEdgeInsets.right);
- }
- }
- return center;
- }
- #pragma mark - Public Methods
- - (void)show {
- self.parentView.hidden = NO;
- [self.parentView addSubview:self];
- }
- - (void)hide {
- self.parentView.hidden = YES;
- [self removeFromSuperview];
- }
- - (void)visible {
- [self show];
- }
- - (void)disVisible {
- [self hide];
- }
- - (void)autoEdgeRetractDuration:(NSTimeInterval)duration edgeRetractConfigHander:(MISEdgeRetractConfig (^)())edgeRetractConfigHander {
- if (self.isAutoCloseEdge) {
- // 只有自动靠近边缘的时候才生效
- self.edgeRetractConfigHander = edgeRetractConfigHander;
- self.autoEdgeOffsetDuration = duration;
- self.autoEdgeRetract = YES;
- }
- }
- - (void)setContent:(id)content contentType:(MISFloatingBallContentType)contentType {
- BOOL notUnknowType = (MISFloatingBallContentTypeCustomView == contentType) || (MISFloatingBallContentTypeImage == contentType) || (MISFloatingBallContentTypeText == contentType);
- NSAssert(notUnknowType, @"can't set ball content with an unknow content type");
-
- [self.ballCustomView removeFromSuperview];
- if (MISFloatingBallContentTypeImage == contentType) {
- NSAssert([content isKindOfClass:[UIImage class]], @"can't set ball content with a not image content for image type");
- [self.ballLabel setHidden:YES];
- [self.ballCustomView setHidden:YES];
- [self.ballImageView setHidden:NO];
- [self.ballImageView setImage:(UIImage *)content];
- }
- else if (MISFloatingBallContentTypeText == contentType) {
- NSAssert([content isKindOfClass:[NSString class]], @"can't set ball content with a not nsstring content for text type");
- [self.ballLabel setHidden:NO];
- [self.ballCustomView setHidden:YES];
- [self.ballImageView setHidden:YES];
- [self.ballLabel setText:(NSString *)content];
- }
- else if (MISFloatingBallContentTypeCustomView == contentType) {
- NSAssert([content isKindOfClass:[UIView class]], @"can't set ball content with a not uiview content for custom view type");
- [self.ballLabel setHidden:YES];
- [self.ballCustomView setHidden:NO];
- [self.ballImageView setHidden:YES];
-
- self.ballCustomView = (UIView *)content;
-
- CGRect frame = self.ballCustomView.frame;
- frame.origin.x = (self.bounds.size.width - self.ballCustomView.bounds.size.width) * 0.5;
- frame.origin.y = (self.bounds.size.height - self.ballCustomView.bounds.size.height) * 0.5;
- self.ballCustomView.frame = frame;
-
- self.ballCustomView.userInteractionEnabled = NO;
- [self addSubview:self.ballCustomView];
- }
- }
- #pragma mark - GestureRecognizer
- // 手势处理
- - (void)panGestureRecognizer:(UIPanGestureRecognizer *)panGesture {
- if (UIGestureRecognizerStateBegan == panGesture.state) {
- [self setAlpha:1.0f];
-
- // cancel
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(autoEdgeOffset) object:nil];
- }
- else if (UIGestureRecognizerStateChanged == panGesture.state) {
- CGPoint translation = [panGesture translationInView:self];
-
- CGPoint center = self.center;
- center.x += translation.x;
- center.y += translation.y;
- self.center = center;
-
- CGFloat leftMinX = 0.0f + self.effectiveEdgeInsets.left;
- CGFloat topMinY = 0.0f + self.effectiveEdgeInsets.top;
- CGFloat rightMaxX = self.parentView.bounds.size.width - self.bounds.size.width + self.effectiveEdgeInsets.right;
- CGFloat bottomMaxY = self.parentView.bounds.size.height - self.bounds.size.height + self.effectiveEdgeInsets.bottom;
-
- CGRect frame = self.frame;
- frame.origin.x = frame.origin.x > rightMaxX ? rightMaxX : frame.origin.x;
- frame.origin.x = frame.origin.x < leftMinX ? leftMinX : frame.origin.x;
- frame.origin.y = frame.origin.y > bottomMaxY ? bottomMaxY : frame.origin.y;
- frame.origin.y = frame.origin.y < topMinY ? topMinY : frame.origin.y;
- self.frame = frame;
-
- // zero
- [panGesture setTranslation:CGPointZero inView:self];
- }
- else if (UIGestureRecognizerStateEnded == panGesture.state) {
- if (self.isAutoCloseEdge) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // 0.2s 之后靠边
- [self autoCloseEdge];
- });
- }
- }
- }
- - (void)tapGestureRecognizer:(UIPanGestureRecognizer *)tapGesture {
- __weak __typeof(self) weakSelf = self;
- if (self.clickHandler) {
- self.clickHandler(weakSelf);
- }
-
- if ([_delegate respondsToSelector:@selector(didClickFloatingBall:)]) {
- [_delegate didClickFloatingBall:self];
- }
- }
- #pragma mark - Setter / Getter
- - (void)setAutoCloseEdge:(BOOL)autoCloseEdge {
- _autoCloseEdge = autoCloseEdge;
-
- if (autoCloseEdge) {
- [self autoCloseEdge];
- }
- }
- - (void)setTextTypeTextColor:(UIColor *)textTypeTextColor {
- _textTypeTextColor = textTypeTextColor;
-
- [self.ballLabel setTextColor:textTypeTextColor];
- }
- - (UIImageView *)ballImageView {
- if (!_ballImageView) {
- _ballImageView = [[UIImageView alloc] initWithFrame:self.bounds];
- [self addSubview:_ballImageView];
- }
- return _ballImageView;
- }
- - (UILabel *)ballLabel {
- if (!_ballLabel) {
- _ballLabel = [[UILabel alloc] initWithFrame:self.bounds];
- _ballLabel.textAlignment = NSTextAlignmentCenter;
- _ballLabel.numberOfLines = 1.0f;
- _ballLabel.minimumScaleFactor = 0.0f;
- _ballLabel.adjustsFontSizeToFitWidth = YES;
- [self addSubview:_ballLabel];
- }
- return _ballLabel;
- }
- @end
|