美文网首页iOS开发
iPad小案例 -- QQ空间界面

iPad小案例 -- QQ空间界面

作者: 面糊 | 来源:发表于2016-06-04 00:02 被阅读573次

    一. 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

    相关文章

      网友评论

        本文标题:iPad小案例 -- QQ空间界面

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