iPad小案例 -- QQ空間界面

一. iPad的一些常識

  1. iPad的屏幕尺寸和分辨率

    • 建議沒有做過iPad適配的同學(xué), 在蘋果官方文檔查看一下不同型號iPad的尺寸
    • 注意一下點與像素的區(qū)別, 尤其是Retina屏幕
  2. iPhone和iPad開發(fā)之間的一些區(qū)別

    • iPad的屏幕相比iPhone較大, 因此能容納更多內(nèi)容, 并且多數(shù)iPad的UI排布都是左右分屏
    • iPad的鍵盤多了一個退出鍵盤的按鈕, 因此不需要手動寫endEditing這樣的代碼了
    • API:
      • 基本所有的API, 在iPad和iPhone上都是共用的, 只是有些效果會有些不同
      • iPad比iPhone多了一些特有類: 如UIPopoverControllerUISplitViewController, 這兩個類分別對應(yīng)下拉菜單和分屏
    • 屏幕方向
      • iPhone只有三個方向: 即豎屏, 左橫屏和右橫屏(一般App只需要豎屏)
      • iPad則支持四個方向: 豎屏, 返轉(zhuǎn)豎屏, 左橫屏和有橫屏(蘋果官方建議, 最好同時支持橫豎屏兩個方向)

二. 項目分析

  1. iPad的QQ空間, 使用的是分屏模式, 即左側(cè)dock邊欄, 右側(cè)content內(nèi)容展示

  2. 本項目比較麻煩的點就在于, 在轉(zhuǎn)換屏幕的時候, 如何設(shè)置好dockcontent的約束

    • 尺寸大小發(fā)生改變
    • 部分控件的排列結(jié)構(gòu)也會發(fā)生改變: 如橫向排列改為縱向排列
    • 按鈕的狀態(tài)變化: 橫豎屏不同的狀態(tài), 按鈕展示的內(nèi)容也不同(只展示圖片/圖文展示)
  3. 方案選擇(布局方式):

    1. Autoresizing
      • 該方案通常用來解決父控件子控件之間相對關(guān)系的問題
      • 因此Autoresizing只能改變父子控件的相對位置/尺寸, 但是當橫豎屏發(fā)生變化時, 無法重新排列控件
      • 從Autoresizing的各種屬性也能得知, 他是更改相對位置和尺寸
      • UIViewAutoresizingFlexibleLeftMargin等等
    2. Autolayout
      • Autolayout是從iOS6.0之后推出的布局方案, 用于解決相對控件的位置/尺寸問題
      • 他可以很便捷的布局控件之間的約束關(guān)系, 并且可以讓控件之間產(chǎn)生耦合, 互相耦合的控件可以一同發(fā)生改變
      • 但他和Autoresing有相同的缺點, 就是不能改變控件的排列方式(橫向排列改為縱向排列)
      • 并且, 控件之間的耦合性過強, 一旦控件發(fā)生改變, 和他有關(guān)聯(lián)的控件都要變
    3. UIStackView
      • iOS9.0后退出的新控件, 他可以快速的將一些控件進行水平/垂直排布, 并且可以設(shè)置間距
      • 控件之間的耦合性很弱, 耦合性只針對于控件和StackView之間產(chǎn)生
      • 一般用于實現(xiàn)一些簡單的水平/垂直排列
      • iOS9.0+才能使用, 對于目前普遍從iOS8.0開始適配來說, 該方案不可選
    4. Sizeclass
      • iOS8.0推出的功能, 可以解決不同屏幕狀態(tài)下的排列方式和重設(shè)控件的內(nèi)容樣式
      • Sizeclass最大的優(yōu)點就在于他可以區(qū)分不同的屏幕尺寸來設(shè)置約束, 但是詳細設(shè)置還要配合其他方案
      • 無法區(qū)分iPad的橫豎屏!!!
    5. 純代碼
      • 較為繁瑣, 很麻煩, 鄙人比較不喜歡純代碼的方式
      • 優(yōu)勢: 優(yōu)秀的代碼風(fēng)格, 可以讓你的控件耦合性很低, 并且復(fù)用性很高
      • 對于iPad這種橫豎屏發(fā)生變化后, 控件的位置/大小/內(nèi)容都會發(fā)生改變的情況下, 最優(yōu)選擇就是純代碼搭建

