06.項目實戰(zhàn) 百思不得姐 精華子控制器view懶加載,監(jiān)聽狀態(tài)欄點擊,tabBarButton重復(fù)點擊監(jiān)聽

@(iOS 項目實戰(zhàn))[項目實戰(zhàn)]


目錄

  • 06.項目實戰(zhàn) 百思不得姐 精華子控制器view懶加載,監(jiān)聽狀態(tài)欄點擊,tabBarButton重復(fù)點擊監(jiān)聽
  • 【相關(guān)知識點補充】
    • UIScrollView動畫滾動方式
    • UIScrollView監(jiān)聽停止?jié)L動
    • 坐標(biāo)系轉(zhuǎn)換
    • 判斷是否重疊
    • 導(dǎo)航條按鈕顯示異常bug
    • 狀態(tài)欄點擊事件
    • UIWindow相關(guān)知識點
    • 監(jiān)聽按鈕事件
  • 1.精華子控制器view懶加載
    • 子控制器view懶加載實現(xiàn)
  • 2.監(jiān)聽頂部狀態(tài)欄區(qū)域的點擊
    • 監(jiān)聽頂部狀態(tài)欄的點擊事件的實現(xiàn)
  • 3.狀態(tài)欄點擊控制tableView滾動
    • 查找所有的scrollView
  • 4.監(jiān)聽tabBarButton的重復(fù)點擊
    • 監(jiān)聽tabBarButton重復(fù)點擊方式一(使用tabBarButton addTarget方式,本項目使用此方式)
    • 監(jiān)聽tabBarButton重復(fù)點擊方式二(使用UITabBarController的代理方式)
    • 子控制器監(jiān)聽tabBarButton重復(fù)點擊通知

【相關(guān)知識點補充】

UIScrollView動畫滾動方式

  • 1.使用setContentOffset:animated:方法實現(xiàn)動畫滾動.
  • 2.scrollRectToVisiable:animated:滾動一塊特定的區(qū)域到scrollView顯示.如果該區(qū)域已經(jīng)在scrollView中可見,調(diào)用此方法沒反應(yīng).
    以上兩個方法animated為YES才能實現(xiàn)動畫滾動.

UIScrollView監(jiān)聽停止?jié)L動

  • 監(jiān)聽UIScrollView停止?jié)L動的四種方法

    • 方式一: 當(dāng)用戶停止拖拽scrollView的時候調(diào)用(手松開)
    • 方式二: 當(dāng)scrollView停止?jié)L動的時候調(diào)用
    • 方式三: 當(dāng)scrollView停止?jié)L動的時候調(diào)用.前提:當(dāng)使用setContentOffset:animated:或者scrollRectToVisible:animated:方法讓scrollView產(chǎn)生了滾動動畫
    • 方式四: 使用UIView animateWithDuration:animations:completion:方法,animations block內(nèi)部修改了scrollView的contentOffset的值,在completion 的block監(jiān)聽scrollView滾動完成.

    • 監(jiān)聽UIScrollView停止?jié)L動的幾種實現(xiàn)參考代碼
      #pragma mark - <UIScrollViewDelegate>
      
      // ----------------------------------------------------------------------------
      // 方式一
      /**
       *  當(dāng)用戶停止拖拽scrollView的時候調(diào)用(手松開)
       *  如果參數(shù)decelerate為YES异雁,手松開后會繼續(xù)滾動锁蠕,滾動完畢后會調(diào)用scrollViewDidEndDecelerating:代理方法
       *  如果參數(shù)decelerate為NO将饺,手松開后不再滾動斜姥,馬上靜止拌禾,也不會調(diào)用scrollViewDidEndDecelerating:代理方法
       */
      - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
      {
          if (decelerate) {
              NSLog(@"用戶停止拖拽scrollView鬓照,scrollView會繼續(xù)滾動");
          } else {
              NSLog(@"用戶停止拖拽scrollView弃甥,scrollView不再滾動");
          }
      }
      
      // ----------------------------------------------------------------------------
      // 方式二
      /**
       *  當(dāng)scrollView停止?jié)L動的時候調(diào)用
       *  前提:人為手動讓scrollView產(chǎn)生滾動
       */
      - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
      {
          NSLog(@"用戶停止拖拽scrollView后滾動完畢");
      }
      
      // ----------------------------------------------------------------------------
      // 方式三
      /**
       *  當(dāng)scrollView停止?jié)L動的時候調(diào)用
       *  前提:當(dāng)使用setContentOffset:animated:或者scrollRectToVisible:animated:方法讓scrollView產(chǎn)生了滾動動畫
       */
      - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
      {
          NSLog(@"通過setContentOffset:animated:或者scrollRectToVisible:animated:方法讓scrollView產(chǎn)生滾動動畫惶室,然后停止?jié)L動");
      }
      
      // ----------------------------------------------------------------------------
      // 方式四
      [UIView animateWithDuration:1.0 animations:^{
          self.scrollView.contentOffset = CGPointMake(150, 150);
          [self.scrollView setContentOffset:CGPointMake(150, 150)];
      } completion:^(BOOL finished) {
          NSLog(@"減速完畢----");
      }];
    

