Demo1
#import "ViewController.h"
#import "Status.h"
#import "StatusCell.h"
#import "MJExtension.h"
#import "MJRefresh/MJRefresh.h"
#import "AFNetworking.h"
#import "SVProgressHUD.h"
@interface ViewController ()
/** 所有的微博模型*/
@property (nonatomic ,strong) NSMutableArray *statuses;
@property(nonatomic,assign) NSInteger page;
@end
@implementation ViewController
- (BOOL)prefersStatusBarHidden{
return YES;
}
- (NSMutableArray *)statuses
{
if (!_statuses) {
_statuses = [NSMutableArray array];
}
return _statuses;
}
- (void)viewDidLoad {
[super viewDidLoad];
// self-sizing(iOS8 以后)
// 告诉tableView所有cell的高度是自动计算的(根据设置的约束来计算)
self.tableView.rowHeight = UITableViewAutomaticDimension;
// 告诉tableView所有cell的估算高度
self.tableView.estimatedRowHeight = 44;
__weak __typeof(self) weakSelf = self;
// 设置回调(一旦进入刷新状态就会调用这个refreshingBlock)
self.tableView.header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
// 将发送网络请求的方法写在这个代码块里。以后只要我们滚动tableView就会自动执行这个代码块。因为MJ在底层已经为我们实现了:如果我们滚动tableView,就会触发headerWithRefreshingBlock,执行代码块中的内容
[weakSelf getDataWithHeader:YES];
}];
self.tableView.footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
// 将发送网络请求的方法写在这个代码块里。以后只要我们滚动tableView就会自动执行这个代码块。因为MJ在底层已经为我们实现了:如果我们滚动tableView,就会触发headerWithRefreshingBlock,执行代码块中的内容
[weakSelf getDataWithHeader:NO];
}];
// 自动进入刷新状态,执行headerWithRefreshingBlock中的代码块.
// 相当于你手动下拉然后松手,就会执行headerWithRefreshingBlock中的代码块{}。
// 如果没有这句代码,程序初始运行将不会执行代买块中的内容,所以也就不会发送网络请求,所以tableView上啥也不显示
[self.tableView.header beginRefreshing];
}
-(void)getDataWithHeader:(BOOL)status
{
// 如果是下拉刷新就设置当前页数为1,否则每次都+1
if (status) {
self.page = 1;
// 下拉刷新清空以前的数据
[self.statuses removeAllObjects];
}else{
self.page += 1;
}
// 创建AFN管理者
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
// 设置参数
NSDictionary *params = @{@"page":@(self.page)};
// 设置URL
NSString *baseUrl = @"http://api.liyaogang.com/weibo/status.php";
// 发送请求
[manager POST:baseUrl parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
// 解析数据
NSMutableArray *arr = [Status mj_objectArrayWithKeyValuesArray:responseObject];
[self.statuses addObjectsFromArray:arr];
// 刷新表格数据
if (arr.count != 0) {
[self.tableView reloadData];
}else{
// 设置黑色的遮罩
[SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
[SVProgressHUD showErrorWithStatus:@"没有更多数据了"];
}
// 拿到当前的下拉刷新控件,结束刷新状态
[self.tableView.header endRefreshing];
[self.tableView.footer endRefreshing];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 拿到当前的下拉刷新控件,结束刷新状态
[self.tableView.header endRefreshing];
[self.tableView.footer endRefreshing];
NSLog(@"%@",error);
}];
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.statuses.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"status";
StatusCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
// 传递模型数据
cell.status = self.statuses[indexPath.row];
return cell;
}
@end
- 未调用beginRefreshing,程序初始运行并不会自动发送网络请求
-
未调用beginRefreshing,程序初始运行会自动发送网络请求
链接密码
Demo2
#import "MJRefresh/MJRefresh.h"
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, weak) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITableView *tableView = [[UITableView alloc] init];
// tableView.frame = CGRectMake(80, 100, 200, 300);
tableView.frame = self.view.bounds;
tableView.backgroundColor = [UIColor grayColor];
//数据源
tableView.dataSource = self;
//代理
tableView.delegate = self;
[self.view addSubview:tableView];
// 由于MJ的底层的作用,以后只要滚动tableView就会自动调用downNetWorkData,即使downNetWorkData写在了viewDidLoad中也没关系。
MJRefreshGifHeader *header = [MJRefreshGifHeader headerWithRefreshingTarget:self refreshingAction:@selector(downNetWorkData)];
[header setImages:@[[UIImage imageNamed:@"first.png"]] forState:MJRefreshStateIdle];
[header setImages:@[[UIImage imageNamed:@"second.png"],[UIImage imageNamed:@"third.png"]] forState:MJRefreshStatePulling];
[header setImages:@[[UIImage imageNamed:@"second.png"],[UIImage imageNamed:@"third.png"],[UIImage imageNamed:@"fourth.png"]] forState:MJRefreshStateRefreshing];
tableView.mj_header = header;
tableView.mj_header.automaticallyChangeAlpha = YES;
self.tableView = tableView;
// 程序初始运行自动调用downNetWorkData方法
[self.tableView.mj_header beginRefreshing];
}
- (void)downNetWorkData
{
NSLog(@"发送网络请求");
[self getNetWorkData];
}
-(void)getNetWorkData{
[self.tableView reloadData];
}
#pragma mark -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"contentOffset.y = %f", tableView.contentOffset.y);
NSLog(@"contentSize.height = %f", tableView.contentSize.height);
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ID];
cell.backgroundColor = [UIColor redColor];
}
cell.textLabel.text = [NSString stringWithFormat:@"%@-%zd", self.class, indexPath.row];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.tableView.mj_header endRefreshing];
});
return cell;
}
@end
-
未调用beginRefreshing,程序初始运行并不会自动发送网络请求
-
调用beginRefreshing,程序初始运行会自动发送网络请求
上拉加载和下拉刷新都是全局刷新[self.tableView reloadData];,知道了这一点,这句话就非常容易理解了:下拉刷新要清空数组中之前存储的数据,上拉加载不仅不清空数组中之前存储的数据而且还要将新请求到的数据拼接到之前数据的后面。
上拉加载:即使数据库中有新数据出现,也不需要,因为我们需要的是更老的数据,在Xcode中通过网络请求参数进行区分上拉加载还是下拉刷新(URL一样,请求参数不一样),后台收到网络请求根据参数识别是上拉加载,通过php代码从数据库中拿到更老的数据返回被Xcode
下拉刷新和上拉刷新 耗费流量的阶段:发送网络请求的阶段和显示UI中的图片的阶段,但是当UI中的图片已经下载完毕之后,利用SDWebImage框架再次显示这张图片时就不需要耗费流量下载了,只需要从缓存中取出来即可。
鉴于这种情况,当cell上有10条数据时,我们此时进行上拉加载,不需要清空数组A中存储的10条数据:因为你清空之后还得发送网络请求,请求回来,这10条数据显示的位置是固定不变的,变的是执行上拉加载操作得到的更老的10条数据。只需要发送网络请求,请求到更老的10条数据拼接到数组A,这样数组A中就有20条数据了,然后利用全局刷新将20条数据按照数组A中的顺序依次显示到cell上。注意:由于TableView的重用机制,之前显示的10条数据在这20条数据里面,所以前10条直接从缓存池取出来重用即可,不需要创建cell了,而剩下的10条,也就是刚刚下拉加载的10条,由于之前没有显示过,就必须得创建10个cell显示这10条数据到cell上。鉴于这种原因:上拉加载就给用户造成了一种假象:前10条数据根本就没有发生变化,后10条数据被新添加到前10条数据的后面,但实际上这20条数据进行了都进行了全局刷新,因为前10条数据在数组A中的位置始终不变,始终在前10位,并且也没有被清空,新请求到数据还拼接到了这10条数据的后面,前10条数据在cell上能看到位置发生变化才怪!!!!!
当cell上有10条数据时,我们此时进行下拉刷新,必须清空数组A中存储的10条数据:因为有新数据要显示时,必须显示到所有数据的最前面,这种情况下数组A中存储的10条数据的位置肯定发生了变化,比如有1条新数据,那么这10条数据就会错位,之前在第1个位置上的数据会下移到第2位,之前在第10个位置上的数据会下移到第11位.唯有清空数组A重新获取10条数据才能确保新数据在第一个位置(PHP后台利用SQL语句读数据库中的数据,并将数据按时间或者id排序来将最新的数据放到数组中的第一个位置,然后返回给Xcode,然后在Xcode中对数据进行字典转模型并存进数组A中,这样数组A中的第一个数据肯定是最新的数据了)。
上拉加载和下拉刷新的细节问题【重点】:运行程序,cell上显示10条数据,此时进行上拉加载又多显示出10条数据,这样cell上一共就有20条数据了。如果我们此时将cell滚动到顶部,然后进行下拉刷新,那么之前的20条数据就会被清除,就会将这次请求到的10条新数据显示到cell上。注意:此时cell上就只有10条数据了,只有再次上拉加载才会增加数据。这也就可以百分之百的确认 下拉刷新会将之前显示到cell上的数据全部清除,只会将本次请求到的数据显示到cell上(所有的APP都是这么设计的)。
下拉刷新之所以先清空可变数组A中的数据,然后再向数组A中存储本次发送网络请求得到的数据的原因:如果不清空,那么上次和本次请求到的数据会同时存储到可变数组A中,刷新tableView就会拿取可变数组A中的数据进行显示,就会造成数据的重复加载。另外数据都是从服务器的数据库中获取的,数据库就是存储数据的地方,所以你即使清空可变数组A中存储的上次发送网络请求得到的数据 也没有关系,因为数据都是从服务器的数据库中获取的,你本次发送网络请求获取到的数据肯定是最新的数据,将最新的数据存储到可变数组A中并不带有上次获取到的数据,才能保证数据不会重复加载。
清空可变数组中数据的三种方式:
方式一: [self.videoRemarkDataSource removeAllObjects];
方式二: self.videoRemarkDataSource = commentArray;
方三:self.topics=[ZBTopic mj_objectArrayWithKeyValuesArray:responseObject[@"list"]];
着重讲一下方式二:commentArray临时可变数组中存储着本次发送网络请求得到的数据,videoRemarkDataSource可变数组是全局的属性,是要拿到cellForRowAtIndexPath:方法中用到的。通过数组赋值给数组的形式,等号左侧之前存储的数据就会被覆盖掉,这种以覆盖的形式来清空可变数组中数据的方式给人一种不是清空可变数组中数据的感觉,但是确确实实是。方式三也是数组赋值给数组的形式。
注意区分[self.videoRemarkDataSource addObjectsFromArray:commentArray];这句代码并不会将videoRemarkDataSource可变数组中之前存储的数据覆盖掉,而是会添加到可变数组中,这样本次请求到的数据和上次请求到的数据会同时存在。
在哪里对服务器的数据库中的数据进行排序?:在后台利用PHP代码+SQL语句读取数据库中的字段并进行排序,这种方式通过读的方式进行排序是最稳妥的,而不是向数据库中插入数据的时候时进行排序,这样不稳妥,一般向服务器插入数据新数据都是插入到最后一行,如果插入到第一行,很麻烦且不说,公司一般也不允许。读的方式既不会影响数据库中原始的数据的存在形式,同时能够达到排序的目的,我们何乐而不为呢?
发送网络请求必须得有数据库,因为数据都在数据库中。当我们在测试MJRefresh的上拉加载,下拉刷新时,若我们没有搭建数据库,可以在代码中模拟一个类似数据库的操作.如下代码↓ ,这样每次下拉刷新调用loadNewData时,之前存储在data可变数组中的数据依然存在,并且由于atIndex:0的作用也能保证最新的数据会显示在data数组中的最前面,最终调用cellForRowAtIndexPath:方法显示cell时,最新的数据也会显示到最前面。
#pragma mark 下拉刷新数据
- (void)loadNewData
{
for (int i = 0; i<5; i++) {
[self.data insertObject:[NSString stringWithFormat:@"随机数据---%d", arc4random_uniform(1000000)] atIndex:0];
}
}
说到这里就要必要讲解一下当有数据库时,下拉刷新为啥从服务器的数据库中获取到的最新的数据会显示最前面:
向数据库中插入数据时:我们在客户端、Xcode发送的评论等数据都要发送网络请求将数据存储到数据库中,两者是通过后台的PHP语句相衔接的,通过后台的PHP语句嵌套SQL语句实现将评论等数据插入到数据库中.
获取数据库中的数据时:当我们下拉刷新时,就会发送网络请求,后台会收到请求,并根据请求中传入的参数来利用PHP语言+SQL语句读取数据库中的指定的数据,这里的SQL语句利用了排序使得获得的数据按时间或者id排序(** select * from 表 order by 时间字段 desc** 或者 ** select * from 表 order by id desc** ),经过排序之后最新的数据将会显示在最前面,然后将最新的数据返回给xcode,这就是为什么下拉刷新时最新的数据会显示最前面的原因。
熟悉一下后台代码
当有数据库时,上拉加载时为啥从服务器的数据库中获取到的数据不包括已经获取到的数据并且 会显示到已显示数据的后面:
当我们上拉加载时,就会发送网络请求,必须携带后台指定的参数,后台会根据传递的参数例如offset = 1判断是上拉加载,既然是上拉加载就拿到上次网络请求得到的最后一个数据的id,以这个id开始,向下读取数据库中的10个数据,这样就能保证上拉加载时为啥从服务器的数据库中获取到的数据不包括已经获取到的数据,但要实现从服务器的数据库中获取到的数据显示到已显示数据的后面这需要我们在Xcode中进行操作,通过[self.videoRemarkDataSource addObjectsFromArray:commentArray];代码,commentArray临时可变数组中存储着本次发送网络请求得到的数据,videoRemarkDataSource全局可变数组之前就存储着之前的获取到的数据,再加上这次本次发送网络请求得到的数据,那么videoRemarkDataSource中数组中数据顺序就是:**老数据+本次发送网络请求得到的数据(更老的数据 或者 上拉加载的数据) **
http://bbs.csdn.net/topics/390849091
[LS](https://pan.baidu.com/s/1bTpXSE 密码2bdi)