三. 登錄界面搭建

  1. 此項目的重點在于如何做好iPad情況下的橫豎屏適配, 所以業(yè)務(wù)邏輯并沒有實現(xiàn)

  2. 登錄界面的重點:

    • 對于橫豎屏而言, 最好不要將約束參照設(shè)置為屏幕的四邊, 當屏幕旋轉(zhuǎn)時, 屏幕尺寸會產(chǎn)生很大的變化, 導(dǎo)致控件拉伸會很嚴重
    • 在開發(fā)的過程中, 一定要記得, 盡量將可復(fù)用的功能抽取為工具類!!也就是封裝思想!! + 在封裝工具類時, 涉及的block的回調(diào)
  3. 具體實現(xiàn)部分

    • 登錄的工具類的封裝(block的回調(diào))

        @implementation QQLoginTool
        + (void)loginByAccount:(NSString *)account password:(NSString *)password result:(void (^)(BOOL))loginResult {
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                
                if ([account isEqualToString:@"qq"] && [password isEqualToString:@"123"]) {
                    
                    loginResult(YES);
                } else {
                    
                    loginResult(NO);
                }
            });
        }
        @end
      
    • 登錄的執(zhí)行

        - (IBAction)loginBtnClick:(id)sender {
            
            // 0. 開始加載動畫
            [self.loginLoading startAnimating];
            
            // 1. 判斷是否內(nèi)容完全
            _loginBtn.enabled = (_accountTF.text.length && _pwdTF.text.length);
            
            // 2. 判斷內(nèi)容密碼是否正確
            [QQLoginTool loginByAccount:_accountTF.text password:_pwdTF.text result:^(BOOL isSuccess) {
                
                if (isSuccess) {
                    // 登錄成功跳轉(zhuǎn)界面
                    QQHomeViewController *homeVC = [[QQHomeViewController alloc] init];
                    [UIApplication sharedApplication].keyWindow.rootViewController = homeVC;
                } else {
                    // 登錄失敗, 執(zhí)行彈跳動畫
                    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.x"];
                    animation.values = @[@-50, @0, @50, @0];
                    animation.duration = 0.2;
                    animation.repeatCount = 3;
                    [_animationView.layer addAnimation:animation forKey:@"error"];
                    
                    // 提示賬號密碼錯誤
                    [QQShowMessageTool showErrorMessage:@"賬號或密碼錯誤!"];
                    
                    // 3. 停止加載動畫
                    [self.loginLoading stopAnimating];
                }
            }];
        }
      