坐標(biāo)系轉(zhuǎn)換

  • 計算控件A在window中的x,y,width,height.
    • convertRect:toView:方法(兩個方法是可逆的)
    • convertRect:fromView:方法
    • 注意: toView和fromView如果為nil,表示window.
    • 如果方法的調(diào)用者是A本身,則傳入的convertRect為A.bounds.
      CGRect rect = [A convertRect:A.bounds toView:nil];
    • 如果方法的調(diào)用者是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];
  • 坐標(biāo)系轉(zhuǎn)換圖解


    坐標(biāo)系轉(zhuǎn)換圖解.png

判斷是否重疊

  • 使用bool CGRectIntersectsRect(CGRect rect1, CGRect rect2)函數(shù)判斷rect1與rect2

    • 注意: 該函數(shù)的參數(shù)rect1和rect2必須處于同一坐標(biāo)系. 如果在不同坐標(biāo)系,必須先進行坐標(biāo)系轉(zhuǎn)換,再用此方法判斷兩個控件是否重疊.

    • 判斷是否重疊參考代碼
      - (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分類,實現(xiàn)判斷是否重疊的方法

- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果傳入的參數(shù)是nil,則表示為[UIApplication sharedApplication].keyWindow
    if (view == nil) view = [UIApplication sharedApplication].keyWindow;
    
    // 都統(tǒng)一轉(zhuǎn)換成window坐標(biāo)系,并判斷是否重疊,返回判斷結(jié)果
    CGRect rect1 = [self convertRect:self.bounds toView:nil];
    CGRect rect2 = [view convertRect:view.bounds toView:nil];
    return CGRectIntersectsRect(rect1, rect2);
}

導(dǎo)航條按鈕顯示異常bug

  • 異常bug現(xiàn)象: 導(dǎo)航條的按鈕會出現(xiàn)位置有誤.只要push到其他控制器再返回時,導(dǎo)航條按鈕就顯示正常..
    • 導(dǎo)航欄按鈕顯示出現(xiàn)問題,push/pop回來就好了的bug.原因是重寫viewWillAppear:時,沒調(diào)用super 的viewWillAppear:方法或者調(diào)用錯方法導(dǎo)致.
// 如果super的方法名寫錯斋枢,會出現(xiàn)界面顯示的一些小問題
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillDisappear:animated];
}

狀態(tài)欄點擊事件

  • 系統(tǒng)默認(rèn)點擊狀態(tài)欄的時候,scrollView滾動到頂部

    • 前提是UIScrollView的scrollsToTop屬性為YES時,并且屏幕上只有一個scrollView時才能用.
    • UIScrollView的scrollsToTop屬性默認(rèn)為YES.
  • 要想window里面的內(nèi)容跟隨屏幕旋轉(zhuǎn)帘靡,那么必須設(shè)置window的rootViewController

  • 狀態(tài)欄的樣式和顯示隱藏由最頂層window的控制器決定

    • - (BOOL)prefersStatusBarHidden : 顯示和隱藏
    • - (UIStatusBarStyle)preferredStatusBarStyle :白色和黑色
  • 旋轉(zhuǎn)狀態(tài)欄消失的原因是最上面window控制器沒實現(xiàn)狀態(tài)欄preferStatusBarHidden方法.


