悬浮框--UIWindow的学习知识点

2,336 阅读3分钟

想法:仿照哆啦A梦(DoraemonKit)制作一个能够持续在所有页面上方的一个悬浮框

总结点1--创建一个新的window

1、首先UIWindow必须被某个对象所强持有,如在AppDelegate中创建一个新的window对象(myWindow)才能显示在屏幕上方

@property(nonatomic,strong) UIWindow *myWindow;

2、实例化这个对象,同时让window显示出来(因为新的window的hidden属性默认为yes,我们需要将hidden设置为no让其显示出来)

self.myWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 100, 40, 40)];
self.myWindow.hidden = NO;

3、通过调整windowLevel从而实现这个window能持续在屏幕上方显示出来

self.myWindow.windowLevel = UIWindowLevelStatusBar;

4、设置window对象的根控制器

因为我想实现一个悬浮框出来,所以我就创建了一个新的UIViewController作为根控制器。
而新的控制器中我添加了一个UIButton控件,因此我能通过点击悬浮框触发一系列需要的动作
最后必须调用原本window对象的makeKeyWindow方法来刷新屏幕

UIViewController *newViewController = [[UIViewController alloc] init];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.backgroundColor = UIColor.redColor;
btn.frame = self.myWindow.bounds;
if (!self.myWindow.rootViewController) {
    self.myWindow.rootViewController = newViewController;
}
[self.myWindow.rootViewController.view addSubview:btn];

[self.window makeKeyAndVisible];

总结点2--滑动悬浮框

1、悬浮框需要滑动则需要用到平移手势(UIPanGestureRecognizer),向myWindow添加手势

UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(locationChange:)];
pan.delaysTouchesBegan = YES;
[self.myWindow addGestureRecognizer:pan];

其中delaysTouchesBegan属性设置为:NO 会直接将触摸事件发送给视图,YES 在手势识别过程中不会将触摸事件发送给视图 只有当手势识别失败时才会发送给视图

2、手势处理内容:

2.1获取滑动偏移量:
UIView *panView = self.myWindow;
CGPoint tranp = [pan translationInView:panView];
//重新设置拖动的起始点相应为上一次的拖动后的点,即每改变一次均要重置定位,类似的拖动,放大,旋转均要重置拖动后的起始点
[pan setTranslation:CGPointZero inView:panView];
CGFloat newX = panView.center.x + tranp.x;
CGFloat newY = panView.center.y + tranp.y;

其中settranslation则会把每次偏移量记录下来,能够让平移变得顺滑,最后总结出每次滑动得到的新的x值(newX)和y值(newY)

2.2计算放手后需要复位到靠边的坐标:
CGFloat safeBottom = 0;
if (@available(iOS 11.0, *)) {
   //安全区域是包括导航栏(navicationbar)以及标签栏(tabbar)
   //安全区域只是不包含iPhone X记性的手势条部分以及刘海左右两侧部分区域
   safeBottom = self.myWindow.safeAreaInsets.bottom;
}
CGFloat afterX = panView.center.x + tranp.x;
CGFloat afterY = panView.center.y + tranp.y;
//当x值小于屏幕的一半时靠左边靠拢,否则靠右靠拢
if (afterX < UIScreen.mainScreen.bounds.size.width / 2) {

    afterX = self.myWindow.bounds.size.width/2;
}else{
    afterX = UIScreen.mainScreen.bounds.size.width - (self.myWindow.bounds.size.width/2);
}
//防止y轴坐标滑动超过安全区域,当超过安全区域的时候复位回到安全区域内部
if (afterY > CGRectGetMaxY([UIScreen mainScreen].bounds)-safeBottom) {
    afterY = CGRectGetMaxY([UIScreen mainScreen].bounds)-safeBottom;
}
if (afterY < [UIApplication sharedApplication].statusBarFrame.size.height) {
    afterY = [UIApplication sharedApplication].statusBarFrame.size.height;
}
2.3根据手势状态改变悬浮窗位置:
if(pan.state == UIGestureRecognizerStateChanged){
    //当滑动状态在滑动中的时候坐标根据滑动偏移量时设置
    self.myWindow.center = CGPointMake(newX, newY);
}else if (pan.state == UIGestureRecognizerStateEnded){
    //当滑动状态在滑动结束的时候通过动画将悬浮框复位到屏幕边缘
    [UIView animateWithDuration:0.2 animations:^{
        self.myWindow.center = CGPointMake(afterX, afterY);
    }];
}

总结点3--通过延时省去悬浮窗的根控制器的花销

[self.window makeKeyAndVisible];
//通过延时操作,可以将按钮直接设置为myWindow的子视图
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    self.myWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0, 100, 40, 40)];
    self.myWindow.hidden = NO;

    self.myWindow.windowLevel = UIWindowLevelStatusBar + 50.0f;

    UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
    btn.backgroundColor = UIColor.redColor;
    btn.frame = self.myWindow.bounds;
    [self.myWindow addSubview:btn];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(locationChange:)];
    pan.delaysTouchesBegan = YES;
    [self.myWindow addGestureRecognizer:pan];
});