悬浮窗ios

2018-02-22 09:15:09 qq_24702189 阅读数 1706

我们经常使用各种调试工具,或者开源库来支持悬浮窗调试信息,但苹果的私有方法就提供了UIDebuggingInformationOverlay。

系统要求: iOS10 经测试 iOS11不可用

使用方法:

AppDelegatedidFinishLaunchingWithOptions 方法中加入两行代码即可。

#if DEBUG
    
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    
    id overlayClass = NSClassFromString(@"UIDebuggingInformationOverlay");
    [overlayClass performSelector:NSSelectorFromString(@"prepareDebuggingOverlay")];
    
#endif
    

调用:

运行后,用两个手指头在状态栏上同时点击下就可以显示出这个调试的悬浮层。


可以看到以上几个选项:

  • View Hierarchy (查看View的层级关系)
  • VC Hierarchy 查看ViewController层级关系)
  • Ivar Explorer(查看UIApplication 的成员属性)
  • Measure (测量View的尺寸)
  • Spec Compare (对比设计图)
  • System Coloer Audit (系统颜色审计)

View Hierarchy

进入到这个页面后,可以看见整个view的层级结构。悬浮窗右上角有个Inspect,点击这个可以高亮显示当前选择的View。如图所示:

7710B96C-6161-4453-808B-AA049885F624.png

当前选中的是 SettingSwitchItemView。
我们在页面上选择一个swicth按钮,这时会定位到该view的条目,如图所示:

4A7275E1-26C2-4A80-B4CF-6F511BE7C5C3.png

我们点击右侧的“!”图标,进入到swicth的详情页面,这里展示了view的详西信息,并且可以修改一些属性,比如我们修改这个button的透明度,如图所示:

E62D4DF3-D935-4116-BA66-5692547339FE.png

VC Hierarchy

我们回到悬浮窗的主界面,进入到VC Hierarchy界面看看。
A3CB9BC0-29E1-408C-8E2C-C53C3E7C8C73.png

Ivar Explorer

接下来我们去Ivar Explorer页面看一下。进到页面后,对里面的一些值不是很熟悉,不知道是哪里的一些值,不过里面,看见了我们熟悉的 AppDelegate 。
1CD86F5A-54AC-4508-99CD-0E7ECF2C47BD.png

点击去看看,有我们更加熟悉的值了,是Appdelegate里我们的一些值。

A5400178-6742-4EE9-A304-7A37EBE53CC7.png

Measure

笔者认为最实用的就是这个测量功能了,它可以帮助我们测量view的大小。进入这个页面是这个样子的


AD56EB68-2F6C-48E4-94BE-486498946DA5.png

上面有三个tab选项,None, Vertical, Horizontal,选择Vertical可以测量垂直方向上的距离或者view的高度,选择Horizontal是在水平方向上进行测量。在悬浮窗上还有一个 View Mode的选项开关,开启时,会以View为单位,来测量view的大小宽高;关闭时是以非空白像素为起始端和结束端进行更宽泛的测量。如图所示:


F58DD36D-15A7-4385-AD99-DA7611AE394E.png

例如:测量switch的高度

19F53376-D326-443B-8075-12EE268F763F.png

关闭ViewMode后的测量

1EB6FE94-0218-41E1-A818-2C26830DA7B9.png

Spec Compare

这个功能是比较spec图片和我们辛苦完成的view的,也比较实用,直接可以看出我们我们扣代码出来的界面和UI给出的设计图的差别。进入到这个页面后,点击右上角的Add进入相册把UI设计师给出的设计图添加进来,如图:


然后点击一下图片,这时会把UI设计图覆盖在界面上,然后,我们用手指从上到下滑动,就可以让UI设计图变得透明, 双击退出。如图:

System Coloer Audit

这个还不知道做什么的,点进去会是空白的列表.

2017-12-21 10:56:34 qq_19697705 阅读数 2925

我们经常使用各种调试工具,或者开源库来支持悬浮窗调试信息,但苹果的私有方法就提供了UIDebuggingInformationOverlay。

系统要求:  ios10+,iOS11 目前在我自己的手机上测试是行不通的。如果英语不错的可以去看这篇,看完记得通知我一下噢。

https://www.raywenderlich.com/177890/swizzling-in-ios-11-with-uidebugginginformationoverlay

使用方法:

在AppDelegate的didFinishLaunchingWithOptions方法中加入两行代码即可。

#if DEBUG

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

id overlayClass = NSClassFromString(@"UIDebuggingInformationOverlay");

[overlayClass performSelector:NSSelectorFromString(@"prepareDebuggingOverlay")];

#endif

调用:

运行后,用两个手指头在状态栏上同时点击下就可以显示出这个调试的悬浮层


