完美实现点击,项目实战

2020-03-24 04:28 来源:未知
  • 苹果是怎么落到实处该功用的, 大家未能得悉, 所以大家得要好实现

  • 大概作用达成步骤: 点击 statusBar--> 触发手势-->-->找到我们需求操作的内容 view-->滚动它

    • 第一我们超级轻松想到, 在 statusBar 上盖上三个 view, 然后就监听点击就是了, 但笔者要告知你那是不可取的
      • 你的 view 不容许盖到 statusBar 上, 因为它也是在几个独立的 window 上的, 这几个 window 在大家 app.keyWindow之上, 所以你的 view 长久都会在 statusBar 上面(有意思味的能够去打字与印刷看看)
      • 兴许你会说把 view 加到了 statusBar 下也没提到, 把 statusBar 的点击忽视掉就能够, 作者只可以说天真, 且不说 statusBar 档次布局复杂, 各类控件, 何况还拿不到那些控件, 就算能得到, 你要在 hitTest 里面一步步递归决断吗, 那样不现实,所以否定这种主张
      • 加 view 不行, 那就一定要加 window 了, 因为 Window是能加到 statusBar 上的, 只要校勘 window 的预先级--windowLevel属性就能够(补充某个, 优先级相像的 window, 后加的在地点State of Qatar
    • 光加个 window 还非常不足, 大家必要三个顶层调整器来统一管理 statusBar 的, 那样技能在调控器里形成对 view 的点击操作实行监听, 光搞个 view 是没用的
      • window 多大合适, 直接告诉你, 和荧屏. bounds 相等就能够, 为啥, 借使跟 statusBar.bounds 相等的话, 旋转显示屏的时候会有 bug, statusBar 会消失不见
      • 调节器 vi的朗朗上口就足以设置成 statusBar.bounds 大小了, 在 window 的 hitTest 方法里忽视掉 statusBar 以下的点击事件, 传递给 app.window拍卖
  • 第一代码:

    • 此地本人把 topWindow 设计成单例, 方便前面获得顶层的 topVc

UIScrollView动漫滚动格局

  • 1.使用setContentOffset:animated:办法完毕动漫滚动.
  • 2.scrollRectToVisiable:animated:滚动一块特定的区域到scrollView显示.假诺该区域曾经在scrollView中可以知道,调用此方式没影响.
    如上七个方法animated为YES本事落到实处动画滚动.

即使你指望体现的viewController只供给承当触摸事件,无需承担弹出键盘等非触摸事件,你完全能够不把newWindow设置成keyWIndow,轻便调用window.hidden = false就足以把分界面显示出来,如下所示

才干中央计算

以下计算一些里面包车型大巴才能中央。

  • 分界面搭建
    随意做什么样项目,首先要搭建好界面。
    分界面结构剖析:
    导航调节器--标题栏(可以滚动,srcollview)--内容区域(能够滚动,来回切换,展现的视图就是导航调节器的栈顶调整器的view)
    · 导航调节器
    首先表现出来的是导航调整器的view,所以要率先合并导航调节器(在storybord中,Editor--Embed In --navigation controler)
    · 增多标题scrollview

UIScrollView *titlesrcoll = [[UIScrollView alloc] init];
titlesrcoll.backgroundColor = [UIColor grayColor];
CGFloat y = self.navigationController.navigationBarHidden? 20:64;
titlesrcoll.frame = CGRectMake(0, y, self.view.bounds.size.width, 44);
[self.view addSubview:titlesrcoll];
_titleScrollView = titlesrcoll;

· 增多内容scrollview

UIScrollView *contentsrcoll = [[UIScrollView alloc] init];
contentsrcoll.backgroundColor = [UIColor greenColor];
CGFloat y = CGRectGetMaxY(self.titleScrollView.frame); //由_titleScrollView的底层边缘决定 y
contentsrcoll.frame = CGRectMake(0, y, self.view.bounds.size.width, self.view.bounds.size.height - y);
[self.view addSubview:contentsrcoll];
_contentScrollView = contentsrcoll; //全局

  • 标题设置
    一体化布局搭建完,上边就多少个二个拉长。有几个标题,就对应多少个view,而view又由子调整器决定,故先加多子调控器。例如:

SocietyViewController *vc4 = [[SocietyViewController alloc] init];
vc4.title = @"社会";
[self addChildViewController:vc4];

PS:子调控器的view的相关属性设置,采用懒加载,在其viewDidLoad方法中设。

  • 拍卖标题按键点击
    监听按键点击,在哪监听?增加完按键后就足以监听

[btn addTarget:self action:@selector(titleClick:) forControlEvents:UIControlEventTouchUpInside];

下一场提供监听方法,深入分析当点击后要做如何动作?深入分析点击标题要贯彻的功效意义:标题颜色改正--切换分界面(先增加view,再滚动过来);其它索要明白点击了哪叁个开关,故需求参数

-(void)titleClick:(UIButton *)button{
NSInteger i = button.tag;
//标题颜色退换
[self SelectedBtn:button];
//把相应的子控件view加多
[self setupOneViewControler:i]; //哪一个子调整器,由所点的btn决定--角标tag
//滚动内容到可视范围(退换偏移量x
CGFloat x = i * [UIScreen mainScreen].bounds.size.width;
self.contentScrollView.contentOffset = CGPointMake(x, 0);
}

-(void)setupOneViewControler:(NSInteger)i{
//把相应的子控件view加多
UIViewController *vc = self.childViewControllers[i];
if(vc.view.superview)
return; //加载了就无须加了
CGFloat x = i * [UIScreen mainScreen].bounds.size.width;
vc.view.frame = CGRectMake(x, 0, [UIScreen mainScreen].bounds.size.width, self.contentScrollView.bounds.size.height);
[self.contentScrollView addSubview:vc.view];
}

  • 存在bug解决
    ios7从此现在,导航调节器中的scrollview顶上部分会产出额外64的轮转区域,消除方式是:

self.automaticallyAdjustsScrollViewInsets = NO;

  • 剧情scrollview滚动监听
    三部曲:设置监听指标---固守公约---选择监听方法(哪天做业务)
    self.contentScrollView.delegate = self;
    <UIScrollViewDelegate>
    怎么样时候做?滚动完结时,截至滚动再加载view,节省财富。
    做怎么着业务?——标题选中、分界面切换。

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
//获取当前角标
NSInteger i = scrollView.contentOffset.x / [UIScreen mainScreen].bounds.size.width;
//获取题目按键
// UIButton *titleBtn = self.titleScrollView.subviews[i];//无法由此那些点子拿,因为通过index不可信
UIButton *titleBtn = self.titleBtns[i]; //用四个新数组保存标题按键,可信
//1,选中标题--哪二个标题? ->通过角标 -> 角标怎么获得
[self SelectedBtn:titleBtn];
//2.把相应的子调节器的view滚动过去
[self setupOneViewControler:i];
}

  • 标题居中拍卖
    如何时候做?标题选中的时候。标题居中,其实要做的就是要调解scrollview的偏移量。偏移多少?---点了哪二个标题,就相应偏移多少。---哪个题目?---由所点击的开关决定--->方法需求参数

