iPad小案例 -- QQ空间界面

一. iPad的一些常识

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

    • 建议没有做过iPad适配的同学, 在苹果官方文档查看一下不同型号iPad的尺寸
    • 注意一下点与像素的区别, 尤其是Retina屏幕
  2. iPhone和iPad开发之间的一些区别

    • iPad的屏幕相比iPhone较大, 因此能容纳更多内容, 并且多数iPad的UI排布都是左右分屏
    • iPad的键盘多了一个退出键盘的按钮, 因此不需要手动写endEditing这样的代码了
    • API:
      • 基本所有的API, 在iPad和iPhone上都是共用的, 只是有些效果会有些不同
      • iPad比iPhone多了一些特有类: 如UIPopoverControllerUISplitViewController, 这两个类分别对应下拉菜单和分屏
    • 屏幕方向
      • iPhone只有三个方向: 即竖屏, 左横屏和右横屏(一般App只需要竖屏)
      • iPad则支持四个方向: 竖屏, 返转竖屏, 左横屏和有横屏(苹果官方建议, 最好同时支持横竖屏两个方向)

二. 项目分析

  1. iPad的QQ空间, 使用的是分屏模式, 即左侧dock边栏, 右侧content内容展示

  2. 本项目比较麻烦的点就在于, 在转换屏幕的时候, 如何设置好dockcontent的约束

    • 尺寸大小发生改变
    • 部分控件的排列结构也会发生改变: 如横向排列改为纵向排列
    • 按钮的状态变化: 横竖屏不同的状态, 按钮展示的内容也不同(只展示图片/图文展示)
  3. 方案选择(布局方式):

    1. Autoresizing
      • 该方案通常用来解决父控件子控件之间相对关系的问题
      • 因此Autoresizing只能改变父子控件的相对位置/尺寸, 但是当横竖屏发生变化时, 无法重新排列控件
      • 从Autoresizing的各种属性也能得知, 他是更改相对位置和尺寸
      • UIViewAutoresizingFlexibleLeftMargin等等
    2. Autolayout
      • Autolayout是从iOS6.0之后推出的布局方案, 用于解决相对控件的位置/尺寸问题
      • 他可以很便捷的布局控件之间的约束关系, 并且可以让控件之间产生耦合, 互相耦合的控件可以一同发生改变
      • 但他和Autoresing有相同的缺点, 就是不能改变控件的排列方式(横向排列改为纵向排列)
      • 并且, 控件之间的耦合性过强, 一旦控件发生改变, 和他有关联的控件都要变
    3. UIStackView
      • iOS9.0后退出的新控件, 他可以快速的将一些控件进行水平/垂直排布, 并且可以设置间距
      • 控件之间的耦合性很弱, 耦合性只针对于控件和StackView之间产生
      • 一般用于实现一些简单的水平/垂直排列
      • iOS9.0+才能使用, 对于目前普遍从iOS8.0开始适配来说, 该方案不可选
    4. Sizeclass
      • iOS8.0推出的功能, 可以解决不同屏幕状态下的排列方式和重设控件的内容样式
      • Sizeclass最大的优点就在于他可以区分不同的屏幕尺寸来设置约束, 但是详细设置还要配合其他方案
      • 无法区分iPad的横竖屏!!!
    5. 纯代码
      • 较为繁琐, 很麻烦, 鄙人比较不喜欢纯代码的方式
      • 优势: 优秀的代码风格, 可以让你的控件耦合性很低, 并且复用性很高
      • 对于iPad这种横竖屏发生变化后, 控件的位置/大小/内容都会发生改变的情况下, 最优选择就是纯代码搭建