可以看到以上几个选项:

View Hierarchy  (查看View的层级关系)

VC Hierarchy      查看ViewController层级关系)

Ivar Explorer(查看UIApplication 的成员属性)

Measure    (测量View的尺寸)

Spec Compare (对比设计图)

System Coloer Audit (系统颜色审计)

View Hierarchy

进入到这个页面后,可以看见整个view的层级结构。悬浮窗右上角有个Inspect,点击这个可以高亮显示当前选择的View。如图所示:


当前选中的是 SettingSwitchItemView。

我们在页面上选择一个swicth按钮,这时会定位到该view的条目,如图所示


我们点击右侧的“!”图标,进入到swicth的详情页面,这里展示了view的详西信息,并且可以修改一些属性,比如我们修改这个button的透明度,如图所示:

VC Hierarchy

我们回到悬浮窗的主界面,进入到VC Hierarchy界面看看。

Ivar Explorer

接下来我们去Ivar Explorer页面看一下。进到页面后,对里面的一些值不是很熟悉,不知道是哪里的一些值,不过里面,看见了我们熟悉的 AppDelegate 。

1CD86F5A-54AC-4508-99CD-0E7ECF2C47BD.png

点击去看看,有我们更加熟悉的值了,是Appdelegate里我们的一些值。

Measure

笔者认为最实用的就是这个测量功能了,它可以帮助我们测量view的大小。进入这个页面是这个样子的

AD56EB68-2F6C-48E4-94BE-486498946DA5.png

上面有三个tab选项,None, Vertical, Horizontal,选择Vertical可以测量垂直方向上的距离或者view的高度,选择Horizontal是在水平方向上进行测量。在悬浮窗上还有一个 View Mode的选项开关,开启时,会以View为单位,来测量view的大小宽高;关闭时是以非空白像素为起始端和结束端进行更宽泛的测量。如图所示:

例如:测量switch的高度

关闭ViewMode后的测量

Spec Compare

这个功能是比较spec图片和我们辛苦完成的view的,也比较实用,直接可以看出我们我们扣代码出来的界面和UI给出的设计图的差别。进入到这个页面后,点击右上角的Add进入相册把UI设计师给出的设计图添加进来,如图:

然后点击一下图片,这时会把UI设计图覆盖在界面上,然后,我们用手指从上到下滑动,就可以让UI设计图变得透明, 双击退出。如图:

System Coloer Audit

这个还不知道做什么的,点进去会是空白的列表.





2018-06-13 16:01:19 u011361385 阅读数 915
转自https://wellphone.me/post/2017/use_uidebugginginformationoverlay_for_ios11/

背景介绍

iOS系统从9.0之后就加入了悬浮窗调试小工具来帮助开发者调试UI,很遗憾的是,这个是一个非公开的功能,苹果没有公开它的头文件。(私有API传送门)当然私有API没有阻挡住我们使用这么酷炫的小工具。如何使用可以看看前段时间笔者写过一片文章《iOS自带悬浮窗调试工具使用详解》。可是好景不长,在iOS11中这个小工具没法用了。最近想用这个系统自带的悬浮窗工具来调试UI,毕竟是接入成本最小UI调试工具,于是看到了国外大神的这篇文章 《Swizzling in iOS 11 with UIDebuggingInformationOverlay》

原因

国外大神的文章很长,详细介绍了他是如何让悬浮窗调试工具重现在iOS11上的。文章具体内容这里就不展开了,感兴趣的可以去看看他的文章。文章主要内容:
iOS9 & 10 上 -[UIDebuggingInformationOverlay init] 和 [UIDebuggingInformationOverlay prepareDebuggingOverlay] 是能正常工作的。在iOS11上,上面这两个方法被苹果做了限制,只有苹果内部设备才可以正常使用。对这两个方法逆向后的代码如下:

@implementation UIDebuggingInformationOverlay

- (instancetype)init {
  static BOOL overlayEnabled = NO;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    overlayEnabled = UIDebuggingOverlayIsEnabled();
  });
  if (!overlayEnabled) { 
    return nil;
  }

  if (self = [super init]) {
    [self _setWindowControlsStatusBarOrientation:NO];
  }
  return self;
}

+ (void)prepareDebuggingOverlay {
  if (_UIGetDebuggingOverlayEnabled()) {
    id handler = [UIDebuggingInformationOverlayInvokeGestureHandler mainHandler];
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];
    [tapGesture setNumberOfTouchesRequired:2];
    [tapGesture setNumberOfTapsRequired:1];
    [tapGesture setDelegate:handler];
    
    UIView *statusBarWindow = [UIApp statusBarWindow];
    [statusBarWindow addGestureRecognizer:tapGesture];
  }
}

@end