UIWindow相關(guān)知識點

  • window顯示只要設(shè)置hidden = NO就可以顯示了,不需要像UIView要添加到父控件view上.

  • window級別越高,越顯示在頂層.

    • UIWindowLevelAlert -> UIWindowLevelStatusBar ->UIWindowLevelNormal
    • 如果級別一樣,越后面顯示在越頂層.
  • 為什么一定要- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中給window添加根控制器?
    • 只要有設(shè)置window的根控制器,window里面的內(nèi)容才會跟隨旋轉(zhuǎn).window本身是不會旋轉(zhuǎn)的.

監(jiān)聽按鈕事件

  • 監(jiān)聽短時間按鈕多次點擊(雙擊)事件
    • UIControlEventTouchDownRepeat

1.精華子控制器view懶加載

子控制器view懶加載實現(xiàn)

  • 1.實現(xiàn)添加index位置對應(yīng)的子控制器view到scrollView參考代碼()

    • 1.根據(jù)索引獲取子控制器(方法一)

    • 2.判斷子控制器的view是否已經(jīng)加載過(三種方法),如果已經(jīng)加載過,退出

      • 方法一: childVc.isViewLoaded 判斷是否已經(jīng)加載過
      • 方法二: childVc.view.superview 判斷是否有父控件
      • 方法三: childVc.view.window 判斷window是否有值
      • 方法四: self.scrollView.subviews containsObject:childVc.view 判斷控制器的view是否在scrollView的子控件數(shù)組中.
    • 3.設(shè)置要添加的子控制器view的frame,并添加到scrollView

      • 3.1 設(shè)置x值
      • 3.2 需將y設(shè)置為0,因為childVc.view是UITableView,UITableView默認(rèn)的y值是20
      • 3.3 默認(rèn)UITableView的高度是屏幕的高度減去它本身的y值(20),所以重新設(shè)置高度為整個scrollView的高度
      • 3.4 添加子控制器的view到scrollView
      // ----------------------------------------------------------------------------
      // 添加index位置對應(yīng)的子控制器view到scrollView
      - (void)addChildVcViewIntoScrollView:(NSInteger)index
      {
          // 1.根據(jù)索引獲取子控制器
          UIViewController *childVc = self.childViewControllers[index];
          
          // TODO: 2.判斷子控制器的view是否已經(jīng)加載過,如果已經(jīng)加載過,退出
          // 方法一: childVc.isViewLoaded 方法二: childVc.view.superview 方法三: childVc.view.window
          if (childVc.isViewLoaded) {
              return;
          }
          
          // 3.設(shè)置要添加的子控制器view的frame,并添加到scrollView
          // 3.1 設(shè)置x值
          childVc.view.wx_x = index * self.scrollView.wx_width;
          // 3.2 需將y設(shè)置為0,因為childVc.view是UITableView,UITableView默認(rèn)的y值是20
          childVc.view.wx_y = 0;
          // 3.3 默認(rèn)UITableView的高度是屏幕的高度減去它本身的y值(20),所以重新設(shè)置高度為整個scrollView的高度
          childVc.view.wx_height = self.scrollView.wx_height;
          // 3.4 添加子控制器的view到scrollView
          [self.scrollView addSubview:childVc.view];
      }
    

    • 使用偏移量計算索引值(方法二)
      使用偏移量計算索引,實現(xiàn)添加子控制器view到scrollView,該方法只能通過偏移量來控制要顯示那個view,靈活性不夠 (不推薦,因為其依賴偏移量)
      • 經(jīng)過以下分析scrollView的x,y偏移量等于bounds的x,y值.可推斷出childVc.view.frame剛好為scrollView.bounds;
      // ----------------------------------------------------------------------------
      // 使用偏移量計算索引,實現(xiàn)添加子控制器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.在初始化添加子控制器的方法中調(diào)用添加子控制器的view的方法設(shè)置默認(rèn)顯示第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.獲取子控制器數(shù)量
    NSInteger count = self.childViewControllers.count;
    // 設(shè)置默認(rèn)顯示第0個子控制器的view
    [self addChildVcViewIntoScrollView:0];
    // 3.設(shè)置scrollView的滾動范圍
    self.scrollView.contentSize = CGSizeMake(count * self.scrollView.wx_width, 0);
}
  • 3.使用按鈕的tag值作為索引,在下劃線動畫執(zhí)行完成修改scrollView的偏移量,顯示對應(yīng)子控制器的view.
    • 如果對應(yīng)子控制器的view為添加到scrollView,則添加view到scrollView,并更新偏移量顯示對應(yīng)view.
