@(iOS 项目实战)[项目实战]
- 作者: Liwx
- 邮箱: 1032282633@qq.com
目录
- 05.项目实战 百思不得姐 精华基本界面搭建
- 1.界面分析
- 精华界面结构分析
- 2.精华界面标题栏
- 标题栏的结构
- 标题栏的实现
- 知识点补充
- 3.精华界面的scrollView
- 滚动切换界面和选中标题
- UITableView重要属性分析
1.界面分析
精华界面结构分析
- 1.精华界面结构
精华界面结构: 由一个占据整个屏幕的UIScrollView和一个标题栏UIView组成,
UIScrollView
内嵌5个UITableView
,标题栏UIView加到控制器的view
上.
2.精华界面标题栏
标题栏的结构
标题栏UIView内部子控件: 5个标题按钮和1个下划线(UIView)组成.
标题栏的实现
-
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的构造方法
,因为系统内部可能会对其做一些处理.
警告如下图所示:
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资源包里面
.
- 注意: 如果是放在第三方类库,
比如放在SVProgressHUD.bundle中的这些图片用imageName是获取不到的
.
- 如果用KSImageNamed图片提示插件,
- 按钮知识点补充
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布局存在问题
,如下图所示:
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;
- 方法一: 使用按钮的tag值获取index,用于设置滚动偏移量.
-
代理监听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重要属性分析图解