您的位置:首页 > 其它

02-百思不得姐(第二天)

2016-03-11 21:57 507 查看
接下来首先完成推荐关注界面!

一.完成左侧分类导航栏



需求分析:这个分类栏可以滚动,所以用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代码也是一致!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: