02-百思不得姐(第二天)
2016-03-11 21:57
507 查看
接下来首先完成推荐关注界面!
一.完成左侧分类导航栏
需求分析:这个分类栏可以滚动,所以用tableView,至于上面的数据则是由服务器返回的数据来显示的
1> 创建DSRecommendViewController控制器和对用的XIB,导入AFNetworking框架,自定义cell(DSRecommendCategoryCell+对应的XIB),创建模型(DSRecommendCategory)
2> 根据服务器返回的数据可知,模型有三个属性(id,name,count)
在DSRecommendCategory的.h文件中:
3> 在DSRecommendViewController对应的xib中添加一个tableView,接着在控制器中实现数据展示
首先设置导航条标题和背景色,然后发送网络请求,加载网络数据(json),利用SVProgressHUD框架显示遮盖,将里面的字典数组转成模型数组(这里得用到MJExtension框架),然后实现tableView的数据源和代理方法
数据源实现方法:
里面返回的cell是自定义cell.
.h文件:
cellWithTableView方法是创建从XIB创建的cell,然后绑定标识.
接着在XIB的cell中添加一个UILabel和UIView(左侧的红色指示条),在设置模型的时候设置label和uiview的值.
接着需要实现点击某个cell,字体会显示红色,然后显示指示条.可以通过重写setSelected:animated:方法来监听cell的选中和取消选中(该方法在点击某个cell时系统默认会自动实现)来完成该功能
这个时候还需要注意一点,就是一进来界面就需要默认选中第一个,这个我们可以在网络请求中完成,写在刷新数据后面.
注意点:cell的selection设置为none时,即使cell被选中时,内部的子控件也不会进入高亮状态
二.完成右侧用户列表(下拉加载新数据)
1> 显示数据和上面也基本一样
模型(DSRecommendUser),自定义cell(DSRecommendUserCell),关于数据源方法的调用,我们可以判断传进来的tableview是那一个,然后返回该tableview的数据.
2> 关于用户表格cell会偏上的问题
automaticallyAdjustsScrollViewInsets属性表示为是否需要自动调整ScrollView的Insets,设置为NO,然后自己调整两个tableView的contentInset,都往下移动64(状态栏+导航栏)
3> 选中某个分类,判断是不是分类表格,然后发送网络请求加载数据(也就是上拉刷新),
此时需要注意的是:我们不需要每点击一个分类就刷新一次,而是应该将已经刷新出的数据保存起来,在下次点击时直接刷新表格展示.(所以可以在分类模型中添加一条users数组属性,保存刷新后的所有用户模型)
所以应该先判断是否需要请求网络数据:
取出该行的分类模型:
如果有值就直接刷新表格:
没有值也应该刷新表格,目的是:马上显示当前category的数据,不让用户看见上一个category残留的数据
接着显示上拉刷新控件:
所以首先得在viewDidLoad方法中添加刷新控件:
下拉刷新后调用loadNewUsers方法(默认加载第一页数据):
这个方法中有几个需要注意的点:
第一个: params的问题
首先私有扩展中创建了一个params字典,该属性解决了:当自动下拉刷新时网络不好,用户可能再点击其他的类别,这个时候应该停止上一次的网路请求,所以可以保存上一次的params,然后判断每次block回调时params值是否一样,不一样就说明不是当前的请求,直接返回,反之则继续.(同时注意这句代码放的位置)
第二个: 移除该分类的所有用户数据
这句代码的目的是:当用户再次下拉刷新时,数据就会累加到之前的数据后面,为了防止这个情况,所以先清除所有数据,然后展示最新的数据
第三个:在请求成功和请求失败后都应该结束刷新
第四个:checkFooterState方法
这个方法主要是检查当前footer的状态(看数据有木有都加载完毕,如果有,就显示数据都已经加载完毕,反之则结束刷新,以便让用户再次刷新),至于判断条件total,还有上面的currentPage两个属性:
这个是在category模型中添加的两个属性,分别代表这个category模型的当前页码和所有用户:
可以通过total是否用户都加载完了,而currentPage则用来保存当前的页数,当需要上拉加载下一页时,只需要将currentPage+1然后保存到请求参数中传给服务器就可以了.
三.完成右侧用户列表(上拉加载更多数据)
由于在已经初始化好了上拉刷新控件,所以接下来只需要调用loadMoreUsers方法来加载数据就好了!
参数page就是需要加载的页数(也就是下一页).
关于上拉加载更多有个问题需要解决,就是在点击category的时候需要判断该分类的footer的状态(不然本来应该还有好几页,但是显示”没有更多数据”的BUG),这个时候就需要在点击分类的时候调用checkFooterState方法,而且应该在if判断语句后面.
还有一个问题(如果在自动下拉刷新的时候,用户点击了返回按钮,销毁了控制器,但是此时回调block,程序就会出问题),所以解决办法就是在控制器的dealloc方法中取消所有的网络请求.
首先将manager对象保存起来:
这个时候,还有一个非常明显的BUG,那就是默认点击第一个category,但是不显示数据,解决办法就是:在默认点击了第一个category后,就让用户表格开始进入下拉刷新状态
到这里,整个推荐关注界面就完成了!
接下来就完成当个点击精华首页左侧的item时,跳转到”推荐标签界面”,然后显示这个界面.
这个界面主要就是一个tableView完成的,非常简单,具体的实现就不一一列举!主要说下需要注意的地方:
1> cell距离左右边距的问题,还有cell之间的间距问题
其实这个实现非常简单,我们只需要把cell的x移动一点,然后把cell的宽度减少两倍的这个宽度,就OK了.
至于cell之间的间距这里就不采用添加一个宽度为1的分割线,而是将cell的高度都减少1,这样间距就出来了(因为控制器view背景色和cell不一样).
上面两个功能的实现,就用到了一个方法,那就是重写cell的setFrame方法,还有setBounds方法,在这个方法里面设置,一旦设置完毕,外界就无法改变了!
setBounds代码也是一致!
一.完成左侧分类导航栏
需求分析:这个分类栏可以滚动,所以用tableView,至于上面的数据则是由服务器返回的数据来显示的
1> 创建DSRecommendViewController控制器和对用的XIB,导入AFNetworking框架,自定义cell(DSRecommendCategoryCell+对应的XIB),创建模型(DSRecommendCategory)
2> 根据服务器返回的数据可知,模型有三个属性(id,name,count)
在DSRecommendCategory的.h文件中:
/** * id */ @property (nonatomic, assign) NSInteger id; /** * 分类名字 */ @property (nonatomic, copy) NSString *name; /** * 数量 */ @property (nonatomic, assign) NSInteger count;
3> 在DSRecommendViewController对应的xib中添加一个tableView,接着在控制器中实现数据展示
首先设置导航条标题和背景色,然后发送网络请求,加载网络数据(json),利用SVProgressHUD框架显示遮盖,将里面的字典数组转成模型数组(这里得用到MJExtension框架),然后实现tableView的数据源和代理方法
@interface DSRecommendViewController () <UITableViewDataSource, UITableViewDelegate> /** * 左侧分类表格 */ @property (weak, nonatomic) IBOutlet UITableView *categoryTableView; /** * 左侧分类模型数组 */ @property (nonatomic, strong) NSArray *categories; @end
- (void)viewDidLoad { [super viewDidLoad]; // 清空categoryTableView背景色 self.categoryTableView.backgroundColor = [UIColor clearColor]; // 显示背景色 self.view.backgroundColor = DSThemeColor; // 显示标题 self.navigationItem.title = @"推荐关注"; // 显示指示器 [SVProgressHUD show]; NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"a"] = @"category"; params[@"c"] = @"subscribe"; [[AFHTTPSessionManager manager] GET:@"http://api.budejie.com/api/api_open.php" parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // 字典转模型 self.categories = [DSRecommendCategory mj_objectArrayWithKeyValuesArray:responseObject[@"list"]]; // 隐藏指示器 [SVProgressHUD dismiss]; // 刷新表格 [self.categoryTableView reloadData]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { [SVProgressHUD showErrorWithStatus:@"加载推荐信息失败!"]; }]; }
数据源实现方法:
#pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.categories.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { DSRecommendCategoryCell *cell = [DSRecommendCategoryCell cellWithTableView:tableView]; cell.category = self.categories[indexPath.row]; return cell; }
里面返回的cell是自定义cell.
.h文件:
@property (nonatomic, strong) DSRecommendCategory *category; + (instancetype)cellWithTableView:(UITableView *)tableView;
cellWithTableView方法是创建从XIB创建的cell,然后绑定标识.
/** * 创建cell */ + (instancetype)cellWithTableView:(UITableView *)tableView { static NSString *identifier = @"category"; DSRecommendCategoryCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; if (cell == nil) { cell = [[[NSBundle mainBundle] loadNibNamed:@"DSRecommendCategoryCell" owner:nil options:nil] firstObject]; } return cell; }
接着在XIB的cell中添加一个UILabel和UIView(左侧的红色指示条),在设置模型的时候设置label和uiview的值.
/** * 左侧的选中条 */ @property (weak, nonatomic) IBOutlet UIView *selectedIndicator; /** * 分类名称 */ @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
/** * 设置模型数据 */ - (void)setCategory:(DSRecommendCategory *)category { _category = category; self.nameLabel.text = category.name; }
/** * 初始化 */ - (void)awakeFromNib { self.backgroundColor = DSColor(244, 244, 244, 1.0); }
接着需要实现点击某个cell,字体会显示红色,然后显示指示条.可以通过重写setSelected:animated:方法来监听cell的选中和取消选中(该方法在点击某个cell时系统默认会自动实现)来完成该功能
/** * 当选中cell时,系统就会调用这个方法 */ - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; self.selectedIndicator.hidden = !selected; self.nameLabel.textColor = selected ? self.selectedIndicator.backgroundColor : DSColor(78, 78, 78, 1.0); }
这个时候还需要注意一点,就是一进来界面就需要默认选中第一个,这个我们可以在网络请求中完成,写在刷新数据后面.
// 默认选中第一行 [self.categoryTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] animated:NO scrollPosition:(UITableViewScrollPositionMiddle)];
注意点:cell的selection设置为none时,即使cell被选中时,内部的子控件也不会进入高亮状态
二.完成右侧用户列表(下拉加载新数据)
1> 显示数据和上面也基本一样
模型(DSRecommendUser),自定义cell(DSRecommendUserCell),关于数据源方法的调用,我们可以判断传进来的tableview是那一个,然后返回该tableview的数据.
2> 关于用户表格cell会偏上的问题
// 设置insert self.automaticallyAdjustsScrollViewInsets = NO; self.categoryTableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0); self.userTableView.contentInset = self.categoryTableView.contentInset;
automaticallyAdjustsScrollViewInsets属性表示为是否需要自动调整ScrollView的Insets,设置为NO,然后自己调整两个tableView的contentInset,都往下移动64(状态栏+导航栏)
3> 选中某个分类,判断是不是分类表格,然后发送网络请求加载数据(也就是上拉刷新),
此时需要注意的是:我们不需要每点击一个分类就刷新一次,而是应该将已经刷新出的数据保存起来,在下次点击时直接刷新表格展示.(所以可以在分类模型中添加一条users数组属性,保存刷新后的所有用户模型)
所以应该先判断是否需要请求网络数据:
取出该行的分类模型:
DSRecommendCategory *category = self.categories[indexPath.row];
// 有值就说明不用再次发送请求 if (category.users.count)
如果有值就直接刷新表格:
[self.userTableView reloadData];
没有值也应该刷新表格,目的是:马上显示当前category的数据,不让用户看见上一个category残留的数据
接着显示上拉刷新控件:
[self.userTableView.mj_header beginRefreshing];
所以首先得在viewDidLoad方法中添加刷新控件:
// 初始化刷新控件 [self setupRefresh];
- (void)setupRefresh { // 下拉刷新控件 self.userTableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewUsers)]; // 下拉刷新控件 self.userTableView.mj_footer = [MJRefreshBackStateFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreUsers)]; }
下拉刷新后调用loadNewUsers方法(默认加载第一页数据):
- (void)loadNewUsers { // 取得选中的category DSRecommendCategory *category = DSSelectedRow; // 设置当前页为1 category.currentPage = 1; // 设置请求参数 NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"a"] = @"list"; params[@"c"] = @"subscribe"; params[@"category_id"] = @(category.id); self.params = params; // 发送请求 [[AFHTTPSessionManager manager] GET:@"http://api.budejie.com/api/api_open.php" parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // 字典模型 -> 模型数组 NSArray *users = [DSRecommendUser mj_objectArrayWithKeyValuesArray:responseObject[@"list"]]; // 首先清除该category的所有用户数据 [category.users removeAllObjects]; // 添加到当前category对应的用户数组 [category.users addObjectsFromArray:users]; // 保存总数到模型中 category.total = [responseObject[@"total"] integerValue]; // 如果这次刷新的params不等于保存的params,说明不是当前category发送的请求(也就是别的category发的请求) // 这句代码放在这里的目的是:既然用户发送了请求,我们就保存用户的数据,而不用再次发送请求导致浪费流量 if (self.params != params) return ; // 刷新表格 [self.userTableView reloadData]; // 结束刷新 [self.userTableView.mj_header endRefreshing]; // 检查Footer的刷新状态 [self checkFooterState]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { // 当刷新失败了,如果不是当前刷新的话,就直接返回就好了 if (self.params != params) return ; // 提示 [SVProgressHUD showErrorWithStatus:@"请求失败!"]; // 结束刷新 [self.userTableView.mj_header endRefreshing]; }]; }
这个方法中有几个需要注意的点:
第一个: params的问题
self.params = params;
首先私有扩展中创建了一个params字典,该属性解决了:当自动下拉刷新时网络不好,用户可能再点击其他的类别,这个时候应该停止上一次的网路请求,所以可以保存上一次的params,然后判断每次block回调时params值是否一样,不一样就说明不是当前的请求,直接返回,反之则继续.(同时注意这句代码放的位置)
第二个: 移除该分类的所有用户数据
[category.users removeAllObjects];
这句代码的目的是:当用户再次下拉刷新时,数据就会累加到之前的数据后面,为了防止这个情况,所以先清除所有数据,然后展示最新的数据
第三个:在请求成功和请求失败后都应该结束刷新
第四个:checkFooterState方法
- (void)checkFooterState { DSRecommendCategory *category = DSSelectedRow; if (category.total == category.users.count) { // 数据全部加载完毕 // 提示没有更多数据 [self.userTableView.mj_footer endRefreshingWithNoMoreData]; }else { // 数据没有加载完毕就结束刷新,让其能继续加载 [self.userTableView.mj_footer endRefreshing]; } }
这个方法主要是检查当前footer的状态(看数据有木有都加载完毕,如果有,就显示数据都已经加载完毕,反之则结束刷新,以便让用户再次刷新),至于判断条件total,还有上面的currentPage两个属性:
这个是在category模型中添加的两个属性,分别代表这个category模型的当前页码和所有用户:
@property (nonatomic, assign) NSInteger total; @property (nonatomic, assign) NSInteger currentPage;
可以通过total是否用户都加载完了,而currentPage则用来保存当前的页数,当需要上拉加载下一页时,只需要将currentPage+1然后保存到请求参数中传给服务器就可以了.
三.完成右侧用户列表(上拉加载更多数据)
由于在已经初始化好了上拉刷新控件,所以接下来只需要调用loadMoreUsers方法来加载数据就好了!
- (void)loadMoreUsers { DSRecommendCategory *category = DSSelectedRow; NSMutableDictionary *params = [NSMutableDictionary dictionary]; params[@"a"] = @"list"; params[@"c"] = @"subscribe"; params[@"category_id"] = @(category.id); params[@"page"] = @(++category.currentPage); [[AFHTTPSessionManager manager] GET:@"http://api.budejie.com/api/api_open.php" parameters:params progress:^(NSProgress * _Nonnull downloadProgress) { } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // 字典模型 -> 模型数组 NSArray *newUsers = [DSRecommendUser mj_objectArrayWithKeyValuesArray:responseObject[@"list"]]; // 添加到当前类别对应的用户数组 [category.users addObjectsFromArray:newUsers]; if (self.params != params) return ; // 刷新表格 [self.userTableView reloadData]; // 检查Footer的刷新状态 [self checkFooterState]; } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { if (self.params != params) return ; // 提示 [SVProgressHUD showErrorWithStatus:@"请求失败!"]; // 结束刷新 [self.userTableView.mj_footer endRefreshing]; }]; }
参数page就是需要加载的页数(也就是下一页).
关于上拉加载更多有个问题需要解决,就是在点击category的时候需要判断该分类的footer的状态(不然本来应该还有好几页,但是显示”没有更多数据”的BUG),这个时候就需要在点击分类的时候调用checkFooterState方法,而且应该在if判断语句后面.
还有一个问题(如果在自动下拉刷新的时候,用户点击了返回按钮,销毁了控制器,但是此时回调block,程序就会出问题),所以解决办法就是在控制器的dealloc方法中取消所有的网络请求.
首先将manager对象保存起来:
@property (nonatomic, strong) AFHTTPSessionManager *manager;
#pragma mark - 懒加载 - (AFHTTPSessionManager *)manager { if (_manager== nil) { _manager = [AFHTTPSessionManager manager]; } return _manager; }
- (void)dealloc { // 控制器销毁时,取消所有的网络请求 [self.manager.operationQueue cancelAllOperations]; }
这个时候,还有一个非常明显的BUG,那就是默认点击第一个category,但是不显示数据,解决办法就是:在默认点击了第一个category后,就让用户表格开始进入下拉刷新状态
// 让用户表格进入刷新状态 [self.userTableView.mj_header beginRefreshing];
到这里,整个推荐关注界面就完成了!
接下来就完成当个点击精华首页左侧的item时,跳转到”推荐标签界面”,然后显示这个界面.
这个界面主要就是一个tableView完成的,非常简单,具体的实现就不一一列举!主要说下需要注意的地方:
1> cell距离左右边距的问题,还有cell之间的间距问题
其实这个实现非常简单,我们只需要把cell的x移动一点,然后把cell的宽度减少两倍的这个宽度,就OK了.
至于cell之间的间距这里就不采用添加一个宽度为1的分割线,而是将cell的高度都减少1,这样间距就出来了(因为控制器view背景色和cell不一样).
上面两个功能的实现,就用到了一个方法,那就是重写cell的setFrame方法,还有setBounds方法,在这个方法里面设置,一旦设置完毕,外界就无法改变了!
- (void)setFrame:(CGRect)frame { frame.origin.x = 10; frame.size.width -= 2 * frame.origin.x; frame.size.height -= 1; [super setFrame:frame]; }
setBounds代码也是一致!
相关文章推荐
- java中初始化与构造器
- Linux下如何查看高CPU占用率线程 LINUX CPU利用率计算(转)
- CSS 中 Font-Family 中英文对照表
- oracle完整卸载
- java笔记-9
- Unity插件 DOTween基础
- CodeForces-630 F. Selection of Personnel【排列组合】
- 实现用画笔画出一个带有渐变效果的线条
- TCP协议中的SO_LINGER选项
- SQLite清空表并将自增列归零
- 最全SpringMVC具体演示样例实战教程
- 使用Python保存屏幕截图(不使用PIL)
- java练习 - 字符串反转
- web.xml
- 完成作业的先后顺序
- iOS UIPickerView 显示全国省市
- Android之–Activity通过Intent传递Object
- Precision和Recall
- bzoj3571: [Hnoi2014]画框
- java笔记-7