可以很清晰的看到,苹果用UIDebuggingOverlayIsEnabled() 对UIDebuggingInformationOverlay的初始化方法做了检测,如果不是内部设备就返回nil,同时对prepareDebuggingOverlay方法也做了检测。

破解

既然我们都知道了方法内容,我们绕过这两个检查方法不就OK了?对的,使用Methond Swizzling 替换这两个OC的方法就好了。
国外大神也给出了一个解决方案,替换上面的两个OC方法,但是其中prepareDebuggingOverlay中添加了汇编代码,并且给出的汇编代码只支持x86_64的cpu。笔者在这个基础上重写了prepareDebuggingOverlay,发现也可以work。代码如下:

@interface UIWindow (PrivateMethods)
- (void)_setWindowControlsStatusBarOrientation:(BOOL)orientation;
@end

@interface FakeWindowClass : UIWindow
@end

@implementation FakeWindowClass

- (instancetype)initSwizzled {
    self = [super init];
    if (self) {
        [self _setWindowControlsStatusBarOrientation:NO];
    }
    return self;
}

@end

@implementation NSObject (UIDebuggingInformationOverlayEnable)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
        [FakeWindowClass swizzleSelector:@selector(init) newSelector:@selector(initSwizzled) forClass:cls isClassMethod:NO];
        [self swizzleSelector:@selector(prepareDebuggingOverlay) newSelector:@selector(prepareDebuggingOverlaySwizzled) forClass:cls isClassMethod:YES];
    });
}

+ (void)swizzleSelector:(SEL)originalSelector newSelector:(SEL)swizzledSelector forClass:(Class)class isClassMethod:(BOOL)isClassMethod {
    Method originalMethod = NULL;
    Method swizzledMethod = NULL;
    
    if (isClassMethod) {
        originalMethod = class_getClassMethod(class, originalSelector);
        swizzledMethod = class_getClassMethod([self class], swizzledSelector);
    } else {
        originalMethod = class_getInstanceMethod(class, originalSelector);
        swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
    }
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

+ (void)prepareDebuggingOverlaySwizzled {
    id overlayClass = NSClassFromString(@"UIDebuggingInformationOverlayInvokeGestureHandler");
    id handler = [overlayClass performSelector:NSSelectorFromString(@"mainHandler")];
  
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];
    tapGesture.numberOfTouchesRequired = 2;
    tapGesture.numberOfTapsRequired = 1;
    tapGesture.delegate = handler;
  
    UIView *statusBarWindow = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"];
    [statusBarWindow addGestureRecognizer:tapGesture];
}

@end

结尾

将上面的代码放在一个文件里,引入到我们的项目中就可以在iOS11上使用苹果自带的悬浮窗UI调试工具了。这里上传了这个文件UIDebuggingTool,方便大家。笔者只测试了iOS11.0.1,欢迎大家帮忙测试下其他系统的情况并修改这个小工具。


UIDebuggingInformationOverlay

2016-05-16 21:02:17 u011993697 阅读数 5660

需求

在一个app应用的最顶部添加一个悬浮窗,就像ios系统AssistiveTouch 可以左右滑动,但是最终会停在左边或右边。

实现思路

在应用的视图的最顶层添加一个UIWindow,用这个UIWindow 充当悬浮窗,给UIWindow添加移动的手势监听,让悬浮窗随着手指移动,释放的时候,让它以动画的方式靠边

代码

    //悬浮窗测试
    //创建一个悬浮窗口
    mwindow = [[AssistiveTouch alloc]initWithFrame:CGRectMake(100, 200, 40, 40) imageName:@"1.png"];
    //ios9 window要设置rootview 不然崩溃
    UIViewController *controller = [[UIViewController alloc] init];
    mwindow.rootViewController = controller;
    //展示悬浮窗。。
    [self.window makeKeyAndVisible];
        //添加移动的手势
        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(locationChange:)];
        pan.delaysTouchesBegan = YES;
        [self addGestureRecognizer:pan];