三. 主頁的實現(xiàn)

  1. 重點

    • dockViewcontentView在切換橫豎屏?xí)r的適配
    • 對于一整塊具有多個子界面的View, 最好的辦法是將其劃分成不同的區(qū)域, 并且抽出不同的類
      • QQDockTopView
      • QQDockMiddleView
      • QQDockBottomView
    • 對于一些固定的取值(橫豎屏狀態(tài)下的dock和contentView的尺寸), 單獨放在一個類或PCH文件中, 方便管理
  2. dockViewcontentView的布局

    • 在切換橫豎屏之后, 屏幕的尺寸就會發(fā)生改變, 而這時候我們要獲取屏幕準確的尺寸, 來重新布局控件
      • -viewWillLayoutSubviews
      • -viewDidLayoutSubviews
      • 經(jīng)過檢測, 以上兩個方法, 是在屏幕轉(zhuǎn)動之后, 獲取屏幕尺寸最準確的方法, 所以更新控件frame的方法應(yīng)該放在這里
    • 鄙人在處理控件不同狀態(tài)下的尺寸時, 使用的是單例模型, 蘋果公司建議大家盡量少用PCH文件的
      • 創(chuàng)建一個QQHomeViewFrameItem模型類, 并且制作為單例

      • 在這個類中, 判斷當前屏幕的橫豎屏狀態(tài)

      • 根據(jù)橫豎屏狀態(tài)獲取各個控件的尺寸/位置值

          #import "QQHomeViewFrameItem.h"
          
          static QQHomeViewFrameItem *_frameItem;
          
          @implementation QQHomeViewFrameItem
          
          #pragma mark - 確定dockView的寬度
          
          + (instancetype)shareFrameItem {
              
              static dispatch_once_t onceToken;
              dispatch_once(&onceToken, ^{
                  _frameItem = [[QQHomeViewFrameItem alloc] init];
              });
              
              return _frameItem;
          }
          
          - (BOOL)isLandScape {
              
              CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
              CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
              
              return (screenWidth > screenHeight);
          }
          
          - (CGFloat)dockWidth {
              
              return (self.isLandScape ? 210 : 70);
          }
          // 等等控件的尺寸位置.....
          @end        
        
  3. dockView的一些重點

    • dockView在切換橫豎屏之后, 內(nèi)部的控件也會相應(yīng)的發(fā)生變化, 所以要在layoutSubviews方法中, 重新布局子控件
    • dockMiddleView中的按鈕, 在橫屏的時候顯示圖片+文字, 而在豎屏的時候只顯示圖片, 因此要自定義按鈕
      • - (CGRect)titleRectForContentRect:(CGRect)contentRect

      • - (CGRect)imageRectForContentRect:(CGRect)contentRect

      • 給上面兩個方法增加一個判斷, 根據(jù)橫豎屏的狀態(tài)來設(shè)置按鈕的圖片/文字的布局

          // 個人認為對于設(shè)置按鈕內(nèi)容布局來說, 非常實用的方法
          - (CGRect)titleRectForContentRect:(CGRect)contentRect {
              
              if (self.frameItem.isLandScape) {
                  return CGRectMake(contentRect.size.width * radio, 0, contentRect.size.width * (1 - radio), contentRect.size.height);
              } else {
                  return CGRectZero;
              }
          }
          
          - (CGRect)imageRectForContentRect:(CGRect)contentRect {
              
              if (self.frameItem.isLandScape) {
                  return CGRectMake(0, 0, contentRect.size.width * radio, contentRect.size.height);
              } else {
                  return contentRect;
              }
          }
        
    • 按鈕監(jiān)聽的傳遞
      • 在日常開發(fā)中, MVC設(shè)計模式, 一定要終于誰的事情交給誰處理, 而這里的麻煩點就在于按鈕是被View包裝起來的, 并且View還有一個dockView來包裝, 但是按鈕點擊的方法實現(xiàn)應(yīng)該交由控制器來管理

      • 因此這里要做多層傳遞, 最簡單的辦法是使用通知來跨層傳遞

      • 但鄙人這里使用的是代理傳遞: 將一個控件的代理, 賦值給另一個控件的代理, 然后交由控制器管理

          #pragma mark - QQDockBottomView
          - (void)addBtn {
              
              NSArray *imageNames = @[@"tabbar_blog", @"tabbar_mood", @"tabbar_photo"];
              
              for (int i = 0; i < imageNames.count; i++) {
                  
                  UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
                  
                  [btn setImage:[UIImage imageNamed:imageNames[i]] forState:UIControlStateNormal];
                  
                  btn.tag = i;
                  [btn addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
                  
                  [self addSubview:btn];
              }
          }
          
          - (void)btnClicked:(UIButton *)btn {
              
              if ([self.delegate respondsToSelector:@selector(dockBottomViewClickButtonWithType:)]) {
                  
                  [self.delegate dockBottomViewClickButtonWithType:btn.tag];
              }
          }
          
          #pragma mark - QQDockView
          // 這里要重寫Setter, 將代理傳給外界
          - (void)setDelegate:(id<QQdockViewDelegate>)delegate {
              
              _delegate = delegate;
              
              // 將代理傳給Dock的代理
              self.middleView.delegate = _delegate;
              self.bottomView.delegate = _delegate;
          }
          
          #pragma mark - QQDockViewController
          // 傳遞給控制器, 由控制器來實現(xiàn)代理方法, 監(jiān)聽按鈕的點擊
          - (void)dockBottomViewClickButtonWithType:(DockBottomViewButtonType)type {
              
              switch (type) {
                  case DockBottomViewButtonTypeRizhi:
                      NSLog(@"日志");
                      break;
                  case DockBottomViewButtonTypeShuoshuo:
                      NSLog(@"說說");
                      break;
                  case DockBottomViewButtonTypeCamera:
                      NSLog(@"相機");
                      break;
              }
          }
        

小結(jié):

  1. iPad開發(fā)麻煩的地方就在于橫豎屏的適配問題
  2. 在這個案例中使用了一次代理傳遞, 比較不好理解
  3. 對于比較復(fù)雜并且多變的控件, 筆者建議大家使用純代碼的方式來搭建, 比較靈活, 也便于維護
  4. iOS開發(fā)多注重封裝思想, 能抽調(diào)出來的功能一定要封裝為一個工具類
  5. 但是, 不要為了解耦去過度解耦, 弄得自己都混亂了
1EAA6434-0B24-4EEB-B96F-E1701997DEDA.png

507EB7CF-7939-41DB-AD37-B3473AF8F113.png

4FDEB061-A77E-421E-B5FC-4B80606B252E.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容