-(void)setupTitleCenter:(UIButton *)button{
CGFloat offsetX = button.center.x - [UIScreen mainScreen].bounds.size.width * 0.5;
if(offsetX < 0 )
offsetX =0;
CGFloat maxoffsetX = self.titleScrollView.contentSize.width - [UIScreen mainScreen].bounds.size.width;
if(offsetX > maxoffsetX)
offsetX = maxoffsetX;
[self.titleScrollView setContentOffset:CGPointMake(offsetX,0) animated:YES];
}

  • 抽出封装
    上述分界面成效特别普及,应用普遍,由此得以视作移植代码。于是思忖把上面的案例封装抽出出来,外面方便使用。
  • 透过 showWithStatusBarClickBlock:方法来创制出 topWindow, 在 应用软件Delegate 中调用, 这里小编把搜索内容 view 并滚动到最前边的法子独立出来, 并在 showWithStatusBarClickBlock:后的 block 回调, 倘令你任什么地点方有这一个须要, 你能够选用那个点子, 你只要传入你要找的剧情view 的父view 或父父view...
  • 那边大家把 application.keyWindow传进去, 因为我们要求全局落成
  • 达成滚动到 top 的法规, 递归查找子控件, 找到 scrollView 就把它滚到最前头

图片 1

https://github.com/RamWire/NinaPagerView *vc框架

