美文网首页iOS入门资源
05.项目实战 百思不得姐 精华基本界面搭建

05.项目实战 百思不得姐 精华基本界面搭建

作者: Liwx | 来源:发表于2016-01-27 01:59 被阅读539次

    @(iOS 项目实战)[项目实战]


    目录

    • 05.项目实战 百思不得姐 精华基本界面搭建
    • 1.界面分析
      • 精华界面结构分析
    • 2.精华界面标题栏
      • 标题栏的结构
      • 标题栏的实现
      • 知识点补充
    • 3.精华界面的scrollView
      • 滚动切换界面和选中标题
      • UITableView重要属性分析

    1.界面分析

    精华界面结构分析

    • 1.精华界面结构

    精华界面结构: 由一个占据整个屏幕的UIScrollView和一个标题栏UIView组成,UIScrollView内嵌5个UITableView,标题栏UIView加到控制器的view上.

    精华界面结构.png

    2.精华界面标题栏

    标题栏的结构

    标题栏UIView内部子控件: 5个标题按钮和1个下划线(UIView)组成.


    标题栏的结构.png

    标题栏的实现

    • 1.创建一个UIView类型的标题栏,添加到控制器的view,并添加5个按钮到标题栏titleView.

      • 5个标题按钮平均分布到标题栏titleView.
      • 设置点击选中按钮时,选中按钮的文字为红色

      实现方案有三种:
      - 直接改颜色(不推荐)
      [titleButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
      - 切换按钮选中状态.(可以重复点击,本项目使用该方式)
      需取消按钮高亮状态,自定义按钮,重写按钮的setHighlight:方法,setHighlight:方法不做任何操作.

        - 如果`不要重复点击,可以用按钮的disable状态`.(不可以重复点击)
      
      • 监听按钮点击,点击按钮切换按钮的选中状态.(切换中状态三部曲)
          // 切换中状态
          self.selectedButton.selected = NO;
          button.selected = YES;
          self.selectedButton = button;
      
      • 设置标题栏的背景颜色为白色透明色.(三种方式任选一种)
      // 设置背景色为白色透明色
      titlesView.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.5];
      titlesView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.5];
      titlesView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5];
      

      • 标题栏实现参考代码
        // ----------------------------------------------------------------------------
        // 设置标题栏
        - (void)setupTitleView
        {
            // 1.创建titleView
            UIView *titleView = [[UIView alloc] init];
            titleView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.7];
            titleView.frame = CGRectMake(0, WXNavMaxY, screenW, WXTitlesViewH);
            [self.view addSubview:titleView];
            self.titleView = titleView;
            
            // 2.添加titleView的按钮
            [self setupTitleViewButtons];
            
            // 3.添加下划线
            [self setupUnderline];
        }
        // ----------------------------------------------------------------------------
        // 添加标题栏按钮
        - (void)setupTitleViewButtons
        {
            // 按钮文字
            NSArray *titles = @[@"全部", @"视频", @"声音", @"图片", @"文字"];
            
            NSInteger count = titles.count;
            CGFloat btnX = 0;
            CGFloat btnY = 0;
            CGFloat btnW = self.titleView.wx_width / count;
            CGFloat btnH = self.titleView.wx_height;
            for (NSInteger i = 0; i < count; i++) {
                
                // 1.创建按钮,并设置属性
                WXTitleButton *button = [WXTitleButton buttonWithType:UIButtonTypeCustom];
                button.tag = i;
                [button setTitle:titles[i] forState:UIControlStateNormal];
                [button addTarget:self action:@selector(titleButtonClick:) forControlEvents:UIControlEventTouchUpInside];
                
                // 2.计算x值
                btnX = i * btnW;
                // 3.设置按钮的frame
                button.frame = CGRectMake(btnX, btnY, btnW, btnH);
                
                // 4.添加到titleView
                [self.titleView addSubview:button];
            }
        }
        
        // ----------------------------------------------------------------------------
        // 添加标题栏的下划线
        - (void)setupUnderline
        {
            // 1.创建下划线view
            UIView *underLineView = [[UIView alloc] init];
            WXTitleButton *firstButton = self.titleView.subviews.firstObject;
            CGFloat underLineH = 2;
            underLineView.frame = CGRectMake(0, self.titleView.wx_height - underLineH, 100, underLineH);
            underLineView.backgroundColor = [firstButton titleColorForState:UIControlStateSelected];
            [self.titleView addSubview:underLineView];
            self.underLineView = underLineView;
            
            // 计算下划线的宽度,直接通过按钮Label的宽度
            [firstButton.titleLabel sizeToFit];
            underLineView.wx_width = firstButton.titleLabel.wx_width;
            underLineView.wx_centerX = firstButton.wx_centerX;
            
            // 设置默认选择
            firstButton.selected = YES;
            self.selectedButton = firstButton;
        }
      

    • 2.添加底部下划功能实现步骤

      • 1.获取titleView中任意一个按钮,如获取第一个按钮.获取按钮的主要目的是为了拿到其按钮内部titleLabel的宽度选中状态下文字的字体颜色.

      • 2.创建高度为2的UIView类型的下划线添加到titleView标题栏,设置下划线的背景颜色为按钮选中状态下的文字颜色.
        通过titleColorForState:方法获取按钮指定状态的文字颜色.

      • 3.获取下划线宽度.为实现下划线的宽度和按钮文字长度一致,获取按钮文字宽度有三种方法.

        • 方法一: 先currentTitle方法: 获取当前状态的标题,再使用sizeWithFont:方法(过期): 传入字体计算一段文字的宽度(只能计算一行).
          - 方法二: 先currentTitle方法: 获取当前状态的标题,再使用sizeWithAttributes:方法: 获取按钮文字的宽度.
          - 3.方法三: 通过获取按钮的titleLabel的宽度,titleLabel的宽度就等于按钮文字的宽度(本项目使用该方法)
      • 设置下划线宽度为按钮titleLabel的宽度.

        • 注意: 设置下划线的宽度和中心点时,需先设置宽度,再设置中心点.
          viewDidLoad方法不能获取到控件的真实frame,所以需在设置下划线宽度和中心点前先获取到按钮titleLabel的真实尺寸,使用sizeToFit方法让按钮的titleLabel立即更新为真实尺寸.
      • 点击按钮让下划线的中心点x值等于被点击按钮的中心点x(动画改变).

      • 设置默认选中第0个按钮

      • 下划线功能实现参考代码

        // ----------------------------------------------------------------------------
        // 添加标题栏的下划线
        - (void)setupUnderline
        {
            // 1.创建下划线view
            UIView *underLineView = [[UIView alloc] init];
            WXTitleButton *firstButton = self.titleView.subviews.firstObject;
            CGFloat underLineH = 2;
            underLineView.frame = CGRectMake(0, self.titleView.wx_height - underLineH, 100, underLineH);
            underLineView.backgroundColor = [firstButton titleColorForState:UIControlStateSelected];
            [self.titleView addSubview:underLineView];
            self.underLineView = underLineView;
            
            // 计算下划线的宽度,直接通过按钮Label的宽度
            [firstButton.titleLabel sizeToFit];
            underLineView.wx_width = firstButton.titleLabel.wx_width;
            underLineView.wx_centerX = firstButton.wx_centerX;
            
            // 设置默认选择
            firstButton.selected = YES;
            self.selectedButton = firstButton;
            
        }
      

    知识点补充

    • 按钮不能点击的两种方法
      • enable = NO; 无法点击,并且进入UIControlStateDisable状态.
      • userInteractionEnable = NO;按钮不能点击,保持原有的状态.

    • 指定构造方法

      • 方法声明后面带有NS_DESIGNATED_INITIALIZER的构造方法.

      • 特点: 子类如果重写了指定构造方法,那么久必须用super来调用父类的一个指定构造方法.否则会有警告.重写构造方法时如果是用NS_DESIGNATED_INITIALIZER修饰的构造方法,必须调用super的构造方法,因为系统内部可能会对其做一些处理.
        警告如下图所示:

        警告.png
      • init方法内部会调用initWithFrame:方法,所以在initWithFrame:调用init方法会死循环

      • 如系统UIView的构造方法

        // 带有`NS_DESIGNATED_INITIALIZER`的构造方法
        - (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
        - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
      

    • KSImageNamed图片提示注意点

      • 如果用KSImageNamed图片提示插件,只有标记imageset才是放在Assets.xcassets资源包里面.
        有标记imageset.png

      • 注意: 如果是放在第三方类库,比如放在SVProgressHUD.bundle中的这些图片用imageName是获取不到的.
        SVProgressHUD.bundle的图片.png

    • 按钮知识点补充
      • currentTitle方法: 获取当前状态的标题
      • sizeWithFont:方法(过期): 传入字体计算一段文字的宽度(只能计算一行).
      • sizeWithAttributes:方法: 传入字典参数(富文本属性),凡是有Attributes和NSDictionary混用就是描述富文本属性.

    • 富文本属性宏定义头文件:
      iOS7之前: 在UIStringDrawing.h头文件,key的格式: UITextAttributeXXX.
      iOS7之后: 在NSAttributedString.h头文件,key的格式: NSXXXAttributeName.
      NS开头能用在Mac/iOS开发,UI开头只能在iOS开发.

      // 设置富文本属性
      NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
      // iOS7之后: NSFontAttributeName在NSAttributedString.h头文件
      attrs[NSFontAttributeName] = [UIFont systemFontOfSize:20];
      // iOS7之前: UITextAttributeFont在`UIStringDrawing.h`头文件
      attrs[UITextAttributeFont] = [UIFont systemFontOfSize:20];
      

    3.精华界面的scrollView

    • 1.创建scrollView

      • 设置frame代理delegate属性.
      • 设置scrollView的滚动范围contentSize属性
      • 设置开启分页功能,隐藏水平垂直滚动条.
    • 2.给精华控制器添加5个子控制器(继承自UITableViewController)

    • 3.添加5个子控制器的view分别添加到scrollView对应位置.(此方式有性能不好,暂不考虑性能,后续会优化)

      • 此时UITableView布局存在问题,如下图所示:
        UITableView布局存在问题.png

      • tableView全屏穿透,并且不会被NavBar,TabBar挡住,那么必须有2个条件

      • 1.UITableView的尺寸根屏幕一样大,占据整个屏幕.(重要).

      • 2.设置UITableView的顶部和底部的内边距.

      • 让TableView全屏正常显示解决方案

      1.设置scrollView不添加额外滚动区域
      self.automaticallyAdjustsScrollViewInsets = NO;
      2.是指tableView的y值为0,且高度为scrollView的高度
      vc.tableView.wx_y = 0;
      vc.tableView.wx_height = self.scrollView.wx_height;
      3.在各个子控制器设置额外滚动区域
      顶部额外滚动区域: 导航条高度64+标题栏高度35 底部额外滚动区域: TabBar的高度49.
      self.tableView.contentInset = UIEdgeInsetsMake(WXNavMaxY + WXTitlesViewH, 0, WXTabBarH, 0);

    • 按钮点击scrollView滚动到对应的TableView上,显示tableView的内容.

      #pragma =======================================================================
      #pragma mark - titleButton按钮点击
      // ----------------------------------------------------------------------------
      // 监听按钮点击
      - (void)titleButtonClick:(WXTitleButton *)button
      {
          // 切换中状态
          self.selectedButton.selected = NO;
          button.selected = YES;
          self.selectedButton = button;
          
          [UIView animateWithDuration:0.25 animations:^{
              
              // TODO: 设置下划线的宽度和中心点
              self.underLineView.wx_width = button.titleLabel.wx_width;
              self.underLineView.wx_centerX = button.wx_centerX;
              
              // 切换到对应的view
              self.scrollView.contentOffset = CGPointMake(self.scrollView.wx_width * button.tag, self.scrollView.contentOffset.y);
          }];
      }
      

    滚动切换界面和选中标题

    • 在监听按钮点击方法中获取按钮在titleView的index

      • 方法一: 使用按钮的tag值获取index,用于设置滚动偏移量.
        self.scrollView.contentOffset = CGPointMake(self.scrollView.wx_width * button.tag, self.scrollView.contentOffset.y);
      • 方法二: 使用contentOffset.x计算index,使用indexOfObject:方法获取按钮在父控件titleView的index
        NSUInteger index = [self.titlesView.subviews indexOfObject:titleButton];
        CGFloat offsetX = index * self.scrollView.wx_width;
    • 代理监听scrollView滚动完毕

      #pragma =======================================================================
      #pragma mark - UIScrollViewDelegate代理方法
      - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
      {
          // 1.获取索引
          NSInteger index = self.scrollView.contentOffset.x / self.scrollView.wx_width;
          
          // 2.根据索引获取按钮
          WXTitleButton *titleButton = self.titleView.subviews[index];
          
          // 3.调用按钮的点击事件
          [self titleButtonClick:titleButton];
      }
      
    • viewWithTag方法实现原理

      • 控件默认的tag值是0
      • viewWithTag会先比较本身的tag,再对比子控件的tag.
      • 递归查找tag(从前往后遍历).
      • 使用viewWithTag性能低.因为需要递归遍历查找.
      • 如果在scrollViewDidEndDecelerating:代理方法中用viewWithTag获取按钮,当拖动到第0个TableView是,会报错:-[UIView setSelected:]: unrecognized selector sent to instance 0x7fb4335958b0,原因是viewWithTag会先比较本身的tag,默认view本身的tag是0,如果返回的是view本身,而不是按钮.view 没有setSelected:方法,所以调用会出错.最好不要使用viewWithTag,因为它是通过遍历来查找的,效率低,性能差.
        @implementation UIView
        
        - (UIView *)viewWithTag:(NSInteger)tag
        {
            // 如果自己的tag复合条件,返回自己
            if (self.tag == tag) return self;
            
            for (UIView *subview in self.subviews) {
                UIView *resultView = [subview viewWithTag:tag];
                if (resultView) return resultView;
            }
            
            return nil;
        }
        
        @end
      

    • 经典错误分析
    // UIView 调用了setSelected:方法,找不到该方法
    -[UIView setSelected:]: unrecognized selector sent to instance 0x7fb4335958b0
    
    // 将WXPerson当字符串使用报错
    -[WXPerson length]: unrecognized selector sent to instance 0x7fb4073958b0
    NSString *str = [[XMGPerson alloc] init];
    str.length;
    
    // 将WXPerson当数组使用报错
    -[WXPerson count]: unrecognized selector sent to instance 0x7f35355958b0
    NSArray *array = [[XMGPerson alloc] init];
    array.count;
    
    // 将WXPerson当字典使用报错
    -[WXPerson setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x7843335958b0
    NSMutableDictionary *dict = [[XMGPerson alloc] init];
    dict[@"name"] = @"jack";
    

    UITableView重要属性分析

    • tableView重要属性概念

    contentSize : 内容大小
    contentOffset : 偏移量
    contentInset : 内边距
    frame : 矩形框,以父控件内容左上角为坐标原点

    • tableView重要属性总结

    contentSize.height : 【内容】的总高度
    【内容】: cell,tableHeaderView,tableFooterView,sectionHeader\Footer.
    contentOffset.y : 【内容顶部线】和【frame顶部线】的差值.contentOffset和contentSize都不包含内边距.也就是说contentOffset和contentSize不受contentInset影响.

    contentInset : 在【内容】的周围增加一段间距

    • UITableView重要属性分析图解


      UITableView重要属性分析图解.png

    相关文章

      网友评论

        本文标题:05.项目实战 百思不得姐 精华基本界面搭建

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