三. 登录界面搭建

  1. 此项目的重点在于如何做好iPad情况下的横竖屏适配, 所以业务逻辑并没有实现

  2. 登录界面的重点:

    • 对于横竖屏而言, 最好不要将约束参照设置为屏幕的四边, 当屏幕旋转时, 屏幕尺寸会产生很大的变化, 导致控件拉伸会很严重
    • 在开发的过程中, 一定要记得, 尽量将可复用的功能抽取为工具类!!也就是封装思想!! + 在封装工具类时, 涉及的block的回调
  3. 具体实现部分

    • 登录的工具类的封装(block的回调)

        @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
      
    • 登录的执行

        - (IBAction)loginBtnClick:(id)sender {
            
            // 0. 开始加载动画
            [self.loginLoading startAnimating];
            
            // 1. 判断是否内容完全
            _loginBtn.enabled = (_accountTF.text.length && _pwdTF.text.length);
            
            // 2. 判断内容密码是否正确
            [QQLoginTool loginByAccount:_accountTF.text password:_pwdTF.text result:^(BOOL isSuccess) {
                
                if (isSuccess) {
                    // 登录成功跳转界面
                    QQHomeViewController *homeVC = [[QQHomeViewController alloc] init];
                    [UIApplication sharedApplication].keyWindow.rootViewController = homeVC;
                } else {
                    // 登录失败, 执行弹跳动画
                    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];
                }
            }];
        }
      

三. 主页的实现

  1. 重点

    • dockViewcontentView在切换横竖屏时的适配
    • 对于一整块具有多个子界面的View, 最好的办法是将其划分成不同的区域, 并且抽出不同的类
      • QQDockTopView
      • QQDockMiddleView
      • QQDockBottomView
    • 对于一些固定的取值(横竖屏状态下的dock和contentView的尺寸), 单独放在一个类或PCH文件中, 方便管理
  2. dockViewcontentView的布局

    • 在切换横竖屏之后, 屏幕的尺寸就会发生改变, 而这时候我们要获取屏幕准确的尺寸, 来重新布局控件
      • -viewWillLayoutSubviews
      • -viewDidLayoutSubviews
      • 经过检测, 以上两个方法, 是在屏幕转动之后, 获取屏幕尺寸最准确的方法, 所以更新控件frame的方法应该放在这里
    • 鄙人在处理控件不同状态下的尺寸时, 使用的是单例模型, 苹果公司建议大家尽量少用PCH文件的
      • 创建一个QQHomeViewFrameItem模型类, 并且制作为单例

      • 在这个类中, 判断当前屏幕的横竖屏状态

      • 根据横竖屏状态获取各个控件的尺寸/位置值

          #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在切换横竖屏之后, 内部的控件也会相应的发生变化, 所以要在layoutSubviews方法中, 重新布局子控件
    • dockMiddleView中的按钮, 在横屏的时候显示图片+文字, 而在竖屏的时候只显示图片, 因此要自定义按钮
      • - (CGRect)titleRectForContentRect:(CGRect)contentRect

      • - (CGRect)imageRectForContentRect:(CGRect)contentRect

      • 给上面两个方法增加一个判断, 根据横竖屏的状态来设置按钮的图片/文字的布局

          // 个人认为对于设置按钮内容布局来说, 非常实用的方法
          - (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;
              }
          }
        
    • 按钮监听的传递
      • 在日常开发中, MVC设计模式, 一定要终于谁的事情交给谁处理, 而这里的麻烦点就在于按钮是被View包装起来的, 并且View还有一个dockView来包装, 但是按钮点击的方法实现应该交由控制器来管理

      • 因此这里要做多层传递, 最简单的办法是使用通知来跨层传递

      • 但鄙人这里使用的是代理传递: 将一个控件的代理, 赋值给另一个控件的代理, 然后交由控制器管理

          #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
          // 传递给控制器, 由控制器来实现代理方法, 监听按钮的点击
          - (void)dockBottomViewClickButtonWithType:(DockBottomViewButtonType)type {
              
              switch (type) {
                  case DockBottomViewButtonTypeRizhi:
                      NSLog(@"日志");
                      break;
                  case DockBottomViewButtonTypeShuoshuo:
                      NSLog(@"说说");
                      break;
                  case DockBottomViewButtonTypeCamera:
                      NSLog(@"相机");
                      break;
              }
          }
        

小结:

  1. iPad开发麻烦的地方就在于横竖屏的适配问题
  2. 在这个案例中使用了一次代理传递, 比较不好理解
  3. 对于比较复杂并且多变的控件, 笔者建议大家使用纯代码的方式来搭建, 比较灵活, 也便于维护
  4. iOS开发多注重封装思想, 能抽调出来的功能一定要封装为一个工具类
  5. 但是, 不要为了解耦去过度解耦, 弄得自己都混乱了
1EAA6434-0B24-4EEB-B96F-E1701997DEDA.png

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

4FDEB061-A77E-421E-B5FC-4B80606B252E.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容