坐标系转变

  • 测算控件A在window中的x,y,width,height.
    • convertRect:toView:方法(八个艺术是可逆的卡塔尔(قطر‎
    • convertRect:fromView:方法
    • 注意: toView和fromView如果为nil,表示window.
    • 若果措施的调用者是A本身,则传入的convertRect为A.bounds.
      CGRect rect = [A convertRect:A.bounds toView:nil];
    • 如若格局的调用者是A的父控件,则传出的convertRect为A.frame.
    // 描述控件A在window中的xywidthheight
    CGRect rect = [A.superview convertRect:A.frame toView:window];
    CGRect rect = [A.superview convertRect:A.frame toView:nil];
    CGRect rect = [A convertRect:A.bounds toView:window];
    CGRect rect = [A convertRect:A.bounds toView:nil];
    CGRect rect = [window convertRect:A.frame fromView:A.superview];
    CGRect rect = [window convertRect:A.bounds fromView:A];
  • 坐标系转换图解
![](https://upload-images.jianshu.io/upload_images/1253159-5f373e8c72e474b2.png)

坐标系转换图解.png

复杂的页面切换中作为遮罩层预防闪屏。

功用意义

能够透过点击标题栏,或左右滑行内容区域,达成五个页面之间的切换。这种应用特别普及。

// UIViewController (JJStatusBarExtension) --分类@implementation UIViewController (JJStatusBarExtension)  load{ // 交换系统 viewWillAppear 和我们自定义的 jj_viewWillAppear Method method1 = class_getInstanceMethod(self, @selector(jj_viewWillAppear:)); Method method2 = class_getInstanceMethod(self, @selector(viewWillAppear:)); method_exchangeImplementations(method1, method2);}- jj_viewWillAppear:animated{ [self jj_viewWillAppear:animated]; // 如果非当前窗口显示的控制器, 我只测试到UINavigationController下,"UIInputWindowController"会产生影响, 把它屏蔽掉// NSLog(@"%@", self.class); if ([NSStringFromClass(self.class) isEqualToString:@"UIInputWindowController"]) return; if ([self respondsToSelector:@selector(jj_ignoreStatusBar)]) { if ([self jj_ignoreStatusBar]) return; } JJTopViewController *statusBarVc = (JJTopViewController *)[JJStatusBarExtension sharedStatusBarExtension].rootViewController; if (statusBarVc == self) return; statusBarVc.showingVc = self; // 判断是否设置动画 if (statusBarVc.showingVc.preferredStatusBarUpdateAnimation == UIStatusBarAnimationNone) { [statusBarVc setNeedsStatusBarAppearanceUpdate]; }else{ [UIView animateWithDuration:[JJStatusBarExtension sharedStatusBarExtension].statusBarAnimationDuration animations:^{ [statusBarVc setNeedsStatusBarAppearanceUpdate]; }]; }}@end

监听按钮事件

  • 监听长时间按键多次点击(双击卡塔尔事件
    • UIControlEventTouchDownRepeat

UIWindow是Cocoa框架的重大组件之一,全部的UIView都要因而UIWindow来扩充表现,未有UIWindow就从未有过大家的分界面。关于UIWindow的牵线和与其余零构件如UIViewControllerUIView之间的关系,有无数文章已经说得很驾驭了,相信作为二个有资历的iOS开荒者都应有领悟的相比较清楚。假使有一点数不清楚的地点,这里给三个传送门:UIWindow简介

分界面显得

先来走访这些练习项指标app页面效果,如下:

图片 2

QQ.png

  • topVc得到数量刷新,
    • topVc 的 touchsBegin中监听点击, 回调 scrollToTop 的措施

导航条开关突显极度bug

  • 可怜bug现象: 导航条的开关会现身岗位有误.只要push到任何调整器再回届期,导航条按键就显得常常..
    • 导航栏开关显示出现难点,push/pop回来就好了的bug.原因是重写viewWillAppear:时,没调用super 的viewWillAppear:方法或者调用错方法导致.
// 如果super的方法名写错,会出现界面显示的一些小问题
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

对应stack overflow上的这么些难题:“From View Controller” disappears using UIViewControllerContextTransitioning

  • 在那功底上, 还加了个jj_ignoreStatusBar的法门, 首假如制止当前体现vc上还有多个小 vc的景况, 这个时候就在小 vc完结那几个艺术, 屏蔽小 vc 的震慑
    • 解惑:因为jj_ignoreStatusBar那一个主意本人是没有必要在小编分类中落到实处的, 我只是供给在要求掩没的时候可选实现, 所以设计成这种合同格局, 这种设计格局, 我们能够借鉴借鉴, 其实小编亦不是通晓的很深, 相互学习吧!

监听tabBarButton重复点击格局一(使用tabBarButton addTarget方式,本项目选择此格局卡塔尔国

  • 1.在layoutSubviews方法中得到拥有tabBarButton,使用addTarget方式监听tabBarButton的点击

    • UITabBarButton是私有类,不可能采纳,所以打字与印刷其superClass,父类为UIControl,所以能够用addTarget方法监听点击事件
    • 监听tabBarButton重复点击的三种方式
      • 1.记录上三遍点击的tabBarButton的tag值.监听到tabBarButton重复点击,发送通知,布告外部tabBarButton重复点击。
          // ----------------------------------------------------------------------------
          // 重新布局tabBar子控件
          - (void)layoutSubviews
          {
              [super layoutSubviews];
    
              // 1.定义frame属性
              NSInteger count = self.items.count;
              CGFloat itemX = 0;
              CGFloat itemY = 0;
              CGFloat itemW = self.wx_width / (count   1);
              CGFloat itemH = self.wx_height;
    
              // 2.遍历子控件(过滤UITabBarButton), UITabBarButton是私有属性
              NSInteger index = 0;
              for (UIView *view in self.subviews) {
                  // 2.1 过滤UITabBarButton
                  // 可以用两张方式判断
                  // 1.[view isKindOfClass:NSClassFromString(@"UITabBarButton")]
                  // 2.[@"UITabBarButton" isEqualToString:NSStringFromClass([view class])]
                  if ([view isKindOfClass:NSClassFromString(@"UITabBarButton")]) {
    
                      // 2.2 计算x值,并设置frame
                      itemX = index * itemW;
                      view.frame = CGRectMake(itemX, itemY, itemW, itemH);
    
                      // 2.3 监听UITabBarButton的点击,打印view的父类为UIControl
                      // TODO: 使用AddTarget监听tabBarButton的点击
                      UIControl *tabBarButton = (UIControl *)view;
                      tabBarButton.tag = index;
                      [tabBarButton addTarget:self action:@selector(tabBarButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    
                      index  ;
                      // 判断如果是是第二个batBarButton,空一格
                      if (index == 2) {
                          index  ;
                      }
                  }
              }
    
              // 3.设置加号按钮
              self.plusButton.center = CGPointMake(self.wx_width * 0.5, self.wx_height * 0.5);
          }
          // ----------------------------------------------------------------------------
          // 使用记录上一次点击的tabBarButton的tag方法监听tabBarButton的点击
          - (void)tabBarButtonClick:(UIControl *)tabBarButton
          {
              // 使用tabBarButton的tag方法监听
              if (self.selectedTabBarButton.tag == tabBarButton.tag) {
                  // 发送通知
                  [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
              }
    
              // 记录选中tabBarButton
              self.selectedTabBarButton = tabBarButton;
          }
    
    • 2.笔录上一次点击的tabBarButton.监听到tabBarButton重复点击,发送通知,通知外部tabBarButton重复点击。

    注意: 此方式有个bug,在先后刚起步成功,暗中认可选中第0个tabBarButton,借使再点击第0个tabBarButton时,并不曾触发重复点击.原因是因为记录上一次点击的tabBarButton默认为nil.所以使用该办法需在合适的地方上一次点击的tabBarButton赋一个初始值.

      以下参考代码存在以上所提的bug,解决此bug必须给上一次点击的tabBarButton赋一个初始值.
    
      ```objectivec
      // ----------------------------------------------------------------------------
      // 使用记录上一次点击的tabBarButton方法监听tabBarButton的点击
      - (void)tabBarButtonClick:(UIControl *)tabBarButton
      {
          // TODO: 需注意程序刚启动时self.selectedTabBarButton == nil的情况
          if (self.selectedTabBarButton == tabBarButton) {
              [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
          }
          // 记录选中tabBarButton
          self.selectedTabBarButton = tabBarButton;
      }
      ```
    

来自:文兴

2.监听顶端场所栏区域的点击

  • 贯彻思路深入分析

    1. 创办二个品级为UIWindowLevelAlert(最高)的且占据全屏的WXTopWindow,并设置其hidden = NO,让其出示在最顶层.

    2. WXTopWindow(继承UIWindow)中重写
      hitTest:withEvent:方法完毕响应中度( <= 20 卡塔尔(قطر‎也正是
      蓝色区域(状态栏区域)能点击,红色区域( >20 )无法点击

    3. 创建一个WXTopViewController(继承UIViewController),并
      WXTopViewController设置为WXTopWindow的根控制器.
      为WXTopWindow已经设置仅顶部状态栏区域能点击,所以
      重写WXTopViewController的view的touchesBegan方法就可以 仅监听状态栏区域的点击事件.

  • 监听状态栏点击完成解析图
![](https://upload-images.jianshu.io/upload_images/1253159-89a88ae251a2017d.png)

监听状态栏点击实现解析图.png

UIWindow旋转难点

// JJStatusBarExtension.mstatic JJStatusBarExtension *_topWindow;  (instancetype)sharedStatusBarExtension{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _topWindow = [[self alloc] init]; }); return _topWindow;}  (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _topWindow = [super allocWithZone:zone]; }); return _topWindow;}- copyWithZone:zone{ return _topWindow;}  showWithStatusBarClickBlock:block{ if (_topWindow) return; [JJStatusBarExtension sharedStatusBarExtension].windowLevel = UIWindowLevelAlert; [JJStatusBarExtension sharedStatusBarExtension].backgroundColor = [UIColor clearColor]; // 先显示window [JJStatusBarExtension sharedStatusBarExtension].hidden = NO; // 设置根控制器 JJTopViewController *topVc = [[JJTopViewController alloc] init]; topVc.view.backgroundColor = [UIColor clearColor]; topVc.view.frame = [UIApplication sharedApplication].statusBarFrame; topVc.view.autoresizingMask = UIViewAutoresizingFlexibleWidth; topVc.clickedBlock = block; [JJStatusBarExtension sharedStatusBarExtension].rootViewController = topVc;}- hitTest:point withEvent:(UIEvent *)event{ if (point.y > 20) { return nil; } return [super hitTest:point withEvent:event];}

4.监听tabBarButton的重复点击

  • 运维效果图
![](https://upload-images.jianshu.io/upload_images/1253159-7136bdf71151ea33.gif)

监听tabBarButton的重复点击效果图.gif
  • 贯彻思路:

    • 1.构思使用UITabBarButton的addTarget方式监听(可行,轻巧卡塔尔
    • 2.选择UITabBar代理格局监听(经历证,不可行卡塔尔国
      原因: 被三个UITabBarController管理的tabBar的代理是不可能被退换的.倘若三个UITabBar被UITabBarController管理,又重新载入参数UITabBar的代办就能报错,运转时报错: reason: 'Changing the delegate of a tab bar managed by a tab bar controller is not allowed.)
      假定UITabBar未有被UITabBarController管理,是能够改良它的代理的.示例代码
    // UITabBar没有被UITabBarController管理,是可以修改它的代理的
    UITabBar *tabBar = [[UITabBar alloc] init];
    tabBar.delegate = self;
    [self.view addSubview:tabBar];
    
    • 3.选取UITabBarController的代办监听(可行卡塔尔(قطر‎

三、小结

@interface JJTopViewController : UIViewController@property(nonatomic, strong) void(^clickedBlock)();@property(nonatomic, strong) UIViewController * showingVc;@end@implementation JJTopViewController- prefersStatusBarHidden{ return self.showingVc.prefersStatusBarHidden;}- (UIStatusBarStyle)preferredStatusBarStyle{ return self.showingVc.preferredStatusBarStyle;}- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation{ return self.showingVc.preferredStatusBarUpdateAnimation;}- touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ if (self.clickedBlock) { self.clickedBlock(); }}@end

子调整器view懒加载达成

  • 1.落到实处加多index地方对应的子调节器view到scrollView参照他事他说加以考查代码(卡塔尔国

    • 1.依据目录获取子调节器(方法一卡塔尔(قطر‎

    • 2.判断子调控器的view是或不是业已加载过(三种艺术卡塔尔国,假设已经加载过,退出

      • 方法一: childVc.isViewLoaded 判断是否已经加载过
      • 方法二: childVc.view.superview 判断是否有父控件
      • 方法三: childVc.view.window 判断window是否有值
      • 方法四: self.scrollView.subviews containsObject:childVc.view 判定调控器的view是不是在scrollView的子控件数组中.
    • 3.设置要充裕的子调整器view的frame,并加多到scrollView

      • 3.1 设置x值
      • 3.2 需将y设置为0,因为childVc.view是UITableView,UITableView默认的y值是20
      • 3.3 暗中同意UITableView的可观是显示屏的冲天减去它自个儿的y值(20State of Qatar,所以重复设置高度为全方位scrollView的万丈
      • 3.4 加多子调节器的view到scrollView
      // ----------------------------------------------------------------------------
      // 添加index位置对应的子控制器view到scrollView
      - (void)addChildVcViewIntoScrollView:(NSInteger)index
      {
          // 1.根据索引获取子控制器
          UIViewController *childVc = self.childViewControllers[index];
    
          // TODO: 2.判断子控制器的view是否已经加载过,如果已经加载过,退出
          // 方法一: childVc.isViewLoaded 方法二: childVc.view.superview 方法三: childVc.view.window
          if (childVc.isViewLoaded) {
              return;
          }
    
          // 3.设置要添加的子控制器view的frame,并添加到scrollView
          // 3.1 设置x值
          childVc.view.wx_x = index * self.scrollView.wx_width;
          // 3.2 需将y设置为0,因为childVc.view是UITableView,UITableView默认的y值是20
          childVc.view.wx_y = 0;
          // 3.3 默认UITableView的高度是屏幕的高度减去它本身的y值(20),所以重新设置高度为整个scrollView的高度
          childVc.view.wx_height = self.scrollView.wx_height;
          // 3.4 添加子控制器的view到scrollView
          [self.scrollView addSubview:childVc.view];
      }
    

    • 运用偏移量计算索引值(方法二卡塔尔国
      选拔偏移量总结索引,实现增加子调整器view到scrollView,该方法只能通过偏移量来控制要显示那个view,灵活性非常不足(不推荐,因为其依赖偏移量)
      • 通过以下深入分析scrollView的x,y偏移量等于bounds的x,y值.可估算出childVc.view.frame刚好为scrollView.bounds;
      // ----------------------------------------------------------------------------
      // 使用偏移量计算索引,实现添加子控制器view到scrollView,该方法只能通过偏移量来控制要显示那个view,灵活性不够 (不推荐,因为其依赖偏移量)
      - (void)addChildVcViewIntoScrollView
      {
          NSInteger index = self.scrollView.contentOffset.x / self.scrollView.xmg_width;
          UIViewController *childVc = self.childViewControllers[index];
          childVc.view.frame = self.scrollView.bounds;
          [self.scrollView addSubview:childVc.view];
    
      //    childVc.view.xmg_x = self.scrollView.bounds.origin.x;
      //    childVc.view.xmg_y = self.scrollView.bounds.origin.y;
      //    childVc.view.xmg_width = self.scrollView.bounds.size.width;
      //    childVc.view.xmg_height = self.scrollView.bounds.size.height;
    
      //    childVc.view.xmg_x = self.scrollView.contentOffset.x;
      //    childVc.view.xmg_y = self.scrollView.contentOffset.y;
      //    childVc.view.xmg_width = self.scrollView.xmg_width;
      //    childVc.view.xmg_height = self.scrollView.xmg_height;
    
      //    childVc.view.xmg_x = index * self.scrollView.xmg_width;
      //    childVc.view.xmg_y = 0;
      //    childVc.view.xmg_width = self.scrollView.xmg_width;
      //    childVc.view.xmg_height = self.scrollView.xmg_height;
      }
    
  • 2.在最早化增添子调整器的办法中调用增多子调整器的view的诀要设置默认显示第0个子控制器的view.

// ----------------------------------------------------------------------------
// 添加子控制器
- (void)setupAllChildViewController
{
    // 1.添加5个子控制器
    [self addChildViewController:[[WXAllViewController alloc] init]];
    [self addChildViewController:[[WXVideoViewController alloc] init]];
    [self addChildViewController:[[WXVoiceViewController alloc] init]];
    [self addChildViewController:[[WXPictureViewController alloc] init]];
    [self addChildViewController:[[WXWordViewController alloc] init]];

    // 2.获取子控制器数量
    NSInteger count = self.childViewControllers.count;
    // 设置默认显示第0个子控制器的view
    [self addChildVcViewIntoScrollView:0];
    // 3.设置scrollView的滚动范围
    self.scrollView.contentSize = CGSizeMake(count * self.scrollView.wx_width, 0);
}
  • 3.选取开关的tag值作为目录,在下划线动漫推行到位改革scrollView的偏移量,呈现对应子调整器的view.
    • 一旦对应子调节器的view为加多到scrollView,则增添view到scrollView,并更新偏移量突显对应view.
#pragma =======================================================================
#pragma mark - titleButton按钮点击
// ----------------------------------------------------------------------------
// 监听按钮点击
- (void)titleButtonClick:(WXTitleButton *)button
{
    // 切换中状态
    self.selectedButton.selected = NO;
    button.selected = YES;
    self.selectedButton = button;

    // 1.获取索引,按钮的tag值
    NSInteger index = button.tag;

    // 2.执行下划线动画,动画执行完成修改scrollView的偏移量,显示对应子控制器的view
    [UIView animateWithDuration:0.25 animations:^{

        // TODO: 设置下划线的宽度和中心点
        self.underLineView.wx_width = button.titleLabel.wx_width;
        self.underLineView.wx_centerX = button.wx_centerX;

        // 切换到对应的view
        self.scrollView.contentOffset = CGPointMake(self.scrollView.wx_width * index, self.scrollView.contentOffset.y);
    } completion:^(BOOL finished) {
        // 更新偏移量
        CGPoint offset = self.scrollView.contentOffset;
        offset.x = index * self.scrollView.wx_width;
        [self.scrollView setContentOffset:offset];

        // 添加对应子控制器的view
        [self addChildVcViewIntoScrollView:index];
    }];
}

keyWindow是钦点的用来收纳键盘以至非触摸类的新闻,何况程序中每三个时时只好有叁个window是keyWindow

  • 为了保险系统原有的对 statusBar 的支配仍有效, 大家就务须在 topVc 里能获得当下显示 Vc对 statusBar 的安装数据
  • 那边大家新建八个 viewController 的分类, 在此其间落成相关操作, 这里要弄到运维时的置换方法, 因为大家要阻止 viewController 的 viewWillAppear 方法
  • 阻拦到后, 将眼下来得调整器, 通过 topWindow 传给 topVc, 那样就会在 topVc 里面取得近些日子体现调整器里对 statusBar 的舍子数据, 然后 topVc 在调用setNeedsStatusBarAppearanceUpdate方法, 刷新状态栏的显示
  • 那边还对即便设置了statusBar 展现动漫的景况作了判别

检索全体的scrollView

  • 1.首先实现UIView的归类方法: 判定方式调用者和view(本项目作用指keyWindow卡塔尔(قطر‎是还是不是重叠
// ----------------------------------------------------------------------------
// 判断方法调用者和view是否重叠
- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果传入的参数是nil,则表示为[UIApplication sharedApplication].keyWindow
    if (view == nil) {
        view = [UIApplication sharedApplication].keyWindow;
    }

    // 都统一转换成window坐标系,并判断是否重叠,返回判断结果
    CGRect rect1 = [self convertRect:self.bounds toView:nil];
    CGRect rect2 = [view convertRect:view.bounds toView:nil];
    return CGRectIntersectsRect(rect1, rect2);
}
  • 2.查寻找window里面包车型客车有着scrollView

    • 1.论断是还是不是在keyWindow的约束内(不跟window重叠卡塔尔国,如若不在,直接退出
    • 2.遍历view的全体子控件和子控件的子控件,此处for循环会退出,所以递归调用会退出
    • 3.论断借使scrollView,直接重回
    • 4.滚动scrollView到最顶上部分
      • 方法一: 获取scrollView,将scrollView的偏移量y值设置为负的内边距顶部值: -scrllView.contentInset.top
        UIScrollView *scrllView = (UIScrollView *)view;
        CGPoint offset = scrllView.contentOffset;
        offset.y = -scrllView.contentInset.top;
        [scrllView setContentOffset:offset animated:YES];
    
    - 方法二: 让`scrollView移动到其内容的最顶部`.
    
      ```objectivec
      [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
      ```
    
    • 使用: 在didFinishLaunchingWithOptions:方法中调用UIView的分类对象方法,searchAllScrollViewsInView:方法,传入application.keyWindow参数,判断scrollView是否在keyWindow中,如果在keyWindow中,则滚动scrollView到顶部.

    • 金玉满堂当前展现的scrollView/tableView点击顶部状态栏区域滚动到scrollView/tableView最顶部参照代码
      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
          // 1.创建window
          self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
          // 2.设置window的根控制器
          self.window.rootViewController = [[WXAdViewController alloc] init];
          // init -> initWithNibName -> 1.判断有没有指定NibName 2.判断有没有跟控制器同名的xib,就会去加载 3.判断下有没有不带controller的xib 4.创建一个clearColor透明的View
    
          // 3.让window成为主窗口,并显示
          [self.window makeKeyAndVisible];
    
          // 4.添加topWindow
          [WXTopWindow showWithStatusBarClickBlock:^{
              [self searchAllScrollViewsInView:application.keyWindow];
          }];
    
          return YES;
      }
    
      // ----------------------------------------------------------------------------
      // 查找出view里面的所有scrollView
      - (void)searchAllScrollViewsInView:(UIView *)view
      {
          // 1.判断是否在keyWindow的范围内(不跟window重叠),如果不在window范围内,直接退出
          if (![view wx_intersectWithView:nil]) {
              return;
          }
    
          // 2.遍历view的所有子控件和子控件的子控件,此处for循环会退出,所以递归调用会退出
          for (UIView *subview in view.subviews) {
              [self searchAllScrollViewsInView:subview];
          }
    
          // 3.判断如果scrollView,直接返回
          if (![view isKindOfClass:[UIScrollView class]]) {
              return;
          }
    
          // 4.滚动scrollView到最顶部
          UIScrollView *scrllView = (UIScrollView *)view;
          CGPoint offset = scrllView.contentOffset;
          offset.y = -scrllView.contentInset.top;
          [scrllView setContentOffset:offset animated:YES];
      }
    

当oldVC调用了presentViewController模态弹出了viewController的时候,oldVC在iOS7上会被保释,可是在iOS8上述的装置并从未被放飞。这么些是自己实践得出的结果,作者认为恐怕的缘故是出于每种viewController都有那四个只读属性presentedViewController和presentingViewController,代表弹出它和它弹出的viewController,或者是那五个属性引致了巡回援用,所以得不到释放。所以,当我们要切换一个一度present了的调控器,大家供给把该调节器逐层dismissViewController(因为present之后还足以三回九转present卡塔尔,直到dismiss到根视图。为了实现那一个功能,作者写了一个简练的extension。

  • 当今有好多 app 都有这些须求, 点击 statusBar, tableView/collectionview 内容滚动到最上端
  • iOS其实已经济合作并了这种意义, 但是它必须要在日前调节器之下独有一个(只能有一个卡塔尔 scrollView 可能其子类的时候本领有用, 假如你 tableView又有八个 scrollView 的标题栏, 那它自带的这么些作用你是用持续的
  • 本文详细探索了哪些扩展该意义, 使其能共通用
  • 代码详见本人 GitHub/quickCode/JJStatusBarExtension

@(iOS 项目实战卡塔尔(قطر‎[类型实战]

二、潜在的坑

子调节器监听tabBarButton重复点击公告

  • 在对应子调整器的viewDidLoad方法中监听tabBarButton重复点击通告,实施响应操作
- (void)viewDidLoad {
    [super viewDidLoad];

    WXFunc();

    self.view.backgroundColor = WXRandomColor;
    self.tableView.contentInset = UIEdgeInsetsMake(WXNavMaxY   WXTitlesViewH, 0, WXTabBarH, 0);

    // 1.监听通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarButtonDidRepeatClick) name:WXTabBarButtonDidRepeatClickNotification object:nil];
}

#pragma =======================================================================
#pragma mark - 监听tabBarButton重复点击通知
- (void)tabBarButtonDidRepeatClick
{
    NSLog(@"%@: 重复点击,执行下拉刷新", [self class]);
}

- (void)dealloc
{
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

transitionContext.completeTransition(true)UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

  • 率先知道多少个典型:
    • 早前可以用 application 来校勘 statusBarHidden 和 style, 但 iOS9后, application 的艺术过期了
    • 在调节器中, 调控器能够决定 statusBar 的 hidden 和 style, 首先知道有个别, 系统是怎么来调节statusBar的, 有几许得以作证到, 就是每一回 view 将在展现的时候, 系统都会调用下边包车型客车章程, 以此来决定 statusBar 状态
    • 由此setNeedsStatusBarAppearanceUpdate能重复调用上边包车型大巴多个方法
    • statusBar 只好由顶层调节器来决定, 所以大家加了 topVc 后, 其余调整器里对 statusBar 状态的改变是没用的,因为顶层调整器会覆盖它
  • 依附地点的深入分析, 大家也在 view 将在彰显的时候来操作, 在 viewWillAppear 方法来想艺术

一、使用情形

// AppDelegate.m- application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {// NSLog(@"%@", self.window); [JJStatusBarExtension showWithStatusBarClickBlock:^{// [self test]; // 如果想要 app的所有界面都有点击 statusBar 滚到最前面, 则调用下面这个方法 [JJStatusBarExtension scrollToTopInsideView:self.window]; }]; return YES;}

  scrollToTopInsideView:view{ CGRect viewRect = [view convertRect:view.bounds toView:nil]; if (!CGRectIntersectsRect([UIApplication sharedApplication].keyWindow.frame, viewRect)) { return; } for (UIView *subview in view.subviews) { [self scrollToTopInsideView:subview]; } if (![view isKindOfClass:[UIScrollView class]]) { return; } UIScrollView *scrollView = (UIScrollView *)view; // CGPoint contentOffset = scrollView.contentOffset; // contentOffset.y = - scrollView.contentInset.top; // [scrollView setContentOffset:contentOffset animated:YES]; [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];}

监听tabBarButton重复点击格局二(使用UITabBarController的代理方式State of Qatar

  • 1.在广告分界面将在跳转到TabBarController的地点,为TabBarController设置代理代理对象是·[UIApplication sharedApplication].delegate,无法用广告调整器做为代理对象,因为只要根调节器切换来TabBarController时,广告控制器就会被销毁.
// ----------------------------------------------------------------------------
// 监听点击跳过按钮
- (IBAction)jump {

    // 关闭定时器
    [self.timer invalidate];

    WXTabBarController *tabBarVc = [[WXTabBarController alloc] init];
    tabBarVc.delegate = (id<UITabBarControllerDelegate>)[UIApplication sharedApplication].delegate;
    [UIApplication sharedApplication].keyWindow.rootViewController = tabBarVc;
}
  • 2.在APPDelegate.m文件中,让AppDelegate遵守UITabBarControllerDelegate协议,并实现tabBarController:didSelectViewController:代办方法,监听TabBarController选中了哪个调整器.
// ----------------------------------------------------------------------------
// 监听tabBarController当前选中哪个控制器
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    // selectedVc用于存放TabBarController上一次选中的控制器
    static UIViewController *selectedVc = nil;

    // 设置初始化上一次选中的控制器为tabBarController的第0个子控制器.
    if (selectedVc == nil) {
        selectedVc = tabBarController.childViewControllers[WXDefaultVcIndex];
    }

    // 如果上一次选中的控制器和当前选中控制器一样,表示重复点击,发送通知
    if (selectedVc == viewController) {
        [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
    }

    // 更新上一次选中控制器
    selectedVc = viewController;
}

忆起一下,写那篇小说的贰个原因便是自家发掘介绍UIWindow的骨子里运用的篇章超级少,所以在那计算一下广阔的采纳处境和方法,还应该有正是选取UIWindow蒙受的坑。那篇小说的剧情大多数是自己在实行中结合UIWindow的法规总括出的一对经验,自个儿留一篇记录加深印象,同一时间也期望对大家有所帮助!

UIWindow相关知识点

  • window展现要是设置hidden = NO就可以显示了,不需要像UIView要添加到父控件view上.

  • window品级越高,越显得在顶层.

    • UIWindowLevelAlert -> UIWindowLevelStatusBar ->UIWindowLevelNormal
    • 设若等第相像,越前边显示在越顶层.
  • 干什么一定要- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions措施中给window增添根调控器?
    • 要是有设置window的根调节器,window里面的内容才会跟随旋转.window本人是不会旋转的.

也才那样带给二个主题素材正是我们的newWindow把下层window的触摸事件都挡住了。那点在自定义AlertView时恐怕是大家想要的结果,终究大家不想在alert弹框时客商还是能够做别的的操作,可是在录音的时候,大家盼望顾客还是能够做别的的操作,那时准确的做法正是继承UIWindow,重载hitTest方法。

  • 06.档期的顺序实战 百思不得姐 精髓子调控器view懒加载,监听状态栏点击,tabBarButton重复点击监听
  • 【相关知识点补充】
    • UIScrollView动漫滚动情势
    • UIScrollView监听停止滚动
    • 坐标系转变
    • 决断是或不是重叠
    • 导航条开关展现格外bug
    • 状态栏点击事件
    • UIWindow相关知识点
    • 监听按键事件
  • 1.精髓子调节器view懒加载
    • 子调控器view懒加载实现
  • 2.监听最上部场所栏区域的点击
    • 监听顶上部分状态栏的点击事件的兑现
  • 3.景观栏点击调整tableView滚动
    • 追寻全数的scrollView
  • 4.监听tabBarButton的重新点击
    • 监听tabBarButton重复点击情势一(使用tabBarButton addTarget方式,本项目接受此方式卡塔尔(قطر‎
    • 监听tabBarButton重复点击方式二(使用UITabBarController的代办格局卡塔尔(قطر‎
    • 子调整器监听tabBarButton重复点击文告

成百上千时候大家的App独有多少个用来突显大家界面的UIWindow,并且以此UIWindow日常是在您创制工程的时候自动生成的,那就让大家纵然掌握UIWindow的基本原理,也会少之甚少的触发到UIWindow的莫过于使用。当App复杂度慢慢增高的时候,一些一定的场所使用UIWindow会获得很好的消除。所以,那篇文章就介绍UIWindow的行使情形和章程,还包括一些施行中潜在的部分坑。Let's Go!

【相关知识点补充】

UIWindow不显示View

- prefersStatusBarHidden{ return YES;}- (UIStatusBarStyle)preferredStatusBarStyle{ return UIStatusBarStyleDefault;}- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation{ return UIStatusBarAnimationFade;}

动静栏点击事件

  • 系统暗中同意点击状态栏的时候,scrollView滚动到最上端

    • 前提是UIScrollView的scrollsToTop属性为YES时,并且屏幕上只有一个scrollView时工夫用.
    • UIScrollView的scrollsToTop属性默认为YES.
  • 要想window里面包车型客车从头到尾的经过跟随荧屏旋转,那么必得安装window的rootViewController

  • 状态栏的体制和突显掩盖由最顶层window的控制器决定

    • - (BOOL)prefersStatusBarHidden : 呈现和藏身
    • - (UIStatusBarStyle)preferredStatusBarStyle :灰黄和蓝绿
  • 旋转状态栏消失的缘由是最上面window控制器没实现状态栏preferStatusBarHidden方法.


上述代码把原来的keyWindow暂存,把大家新建的window设置成keyWindow,在退出分界面时上涨原先的keyWindow,销毁viewController和newWindow。由于keyWindow的官方概念是

  • 作者: Liwx
  • 邮箱: 1032282633@qq.com

extension UIScreen {varcompatibleBounds:CGRect {//iOS7 mainScreen bounds 不随设备旋转varrect = self.boundsifNSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0 {          let orientation = UIApplication.sharedApplication().statusBarOrientationiforientation.isLandscape{              rect.size.width= self.bounds.heightrect.size.height= self.bounds.width}      }  return rect  }}

1.精粹子调控器view懒加载

classModalViewController:UIViewController{varnewWindow:UIWindow?varprevWindow:UIWindow?funcshow(State of Qatar{//展现分界面ifnewWindow ==nil{//暂存原本的keyWindowself.prevWindow =UIApplication.sharedApplication(卡塔尔(قطر‎.keyWindow//新建UIWIndowletuiwindow =UIWindow(frame:UIScreen.mainScreen(卡塔尔国.bounds卡塔尔(قطر‎          uiwindow.rootViewController =selfuiwindow.makeKeyAndVisible(卡塔尔国self.newWindow = uiwindow      }  }funcdismiss(State of Qatar{//退出界面self.prevWindow?.makeKeyAndVisible(卡塔尔(قطر‎self.newWindow?.rootViewController =nilself.newWindow =nil}}

监听顶端状态栏的点击事件的实现

  • 监听状态栏点击事件完成

    • 1.自定义WXTopWindow(继承UIWindow)
    • 2.提供多少个参数为block的的类方法,供外界调用.block会在状态栏区域被点击的时候调用
        (void)showWithStatusBarClickBlock:(void (^)())block;
    
    • 3.在WXTopWindow(继承UIWindow)中重写hitTest:withEvent:方法福寿康宁只响应状态栏区域的点击.
    • 4.showWithStatusBarClickBlock:类方式的落实

      • 1.判断假设该window已经创立,没有供给再成立,因为window整个应用程序只供给叁个,没有须求另行制造
      • 2.加多window到状态栏区域,window私下认可填充整个显示器,所以不用设置frame
      • 3.装置window的优先级为最高,比状态栏的预先级高
        UIWindowLevelAlert(高) UIWindowLevelStatusBar(中) UIWindowLevelNormal(低,默认)
      • 4.装置window的背景象为透明色
      • 注意: 需先显示window再设置根控制器.
      • 5.创立WXTopViewController并安装背景象为clearColor
      • 6.设置旋转时只拉伸宽度,不然会产出view的情状栏区域旋转消失或变大问题.
        调控器的view默许是长宽都自动拉伸,此处只需拉伸宽度,无需拉伸高度.
      • 7.将block传递给调节器管理,当调节器的view的图景栏区域被点击,调用block
      • 8.设置WXTopWindow的根调节器为WXTopViewController.独有设置window的根控制器,window里面的内容才会跟随旋转.window本身不会旋转.

    • 5.在WXTopViewController调整器中贯彻touchesBegan:方法监听最上部状态栏区域的点击.
      • touchesBegan:方法监听到顶端意况栏区域的点击,调用WXTopWindow传递过来的block.
    • 注意: WXTopViewController必得分明钦定状态栏显示,不然会冒出旋转后状态栏未有.

    • 行使格局: 调用showWithStatusBarClickBlock:就能够兑现监听状态栏区域的点击.
    [WXTopWindow showWithStatusBarClickBlock:^{
        NSLog(@"点击了顶部状态栏区域");
    }];
    

本条难题严刻上说不是UIWindow自个儿的主题素材,而是出今后运用转场动漫UIViewControllerContextTransitioning时,在动漫甘休之后,待展现的viewController.view没有被机关增加到UIWindow上,招致展现为黑屏或空白,那是Cocoa本身的bug。消除的办法正是在转场动漫截至今后手动把view加多到keyWindow上。

UIScrollView监听结束滚动

  • 监听UIScrollView结束滚动的二种艺术

    • 方式一: 当用户停止拖拽scrollView的时候调用(手松开)
    • 方式二: 当scrollView停止滚动的时候调用
    • 方式三: 当scrollView停止滚动的时候调用.前提:当使用setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生了滚动动画
    • 方式四: 使用UIView animateWithDuration:animations:completion:方法,animations block内部改革了scrollView的contentOffset的值,在completion 的block监听scrollView滚动实现.

    • 监听UIScrollView截至滚动的两种完成参谋代码
      #pragma mark - <UIScrollViewDelegate>
    
      // ----------------------------------------------------------------------------
      // 方式一
      /**
       *  当用户停止拖拽scrollView的时候调用(手松开)
       *  如果参数decelerate为YES,手松开后会继续滚动,滚动完毕后会调用scrollViewDidEndDecelerating:代理方法
       *  如果参数decelerate为NO,手松开后不再滚动,马上静止,也不会调用scrollViewDidEndDecelerating:代理方法
       */
      - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
      {
          if (decelerate) {
              NSLog(@"用户停止拖拽scrollView,scrollView会继续滚动");
          } else {
              NSLog(@"用户停止拖拽scrollView,scrollView不再滚动");
          }
      }
    
      // ----------------------------------------------------------------------------
      // 方式二
      /**
       *  当scrollView停止滚动的时候调用
       *  前提:人为手动让scrollView产生滚动
       */
      - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
      {
          NSLog(@"用户停止拖拽scrollView后滚动完毕");
      }
    
      // ----------------------------------------------------------------------------
      // 方式三
      /**
       *  当scrollView停止滚动的时候调用
       *  前提:当使用setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生了滚动动画
       */
      - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
      {
          NSLog(@"通过setContentOffset:animated:或者scrollRectToVisible:animated:方法让scrollView产生滚动动画,然后停止滚动");
      }
    
      // ----------------------------------------------------------------------------
      // 方式四
      [UIView animateWithDuration:1.0 animations:^{
          self.scrollView.contentOffset = CGPointMake(150, 150);
          [self.scrollView setContentOffset:CGPointMake(150, 150)];
      } completion:^(BOOL finished) {
          NSLog(@"减速完毕----");
      }];
    

The key window is the one that is designated to receive keyboard and other non-touch related events. Only  one window at a time may be the key window.

目录

let newVC= SomeViewController()iflet window = UIApplication.sharedApplication().keyWindow{varoldVC = window.rootViewControllerwindow.rootViewController= vc  oldVC = nil}

3.景况栏点击调控tableView滚动

充实snapView的指标是防卫闪屏,因为大家要在逐层dismiss到根视图后,再切换viewController,那就能够变成顶层视图到根视图之间的视图一闪而过。具体的实今世码注释部分已经注解的可比详细了,就不再赘述。

判定是不是重叠

  • 使用bool CGRectIntersectsRect(CGRect rect1, CGRect rect2)函数判断rect1与rect2

    • 注意: 该函数的参数rect1和rect2必须处于同一坐标系. 设若在分裂坐标系,必需先举行坐标系转变,再用此方法决断四个控件是或不是重叠.

    • 看清是还是不是重叠仿效代码
      - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
      {
          CGRect rect1 = [self.blueView convertRect:self.blueView.bounds toView:nil];
          CGRect rect2 = [self.redView convertRect:self.redView.bounds toView:nil];
          BOOL result = CGRectIntersectsRect(rect1, rect2);
          NSLog(@"%zd", result);
      }
    
  • 封装UIView分类,完结判定是或不是重叠的法子

- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果传入的参数是nil,则表示为[UIApplication sharedApplication].keyWindow
    if (view == nil) view = [UIApplication sharedApplication].keyWindow;

    // 都统一转换成window坐标系,并判断是否重叠,返回判断结果
    CGRect rect1 = [self convertRect:self.bounds toView:nil];
    CGRect rect2 = [view convertRect:view.bounds toView:nil];
    return CGRectIntersectsRect(rect1, rect2);
}

安装window.frame为比UIScreen.mainScreen(卡塔尔(قطر‎.bounds小的值,举个例子录音的提醒框,恐怕很自然会设置成statusBar.frame,但那大概会影响UIWindow的转动。为了防止那么些难点,我们平常的话都会把window.frame设置成UIScreen.mainScreen(卡塔尔国.bounds。

extensionUIWindow{varsafeRootViewController:UIViewController? {get{returnself.rootViewController    }set{ifletprevRootVC =self.rootViewController {// Get TopMost VCvartopMostVC:UIViewController! = prevRootVCwhile(topMostVC.presentedViewController !=nil卡塔尔(قطر‎ {                topMostVC = topMostVC.presentedViewController            }varwindow:UIWindow?// 增添snapView幸免闪屏varsnapView:UIView?iftopMostVC != prevRootVC {                snapView = topMostVC.view.snapshotViewAfterScreenUpdates(false)                snapView?.frame =UIScreen.mainScreen().compatibleBoundsself.addSubview(snapView!State of Qatar            }funcdismissToRootVC(topMostVC:UIViewController,complete:(卡塔尔->Void卡塔尔国{// 获取present topMostVC的VCifletpresentingVC = topMostVC.presentingViewController {                    topMostVC.dismissViewControllerAnimated(false卡塔尔国 {                        dismissToRootVC(presentingVC,complete: complete卡塔尔(قطر‎                    }                }else{// 表达topMostVC未有被present,已经dismiss到最尾部了complete(State of Qatar                }            }            dismissToRootVC(topMostVC卡塔尔(قطر‎{self.rootViewController = newValue// 延迟实践,等待UI更新分界面self.performSelector(#selector(UIWindow.delay(_:)), withObject: snapView, afterDelay:0)            }        }else{self.rootViewController = newValue        }    }}funcdelay(snapView:AnyObject?){    (snapViewas?UIView)?.removeFromSuperview()}}

虽说UIWindow非常适用于上述提到的三种情景,但总体来讲,大家选择到不荒谬组件的效能依然要比UIWindow高出不菲,这也就表示当你在行使UIWindow进度中蒙受了坑时,恐怕相比难找到相应的杀绝办法。这里自个儿也记录一下本人在采用UIWindow的长河中踩的部分坑,都是归于相比较掩盖何况资料很少的,希望能帮忙到我们。

Debug Session.png

那类用法没有须要大家创造新的UIWindow,而是当大家相遇复杂的页面切换时,调用当前视图的截屏方法snapshotViewAfterScreenUpdates获取显示屏截图snapView,再调用window.addSubview方法加多snapView,到达覆盖下层的页面切换效果,防止闪屏,页面切换截至后removeFromSuperview。这种格局在上面介绍的UIWindow切换rootViewController引致力不能及自由中有利用到,具体的行使可今后后后续看。

classThroughView:UIView{// 在您的xib/storyboard/代码里,设置须要穿透的View为ThroughView}classProgressWindow:UIWindow{overridefunchitTest(point: CGPoint, with伊夫nt event: UIEvent?State of Qatar->UIView? {lethitView =super.hitTest(point, with伊芙nt: eventState of Qatar//如果hitView是必要事件穿透的ThoughView则赶回niliflet_= hitViewas?ThroughView{returnnil}else{returnhitView      }returnhitView  }}

本人还未找到合理的阐述,可是作者以为说不允许的来由是,旋转事件的派发流程是:UIApplication->UIWindow->UIViewController,UIWindow能够自身管理自个儿的团团转难点,所以无需大家再做额外的操作。

接下去就大致说圣元下什么利用UIWindow,平日我们不会去一贯继承UIWindow,因为大家无需转移进行它的用场而是独有使用它。日常把新建的window作为viewController的强持有属性,代码如下

纵然oldVC是UINavigationController或许UITabbarController之内的容器类,oldVC和上述相像,也会被放飞。然而那个时候借使oldVC已经push进了多少个viewController,那几个viewController会被释放么?答案是无可置疑的。因为当viewController被容器类管理时,只有容器类会持有viewController的援引,当容器类被销毁时遗失援用也会被放走了。

图片 3

这种景况是UIWindow最注重的施用情状之一,也是Cocoa本人对UIWindow的运用境况之一,譬如Alert提醒框、自定义键盘、Loading框等。UIKit中的UIAlertView和弹出键盘都以新建了一个UIWindow,那或多或少方可在Debug格局下设置断点,再点击Debug View Hierarchey查看UIView的档案的次序构造,清晰的看来日前利用有多少个UIWindow

Debug View Hierarchy.png

UIWindow切换rootViewController引致不能够自由

可能在其它分界面弹出的视图

由此在这里地,你可能认为,假若使用要宽容iOS7,那么相应把window.bounds设置为UIScreen.mainScreen(State of Qatar.compatibleBounds。但实在这里样做是错误的,正确的做法是刚刚是最原始的做法,即设置成UIScreen.mainScreen(卡塔尔(قطر‎.bounds。

正规的事态下,oldVC就算是贰个总结的viewController,oldVC当然是被放出了,因为早就远非引用指向oldVC。

那类使用情状也正如宽泛,比方录音、仿Assistive Touch、悬浮窗等。以录音为例,录音步入后台后,大概会在statusBar上出示二个花青的正在录音的提示框,这种功能用UIWindow来落到实处就可怜自然,因为它不会听得多了就能够说的详细到其余分界面,始终在协调的newWindow里做相应的操作,不用关注主窗口的页面切换。完成方式实在和率先种选择情况相似,也是强持有newWindow的实例,然则有三个索要小心的地点:

独立的、跨分界面使用的服务

貌似的话,我们成立一个UIWindow的时候都会把它的bounds设置成主显示屏大小UIScreen.mainScreen(卡塔尔(قطر‎.bounds

那正是说,那个时候oldVC有未有被保释吧?

但大家明白,在iOS7上,UIScreen.mainScreen(卡塔尔(قطر‎.bounds不会随着设备旋转方向而纠正,iOS8以上则会随设备旋转方向退换,即横屏和竖屏状态下,宽和高会交换。所以,常常为了包容iOS7获取主显示器的bounds的不错大小,我们会给UIScreen加七个extension/category

当大家必要从其余界面跳转到一个viewController时,而且释放本来的viewController,假设利用普通的presentViewController,原本的viewController并未自由。此时能够透过简单的改造window.rootViewController达到这么些意义。

图中的UITextEffectsWindow指的就是键盘的window。

snapView =self.view.snapshotViewAfterScreenUpdates(false)snapView.frame =self.view.frameself.window?.addSubview(snapView)

classModalViewController:UIViewController{varnewWindow:UIWindow!funcshow(卡塔尔{//呈现分界面ifnewWindow ==nil{//新建UIWIndowletuiwindow =UIWindow(frame:UIScreen.mainScreen(State of Qatar.bounds卡塔尔(قطر‎          uiwindow.rootViewController =selfuiwindow.hidden =falseuiwindow.backgroundColor =UIColor.clearColor(State of Qatarself.newWindow = uiwindow      }  }funcdismiss(State of Qatar{//退出分界面self.newWindow.rootViewController =nilself.newWindow =nil}}

TAG标签:
版权声明:本文由美高梅网投平台发布于美高梅简介,转载请注明出处:完美实现点击,项目实战