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

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

推荐阅读更多精彩内容