#pragma =======================================================================
#pragma mark - titleButton按鈕點擊
// ----------------------------------------------------------------------------
// 監(jiān)聽按鈕點擊
- (void)titleButtonClick:(WXTitleButton *)button
{
    // 切換中狀態(tài)
    self.selectedButton.selected = NO;
    button.selected = YES;
    self.selectedButton = button;
    
    // 1.獲取索引,按鈕的tag值
    NSInteger index = button.tag;
    
    // 2.執(zhí)行下劃線動畫,動畫執(zhí)行完成修改scrollView的偏移量,顯示對應(yīng)子控制器的view
    [UIView animateWithDuration:0.25 animations:^{
        
        // TODO: 設(shè)置下劃線的寬度和中心點
        self.underLineView.wx_width = button.titleLabel.wx_width;
        self.underLineView.wx_centerX = button.wx_centerX;
        
        // 切換到對應(yīng)的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];
        
        // 添加對應(yīng)子控制器的view
        [self addChildVcViewIntoScrollView:index];
    }];
}

2.監(jiān)聽頂部狀態(tài)欄區(qū)域的點擊

  • 實現(xiàn)思路分析

    1. 創(chuàng)建一個級別為UIWindowLevelAlert(最高)的且占據(jù)全屏的WXTopWindow,并設(shè)置其hidden = NO,讓其顯示在最頂層.
    1. WXTopWindow(繼承UIWindow)中重寫
      hitTest:withEvent:方法實現(xiàn)響應(yīng)高度( <= 20 )也就是
      藍(lán)色區(qū)域(狀態(tài)欄區(qū)域)能點擊,紅色區(qū)域( >20 )不能點擊
    1. 創(chuàng)建一個WXTopViewController(繼承UIViewController),并
      WXTopViewController設(shè)置為WXTopWindow的根控制器.
      為WXTopWindow已經(jīng)設(shè)置僅頂部狀態(tài)欄區(qū)域能點擊,所以
      重寫WXTopViewController的view的touchesBegan方法就可以 僅監(jiān)聽狀態(tài)欄區(qū)域的點擊事件.
  • 監(jiān)聽狀態(tài)欄點擊實現(xiàn)解析圖


    監(jiān)聽狀態(tài)欄點擊實現(xiàn)解析圖.png