//改变位置
-(void)locationChange:(UIPanGestureRecognizer*)p
{
    //[[UIApplication sharedApplication] keyWindow]
    CGPoint panPoint = [p locationInView:[[UIApplication sharedApplication] keyWindow]];
    if(p.state == UIGestureRecognizerStateBegan)
    {
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(changeColor) object:nil];
        _imageView.alpha = 0.8;
    }
    else if (p.state == UIGestureRecognizerStateEnded)
    {
        [self performSelector:@selector(changeColor) withObject:nil afterDelay:4.0];
    }
    if(p.state == UIGestureRecognizerStateChanged)
    {
        self.center = CGPointMake(panPoint.x, panPoint.y);
    }
    else if(p.state == UIGestureRecognizerStateEnded)
    {
        if(panPoint.x <= kScreenWidth/2)
        {
            if(panPoint.y <= 40+HEIGHT/2 && panPoint.x >= 20+WIDTH/2)
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(panPoint.x, HEIGHT/2);
                }];
            }
            else if(panPoint.y >= kScreenHeight-HEIGHT/2-40 && panPoint.x >= 20+WIDTH/2)
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(panPoint.x, kScreenHeight-HEIGHT/2);
                }];
            }
            else if (panPoint.x < WIDTH/2+15 && panPoint.y > kScreenHeight-HEIGHT/2)
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(WIDTH/2, kScreenHeight-HEIGHT/2);
                }];
            }
            else
            {
                CGFloat pointy = panPoint.y < HEIGHT/2 ? HEIGHT/2 :panPoint.y;
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(WIDTH/2, pointy);
                }];
            }
        }
        else if(panPoint.x > kScreenWidth/2)
        {
            if(panPoint.y <= 40+HEIGHT/2 && panPoint.x < kScreenWidth-WIDTH/2-20 )
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(panPoint.x, HEIGHT/2);
                }];
            }
            else if(panPoint.y >= kScreenHeight-40-HEIGHT/2 && panPoint.x < kScreenWidth-WIDTH/2-20)
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(panPoint.x, 480-HEIGHT/2);
                }];
            }
            else if (panPoint.x > kScreenWidth-WIDTH/2-15 && panPoint.y < HEIGHT/2)
            {
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(kScreenWidth-WIDTH/2, HEIGHT/2);
                }];
            }
            else
            {
                CGFloat pointy = panPoint.y > kScreenHeight-HEIGHT/2 ? kScreenHeight-HEIGHT/2 :panPoint.y;
                [UIView animateWithDuration:0.2 animations:^{
                    self.center = CGPointMake(320-WIDTH/2, pointy);
                }];
            }
        }
    }
}
2017-02-22 14:22:09 wang_yuewen 阅读数 3176

一、UIWindow的简介

1.UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow

2.iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了

3.一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow。也就说,没有UIWindow,就看不见任何UI界面

二、在window上添加Button

1.创建window

-(UIWindow *)window
{
    if (!_window)
    {
        id <UIApplicationDelegate> delegate = [[UIApplication sharedApplication] delegate];
        if ([delegate respondsToSelector:@selector(window)])
        {
            _window = [delegate performSelector:@selector(window)];
        }
        else
        {
            _window = [[UIApplication sharedApplication] keyWindow];
        }
    }
    return _window;
}

2.创建Buuton

-(UIButton *)levitateButton
{
    if (!_levitateButton)
    {
        _levitateButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _levitateButton.frame = CGRectMake(self.view.frame.size.width - 70, self.view.frame.size.height - 200, 60, 60);
        _levitateButton.backgroundColor = MainColor;
        [_levitateButton setBackgroundImage:[UIImage imageNamed:@"Button"] forState:UIControlStateNormal];
        _levitateButton.layer.cornerRadius = 30.0f;
        _levitateButton.layer.masksToBounds = YES;
        [self.window addSubview:_levitateButton];
        [_levitateButton addTarget:self action:@selector(edit) forControlEvents:UIControlEventTouchUpInside];
        UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRecognizer:)];
        gesture.delegate = self;
        [_levitateButton addGestureRecognizer:gesture];
    }
    return _levitateButton;
}

3.拉动悬浮按钮移动需要遵守手势的协议 UIGestureRecognizerDelegate

-(void)panGestureRecognizer:(UIPanGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer translationInView:recognizer.view.superview];
    CGPoint center = CGPointMake(recognizer.view.center.x + point.x,recognizer.view.center.y + point.y);
    if (center.x > self.view.frame.size.width - recognizer.view.frame.size.width/2)
    {
        center.x = self.view.frame.size.width - recognizer.view.frame.size.width/2;
    }
    else if (center.x < recognizer.view.frame.size.width/2)
    {
        center.x = recognizer.view.frame.size.width/2;
    }
    else if (center.y < 109 + recognizer.view.frame.size.width/2)
    {
        center.y = 109 + recognizer.view.frame.size.width/2;
    }
    else if (center.y > self.view.frame.size.height - recognizer.view.frame.size.width/2)
    {
        center.y = self.view.frame.size.height - recognizer.view.frame.size.width/2;
    }
    [UIView animateWithDuration:0.1 animations:^{
        recognizer.view.center = center;
        [recognizer setTranslation:CGPointZero inView:recognizer.view.superview];
    }];
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return YES;
}
根据自己的需要修改拉动的范围
三、总结

上面的代码还稍微繁琐点不过可以使用的,后续我会优化封装下使代码更加简洁,欢迎大家关注奔跑的蜗牛