iPad小案例 -- QQ空間界面

一. iPad的一些常識(shí)

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

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

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

二. 項(xiàng)目分析

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

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

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

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

三. 登錄界面搭建

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

  2. 登錄界面的重點(diǎn):

    • 對于橫豎屏而言, 最好不要將約束參照設(shè)置為屏幕的四邊, 當(dāng)屏幕旋轉(zhuǎn)時(shí), 屏幕尺寸會(huì)產(chǎn)生很大的變化, 導(dǎo)致控件拉伸會(huì)很嚴(yán)重
    • 在開發(fā)的過程中, 一定要記得, 盡量將可復(fù)用的功能抽取為工具類!!也就是封裝思想!! + 在封裝工具類時(shí), 涉及的block的回調(diào)
  3. 具體實(shí)現(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. 開始加載動(dòng)畫
            [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í)行彈跳動(dòng)畫
                    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"];
                    
                    // 提示賬號(hào)密碼錯(cuò)誤
                    [QQShowMessageTool showErrorMessage:@"賬號(hào)或密碼錯(cuò)誤!"];
                    
                    // 3. 停止加載動(dòng)畫
                    [self.loginLoading stopAnimating];
                }
            }];
        }
      

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

  1. 重點(diǎn)

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

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

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

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

          #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的一些重點(diǎn)

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

      • - (CGRect)imageRectForContentRect:(CGRect)contentRect

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

          // 個(gè)人認(rèn)為對于設(shè)置按鈕內(nèi)容布局來說, 非常實(shí)用的方法
          - (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è)計(jì)模式, 一定要終于誰的事情交給誰處理, 而這里的麻煩點(diǎn)就在于按鈕是被View包裝起來的, 并且View還有一個(gè)dockView來包裝, 但是按鈕點(diǎn)擊的方法實(shí)現(xiàn)應(yīng)該交由控制器來管理

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

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

          #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
          // 傳遞給控制器, 由控制器來實(shí)現(xiàn)代理方法, 監(jiān)聽按鈕的點(diǎn)擊
          - (void)dockBottomViewClickButtonWithType:(DockBottomViewButtonType)type {
              
              switch (type) {
                  case DockBottomViewButtonTypeRizhi:
                      NSLog(@"日志");
                      break;
                  case DockBottomViewButtonTypeShuoshuo:
                      NSLog(@"說說");
                      break;
                  case DockBottomViewButtonTypeCamera:
                      NSLog(@"相機(jī)");
                      break;
              }
          }
        

小結(jié):

  1. iPad開發(fā)麻煩的地方就在于橫豎屏的適配問題
  2. 在這個(gè)案例中使用了一次代理傳遞, 比較不好理解
  3. 對于比較復(fù)雜并且多變的控件, 筆者建議大家使用純代碼的方式來搭建, 比較靈活, 也便于維護(hù)
  4. iOS開發(fā)多注重封裝思想, 能抽調(diào)出來的功能一定要封裝為一個(gè)工具類
  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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阴汇,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸟缕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來排抬,“玉大人懂从,你說我怎么就攤上這事《灼眩” “怎么了番甩?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長届搁。 經(jīng)常有香客問我缘薛,道長,這世上最難降的妖魔是什么卡睦? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任宴胧,我火速辦了婚禮,結(jié)果婚禮上表锻,老公的妹妹穿的比我還像新娘恕齐。我一直安慰自己,他們只是感情好瞬逊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布显歧。 她就那樣靜靜地躺著,像睡著了一般确镊。 火紅的嫁衣襯著肌膚如雪士骤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天骚腥,我揣著相機(jī)與錄音敦间,去河邊找鬼。 笑死束铭,一個(gè)胖子當(dāng)著我的面吹牛廓块,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播契沫,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼带猴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了懈万?” 一聲冷哼從身側(cè)響起拴清,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對情侶失蹤靶病,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后口予,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娄周,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年沪停,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煤辨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡木张,死狀恐怖众辨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情舷礼,我是刑警寧澤鹃彻,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站妻献,受9級(jí)特大地震影響蛛株,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜旋奢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一泳挥、第九天 我趴在偏房一處隱蔽的房頂上張望然痊。 院中可真熱鬧至朗,春花似錦、人聲如沸剧浸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唆香。三九已至嫌变,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間躬它,已是汗流浹背腾啥。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冯吓,地道東北人倘待。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像组贺,于是被迫代替她去往敵國和親凸舵。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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