監(jiān)聽頂部狀態(tài)欄的點擊事件的實現(xiàn)

  • 監(jiān)聽狀態(tài)欄點擊事件實現(xiàn)

    • 1.自定義WXTopWindow(繼承UIWindow)
    • 2.提供一個參數(shù)為block的的類方法,供外部調(diào)用.block會在狀態(tài)欄區(qū)域被點擊的時候調(diào)用
      + (void)showWithStatusBarClickBlock:(void (^)())block;
    
    • 3.在WXTopWindow(繼承UIWindow)中重寫hitTest:withEvent:方法實現(xiàn)只響應(yīng)狀態(tài)欄區(qū)域的點擊.
    • 4.showWithStatusBarClickBlock:類方法的實現(xiàn)
      • 1.判斷如果該window已經(jīng)創(chuàng)建,無需再創(chuàng)建,因為window整個應(yīng)用程序只需要一個,無需重復(fù)創(chuàng)建
      • 2.添加window到狀態(tài)欄區(qū)域,window默認(rèn)填充整個屏幕,所以無需設(shè)置frame
      • 3.設(shè)置window的優(yōu)先級為最高,比狀態(tài)欄的優(yōu)先級高
        UIWindowLevelAlert(高) UIWindowLevelStatusBar(中) UIWindowLevelNormal(低,默認(rèn))
      • 4.設(shè)置window的背景色為透明色
      • 注意: 需先顯示window再設(shè)置根控制器.
      • 5.創(chuàng)建WXTopViewController并設(shè)置背景色為clearColor
      • 6.設(shè)置旋轉(zhuǎn)時只拉伸寬度,否則會出現(xiàn)view的狀態(tài)欄區(qū)域旋轉(zhuǎn)消失或變大問題.
        控制器的view默認(rèn)是長度寬度都自動拉伸,此處只需拉伸寬度,不需要拉伸高度.
      • 7.將block傳遞給控制器管理,當(dāng)控制器的view的狀態(tài)欄區(qū)域被點擊,調(diào)用block
      • 8.設(shè)置WXTopWindow的根控制器為WXTopViewController.只有設(shè)置window的根控制器,window里面的內(nèi)容才會跟隨旋轉(zhuǎn).window本身不會旋轉(zhuǎn).

    • 5.在WXTopViewController控制器中實現(xiàn)touchesBegan:方法監(jiān)聽頂部狀態(tài)欄區(qū)域的點擊.
      • touchesBegan:方法監(jiān)聽到頂部狀態(tài)欄區(qū)域的點擊,調(diào)用WXTopWindow傳遞過來的block.
    • 注意: WXTopViewController必須明確指定狀態(tài)欄顯示,否則會出現(xiàn)旋轉(zhuǎn)后狀態(tài)欄消失.

    • 使用方法: 調(diào)用showWithStatusBarClickBlock:即可實現(xiàn)監(jiān)聽狀態(tài)欄區(qū)域的點擊.
    [WXTopWindow showWithStatusBarClickBlock:^{
        NSLog(@"點擊了頂部狀態(tài)欄區(qū)域");
    }];
    

3.狀態(tài)欄點擊控制tableView滾動

查找所有的scrollView

  • 1.首先實現(xiàn)UIView的分類方法: 判斷方法調(diào)用者和view(本項目功能指keyWindow)是否重疊
