美文网首页iOSiOS入门Demo
06.项目实战 百思不得姐 精华子控制器view懒加载,监听状态

06.项目实战 百思不得姐 精华子控制器view懒加载,监听状态

作者: Liwx | 来源:发表于2016-01-28 20:37 被阅读1164次

    @(iOS 项目实战)[项目实战]


    目录

    • 06.项目实战 百思不得姐 精华子控制器view懒加载,监听状态栏点击,tabBarButton重复点击监听
    • 【相关知识点补充】
      • UIScrollView动画滚动方式
      • UIScrollView监听停止滚动
      • 坐标系转换
      • 判断是否重叠
      • 导航条按钮显示异常bug
      • 状态栏点击事件
      • UIWindow相关知识点
      • 监听按钮事件
    • 1.精华子控制器view懒加载
      • 子控制器view懒加载实现
    • 2.监听顶部状态栏区域的点击
      • 监听顶部状态栏的点击事件的实现
    • 3.状态栏点击控制tableView滚动
      • 查找所有的scrollView
    • 4.监听tabBarButton的重复点击
      • 监听tabBarButton重复点击方式一(使用tabBarButton addTarget方式,本项目使用此方式)
      • 监听tabBarButton重复点击方式二(使用UITabBarController的代理方式)
      • 子控制器监听tabBarButton重复点击通知

    【相关知识点补充】

    UIScrollView动画滚动方式

    • 1.使用setContentOffset:animated:方法实现动画滚动.
    • 2.scrollRectToVisiable:animated:滚动一块特定的区域到scrollView显示.如果该区域已经在scrollView中可见,调用此方法没反应.
      以上两个方法animated为YES才能实现动画滚动.

    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(@"减速完毕----");
        }];
      

    坐标系转换

    • 计算控件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中的x\y\width\height
        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];
    
    • 坐标系转换图解


      坐标系转换图解.png

    判断是否重叠

    • 使用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);
    }
    

    导航条按钮显示异常bug

    • 异常bug现象: 导航条的按钮会出现位置有误.只要push到其他控制器再返回时,导航条按钮就显示正常..
      • 导航栏按钮显示出现问题,push/pop回来就好了的bug.原因是重写viewWillAppear:时,没调用super 的viewWillAppear:方法或者调用错方法导致.
    // 如果super的方法名写错,会出现界面显示的一些小问题
    - (void)viewWillAppear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
    }
    

    状态栏点击事件

    • 系统默认点击状态栏的时候,scrollView滚动到顶部

      • 前提是UIScrollView的scrollsToTop属性为YES时,并且屏幕上只有一个scrollView时才能用.
      • UIScrollView的scrollsToTop属性默认为YES.
    • 要想window里面的内容跟随屏幕旋转,那么必须设置window的rootViewController

    • 状态栏的样式和显示隐藏由最顶层window的控制器决定

      • - (BOOL)prefersStatusBarHidden : 显示和隐藏
      • - (UIStatusBarStyle)preferredStatusBarStyle :白色和黑色
    • 旋转状态栏消失的原因是最上面window控制器没实现状态栏preferStatusBarHidden方法.


    UIWindow相关知识点

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

    • window级别越高,越显示在顶层.

      • UIWindowLevelAlert -> UIWindowLevelStatusBar ->UIWindowLevelNormal
      • 如果级别一样,越后面显示在越顶层.
    • 为什么一定要- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中给window添加根控制器?
      • 只要有设置window的根控制器,window里面的内容才会跟随旋转.window本身是不会旋转的.

    监听按钮事件

    • 监听短时间按钮多次点击(双击)事件
      • UIControlEventTouchDownRepeat

    1.精华子控制器view懒加载

    子控制器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值(20),所以重新设置高度为整个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];
        }];
    }
    

    2.监听顶部状态栏区域的点击

    • 实现思路分析

      1. 创建一个级别为UIWindowLevelAlert(最高)的且占据全屏的WXTopWindow,并设置其hidden = NO,让其显示在最顶层.
      1. WXTopWindow(继承UIWindow)中重写
        hitTest:withEvent:方法实现响应高度( <= 20 )也就是
        蓝色区域(状态栏区域)能点击,红色区域( >20 )不能点击
      1. 创建一个WXTopViewController(继承UIViewController),并
        WXTopViewController设置为WXTopWindow的根控制器.
        为WXTopWindow已经设置仅顶部状态栏区域能点击,所以
        重写WXTopViewController的view的touchesBegan方法就可以 仅监听状态栏区域的点击事件.
    • 监听状态栏点击实现解析图


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

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

    • 监听状态栏点击事件实现

      • 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(@"点击了顶部状态栏区域");
      }];
      

    3.状态栏点击控制tableView滚动

    查找所有的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];
        }
      

    4.监听tabBarButton的重复点击

    • 运行效果图


      监听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的代理监听(可行)

    监听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;
        }
        ```
      

    监听tabBarButton重复点击方式二(使用UITabBarController的代理方式)

    • 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;
    }
    

    子控制器监听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];
    }
    

    相关文章

      网友评论

      • feng_dev:求回复啊
      • feng_dev:大神,有个问题,我在UITabbar controller delegate 的 代理方法 里面 didselect View controller 这个 方法写在 我 自己定义的 UItab Controller 好 还是 写在 他的 子控制器 好啊

      本文标题:06.项目实战 百思不得姐 精华子控制器view懒加载,监听状态

      本文链接:https://www.haomeiwen.com/subject/erdjkttx.html