// ----------------------------------------------------------------------------
// 判斷方法調(diào)用者和view是否重疊
- (BOOL)wx_intersectWithView:(UIView *)view
{
    // 如果傳入的參數(shù)是nil,則表示為[UIApplication sharedApplication].keyWindow
    if (view == nil) {
        view = [UIApplication sharedApplication].keyWindow;
    }
    
    // 都統(tǒng)一轉(zhuǎn)換成window坐標(biāo)系,并判斷是否重疊,返回判斷結(jié)果
    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的范圍內(nèi)(不跟window重疊),如果不在,直接退出
    • 2.遍歷view的所有子控件和子控件的子控件,此處for循環(huán)會退出,所以遞歸調(diào)用會退出
    • 3.判斷如果scrollView,直接返回
    • 4.滾動scrollView到最頂部
      • 方法一: 獲取scrollView,將scrollView的偏移量y值設(shè)置為負(fù)的內(nèi)邊距頂部值: -scrllView.contentInset.top
        UIScrollView *scrllView = (UIScrollView *)view;
        CGPoint offset = scrllView.contentOffset;
        offset.y = -scrllView.contentInset.top;
        [scrllView setContentOffset:offset animated:YES];
    
    - 方法二: 讓`scrollView移動到其內(nèi)容的最頂部`.
      
      ```objectivec
      [scrollView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
      ```
    
    • 使用: 在didFinishLaunchingWithOptions:方法中調(diào)用UIView的分類對象方法,searchAllScrollViewsInView:方法,傳入application.keyWindow參數(shù),判斷scrollView是否在keyWindow中,如果在keyWindow中,則滾動scrollView到頂部.

    • 實現(xiàn)當(dāng)前顯示的scrollView/tableView點擊頂部狀態(tài)欄區(qū)域滾動到scrollView/tableView最頂部參考代碼
      - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
          
          // 1.創(chuàng)建window
          self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
          
          // 2.設(shè)置window的根控制器
          self.window.rootViewController = [[WXAdViewController alloc] init];
          // init -> initWithNibName -> 1.判斷有沒有指定NibName 2.判斷有沒有跟控制器同名的xib,就會去加載 3.判斷下有沒有不帶controller的xib 4.創(chuàng)建一個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的范圍內(nèi)(不跟window重疊),如果不在window范圍內(nèi),直接退出
          if (![view wx_intersectWithView:nil]) {
              return;
          }
          
          // 2.遍歷view的所有子控件和子控件的子控件,此處for循環(huán)會退出,所以遞歸調(diào)用會退出
          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.監(jiān)聽tabBarButton的重復(fù)點擊

  • 運行效果圖


    監(jiān)聽tabBarButton的重復(fù)點擊效果圖.gif
  • 實現(xiàn)思路:

    • 1.思考使用UITabBarButton的addTarget方式監(jiān)聽(可行,簡單)
    • 2.使用UITabBar代理方式監(jiān)聽(經(jīng)驗證,不可行)
      原因: 被一個UITabBarController管理的tabBar的代理是不能被改變的.如果一個UITabBar被UITabBarController管理,又重新設(shè)置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的代理監(jiān)聽(可行)

監(jiān)聽tabBarButton重復(fù)點擊方式一(使用tabBarButton addTarget方式,本項目使用此方式)

  • 1.在layoutSubviews方法中獲取所有tabBarButton,使用addTarget方式監(jiān)聽tabBarButton的點擊

    • UITabBarButton是私有類,不能使用,所以打印其superClass,父類為UIControl,所以可以用addTarget方法監(jiān)聽點擊事件
    • 監(jiān)聽tabBarButton重復(fù)點擊的兩種方式
      • 1.記錄上一次點擊的tabBarButton的tag值.監(jiān)聽到tabBarButton重復(fù)點擊,發(fā)送通知瓤帚,通知外界tabBarButton重復(fù)點擊描姚。
          // ----------------------------------------------------------------------------
          // 重新布局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值,并設(shè)置frame
                      itemX = index * itemW;
                      view.frame = CGRectMake(itemX, itemY, itemW, itemH);
                      
                      // 2.3 監(jiān)聽UITabBarButton的點擊,打印view的父類為UIControl
                      // TODO: 使用AddTarget監(jiān)聽tabBarButton的點擊
                      UIControl *tabBarButton = (UIControl *)view;
                      tabBarButton.tag = index;
                      [tabBarButton addTarget:self action:@selector(tabBarButtonClick:) forControlEvents:UIControlEventTouchUpInside];
                      
                      index++;
                      // 判斷如果是是第二個batBarButton,空一格
                      if (index == 2) {
                          index++;
                      }
                  }
              }
              
              // 3.設(shè)置加號按鈕
              self.plusButton.center = CGPointMake(self.wx_width * 0.5, self.wx_height * 0.5);
          }
          // ----------------------------------------------------------------------------
          // 使用記錄上一次點擊的tabBarButton的tag方法監(jiān)聽tabBarButton的點擊
          - (void)tabBarButtonClick:(UIControl *)tabBarButton
          {
              // 使用tabBarButton的tag方法監(jiān)聽
              if (self.selectedTabBarButton.tag == tabBarButton.tag) {
                  // 發(fā)送通知
                  [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
              }
              
              // 記錄選中tabBarButton
              self.selectedTabBarButton = tabBarButton;
          }
    
    • 2.記錄上一次點擊的tabBarButton.監(jiān)聽到tabBarButton重復(fù)點擊,發(fā)送通知戈次,通知外界tabBarButton重復(fù)點擊轩勘。

    注意: 此方式有個bug,在程序剛啟動完成,默認(rèn)選中第0個tabBarButton,如果再點擊第0個tabBarButton時,并沒有觸發(fā)重復(fù)點擊.原因是因為記錄上一次點擊的tabBarButton默認(rèn)為nil.所以使用該方法需在合適的地方上一次點擊的tabBarButton賦一個初始值.

      以下參考代碼存在以上所提的bug,解決此bug必須給上一次點擊的tabBarButton賦一個初始值.
    
      ```objectivec
      // ----------------------------------------------------------------------------
      // 使用記錄上一次點擊的tabBarButton方法監(jiān)聽tabBarButton的點擊
      - (void)tabBarButtonClick:(UIControl *)tabBarButton
      {
          // TODO: 需注意程序剛啟動時self.selectedTabBarButton == nil的情況
          if (self.selectedTabBarButton == tabBarButton) {
              [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
          }
          // 記錄選中tabBarButton
          self.selectedTabBarButton = tabBarButton;
      }
      ```
    

監(jiān)聽tabBarButton重復(fù)點擊方式二(使用UITabBarController的代理方式)

  • 1.在廣告界面即將跳轉(zhuǎn)到TabBarController的地方,為TabBarController設(shè)置代理怯邪,代理對象是·[UIApplication sharedApplication].delegate绊寻,不能用廣告控制器做為代理對象,因為一旦根控制器切換到TabBarController時擎颖,廣告控制器就會被銷毀.
// ----------------------------------------------------------------------------
// 監(jiān)聽點擊跳過按鈕
- (IBAction)jump {
    
    // 關(guān)閉定時器
    [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協(xié)議,并實現(xiàn)tabBarController:didSelectViewController:代理方法,監(jiān)聽TabBarController選中了哪個控制器.
// ----------------------------------------------------------------------------
// 監(jiān)聽tabBarController當(dāng)前選中哪個控制器
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    // selectedVc用于存放TabBarController上一次選中的控制器
    static UIViewController *selectedVc = nil;
    
    // 設(shè)置初始化上一次選中的控制器為tabBarController的第0個子控制器.
    if (selectedVc == nil) {
        selectedVc = tabBarController.childViewControllers[WXDefaultVcIndex];
    }
    
    // 如果上一次選中的控制器和當(dāng)前選中控制器一樣,表示重復(fù)點擊,發(fā)送通知
    if (selectedVc == viewController) {
        [[NSNotificationCenter defaultCenter] postNotificationName:WXTabBarButtonDidRepeatClickNotification object:nil];
    }
    
    // 更新上一次選中控制器
    selectedVc = viewController;
}

子控制器監(jiān)聽tabBarButton重復(fù)點擊通知

  • 在對應(yīng)子控制器的viewDidLoad方法中監(jiān)聽tabBarButton重復(fù)點擊通知,執(zhí)行響應(yīng)操作
- (void)viewDidLoad {
    [super viewDidLoad];
    
    WXFunc();
    
    self.view.backgroundColor = WXRandomColor;
    self.tableView.contentInset = UIEdgeInsetsMake(WXNavMaxY + WXTitlesViewH, 0, WXTabBarH, 0);
    
    // 1.監(jiān)聽通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tabBarButtonDidRepeatClick) name:WXTabBarButtonDidRepeatClickNotification object:nil];
}

#pragma =======================================================================
#pragma mark - 監(jiān)聽tabBarButton重復(fù)點擊通知
- (void)tabBarButtonDidRepeatClick
{
    NSLog(@"%@: 重復(fù)點擊榛斯,執(zhí)行下拉刷新", [self class]);
}

- (void)dealloc
{
    // 移除通知
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搂捧,隨后出現(xiàn)的幾起案子驮俗,更是在濱河造成了極大的恐慌,老刑警劉巖允跑,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件王凑,死亡現(xiàn)場離奇詭異搪柑,居然都是意外死亡,警方通過查閱死者的電腦和手機索烹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門工碾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人百姓,你說我怎么就攤上這事渊额。” “怎么了垒拢?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵旬迹,是天一觀的道長。 經(jīng)常有香客問我求类,道長奔垦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任尸疆,我火速辦了婚禮椿猎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寿弱。我一直安慰自己犯眠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布脖捻。 她就那樣靜靜地躺著阔逼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪地沮。 梳的紋絲不亂的頭發(fā)上嗜浮,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音摩疑,去河邊找鬼危融。 笑死,一個胖子當(dāng)著我的面吹牛雷袋,可吹牛的內(nèi)容都是我干的吉殃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼楷怒,長吁一口氣:“原來是場噩夢啊……” “哼蛋勺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸠删,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抱完,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刃泡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巧娱,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡碉怔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了禁添。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撮胧。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖老翘,靈堂內(nèi)的尸體忽然破棺而出芹啥,到底是詐尸還是另有隱情,我是刑警寧澤酪捡,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布叁征,位于F島的核電站纳账,受9級特大地震影響逛薇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疏虫,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一永罚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卧秘,春花似錦呢袱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚯涮,卻和暖如春治专,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遭顶。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工张峰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棒旗。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓喘批,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铣揉。 傳聞我的和親對象是個殘疾皇子饶深,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容