一、咱们上午就做了两件事儿,
1.把我们的数据,加载起来,
2.实现了下面这个”加载更多“按钮的功能,

3.只不过,我们加载数据的时候,用了一个自定义cell,
那么,基本加载数据的办法,我就不再说了,
基本,就是那些步骤,
只是把我们自定义cell部分,再给大家复习一下,
上午,我们在控制器里面,是不是有个数据源方法,
咱们上午,就是在我们这个数据源方法中,返回单元格的数据源方法中,

我们有这么四步,
1)第一步,获取模型数据,
2)第二步,创建单元格,
3)第三步,把模型,设置给单元格,
4)第四步,返回单元格,
只是在第二步,创建单元格的时候,以前是怎么创建的,
以前是直接UITableView *cell = 直接从缓存池里面取,如果不到的话,直接alloc init,这种方式创建,
然后,我们上午,创建cell的时候,就是用一个xib来表示,这里建了一个xib,在xib里面,拖拉拽,拽了这么一个cell,然后呢,设置一下这个cell的identifier,

设置它这么一个重用ID,
然后,我们在代码里面,让这个拽好的cell,与我们新建的具体这么一个类型,这个自定义类型,

自定义cell,相关联,
然后,这样的话,当通过xib,创建这个cell以后,就是创建这个类的一个对象,
然后,我们在这个控制器里面,我们用这个自定义的cell,调它的一个类方法,

调它的一个类方法,叫goodsCellWithTableView,
然后,在这个类方法里面,我们其实就是干什么呢,

其实,就是封装了一个通过xib,创建cell,的这么一个代码,
通过xib,创建cell的代码,就是,
1)首先,获取我们的根目录,在根目录里面,调它的loadNibNamed,这个方法,加载这个xib,@“CZGoodsCell”,
2)然后,这个方法,会返回一个数组,在这个数组当中,我们取得第一个对象,或者最后一个对象,就是我们这个xib里面,这个cell吧,

因为,这个xib里面,只有一个cell,所以说,获取到的就是这个cell,
OK,这样的话,在控制器里面,就可以自定义一个cell了,
拿到这个cell,

拿到这个cell以后,然后我们基本上上面这个滚动,就做完了,

上面这个数据,就做完了,
2.然后,接下来,我们就实现一个“加载更多”,
“加载更多”,它首先是在我们tableView的tableFooterView里面,
有一个“加载更多”,
因为,这个“加载更多”里面,不仅仅是一个按钮,
里面是不是有可能,有一些其他的一些控件吧,

所以说,我们这里,footerView里,要加一个UIView,
在UIView里面,再放一些其他的子控件,而不是下面只是一个按钮,
所以,我们这个时候,就发现,footerView,“加载更多”,这块儿代码,这个块儿内容,是不是也可以使用一个xib来描述啊,
所以,我们就又建了一个xib,

我们建两个xib了,
1)一个是描述单元格的一个xib:CZGoodsCell.xib,
2)一个是描述footerView的一个xib:CZFooterView.xib,


一个是描述单元格的xib,
一个是描述footerView的xib,
1)在这个xib里面,首先我们就拽了一个“加载更多”按钮,
2)然后,我们又放了一个,用来显示等待信息的View,

3)然后,我们通过拖线的方式,让两个属性和它相关联,

这里,两个属性,一个是btnLoadMore,一个是waitingView,
让这两个属性,分别和我们这个View,和上面按钮相关联,
设好关联以后,我们接下来,为我们这个“加载更多”按钮,注册单击事件,
这就是“加载更多”按钮的单击事件,

3.在这个单击事件里面,
1)首先,第一步,就是让我们,当点“加载更多”按钮的时候,就让“加载更多”按钮隐藏掉,
2)第二步,让waiting,那个“等待指示器”,显示出来,
3)第三步,更新数据,
但是我们发现,更新数据,其实就是创建新的model,把它加到那个数据集合里面,就可以了,但是我们在footerView里面,发现在这里是访问不到,无法访问到我们这个控制器里面这个goods集合的,

所以说,这个时候,就遇到一个问题,你在footerView这个自定义的一个View里面,想访问控制器里面那个集合,但是访问不到,这个时候,我们首先想到的就是什么,代理吧,
那么,在用代理之前,我们基本的思考方式,就是
1)首先,想现在要为谁找代理,footerView吧,

现在是不是要为footerView,是不是要为它找代理,
因为它想做一件事儿,它自己干不了,所以这个时候,它就看一下,谁能干了呢,因为那个goods集合是在我们的控制器里面,所以说,它发现,控制器能做这个事儿,所以说,这个时候,就找谁作为它的代理,控制器吧,
然后,到此为止,我们就分析出了,现在是谁要找代理,然后呢,让谁作为它的代理,那么就是footerView,它要干一件事儿,它干不了,所以,它要找代理,这件事儿,谁能干了,控制器能干了,所以说,让控制器作为它的代理对象,所以说,谁找代理,谁是代理,这两个就已经确定了,
然后,接下来,就是哪个控件要找代理,是不是要为这个控件,写一个代理协议啊,
2)写代理协议,现在就是,CZFooterView,这个控件要找代理,所以说,就在这个控件的头文件里面,新建这么一个,定义这么一个协议,

这个协议就是,为这个控件,CZFooterView,代理而生的一个代理协议,就是这个控件的一个代理协议,
一般命名规则就是,控件名后面,跟一个Delegate,
控件名后面,跟一个Delegate,就是这个控件的一个代理协议,
然后,写好代理协议以后,就是
3)代理协议里面这个方法,叫什么,
这个方法的命名规则就是,这个代理协议是哪个控件的代理协议,前面就是控件名,当然,去除那个前缀,控件名,后面加一个具体的方法名称,
然后,一般情况下,你为控件写代理,这个代理方法中,一定会有一个参数,这个参数就是,哪个控件写代理,这个参数就是对应的控件,
现在,我们的footerView,要找代理,所以说,为它写好了一个代理协议,
写好一个代理协议以后,紧接着,第二步,就是
4)为我们控件,加一个delegate属性,

为控件,加一个delegate,这个属性,
这个代理这个属性,一般名称就叫delegate,这是我们的约定,
然后呢,还有就是我们这个属性里面的类型,就是id类型,同时要用一个协议来约束一下,
id< CZFooterViewDelegate >
它必须是遵守这个协议的,
然后,我们的自定义控件里面,就是写代理,就基本写完了,
这里创建代理协议,这里有个代理属性,
然后,紧接着,就是自定义控件里面,什么时候要用到这个代理对象呢,在这个CZFooterView.m文件中,
5)当我们点击“加载更多”的时候,

接下来,是不是要调我们代理里面的这个方法,footerViewUpdateData:
来更新tableView的数据啊,
所以说,接下来,我们就是要用一下代理,
自定义控件里面,
1)第一,写代理协议,
2)第二,增加一个代理属性,
3)第三,使用代理,
在这个地方,首先判断,当前这个代理对象里面,是否有这个方法,

如果有这个方法,调一下当前这个footerView控件自己的delegate属性里面的,这个footerViewUpdateData:,方法,把当前footerView,自己传进来,
[self.delegate footerViewUpdateData:self];
这是不是就是使用代理,

这,就是使用代理,
那么,现在我们基本上这个footerView控件就写完了,
那么,它写完以后,要想用代理,是不是你得先给它设置一个代理对象啊,
3.所以说,这个时候,就找到我们控制器,

控制器这儿,viewDidLoad里面,在我们控制器的这个View加载完毕以后,接下来,我们就在这里,创建了一个footerView,

通过xib来创建footerView,
通过xib,创建好了这个footerView,
接下来,设置当前这个footerView控件的代理对象,等于控制器自己,
然后,我们就可以把footerView这个控件,设置给当前tableView的这个tableFooterView的这个属性了,
然后,footerView里面,就有这个我们自己写的这个footerView控件了,

然后,我们运行起来之后,你再点击“加载更多”,加载更多按钮被隐藏,同时显示“等待指示器”,同时“驴肉火烧”被显示出来了,
##
基本上,我们上午就说到这儿了,
二、我们希望的是,当点击“加载更多”的时候,这里显示这个“驴肉火烧”,同时,这个是不是要转一下,应该是这个转一下,然后是不是再显示这个“驴肉火烧”,
1.显示完毕以后,接下来,这个东西,是不是要再隐藏掉,等待指示器,再隐藏掉,
“加载更多”按钮,是不是再显示回来,
那么,这个操作,在哪儿写呢,
还是找到我们这个footerView,

我们希望是,一开始先隐藏”加载更多“按钮,
显示等待指示器,
然后呢,开始刷新数据,
先判断一下,代理是否实现了代理方法,footerViewUpdateData:
如果实现了代理方法,则调用代理方法
[self.delegate footerViewUpdateData:self];
然后,接下来,数据刷新完毕以后,把这两个再给它显示回来啊,
把“加载更多”按钮,再显示回来,
把“等待指示器”,再隐藏回去,
但是大家想,我如果这么写,对不对,

这么写大家觉得对不对,序号改一下,

这么写,大家觉得对不对,NO,和YES,改一下,

这样写是不对的,为什么,
这样写,你其实看不到效果的,
因为,你一开始,让它隐藏(加载更多按钮),让它显示(等待指示器),执行完这句代码,是不是紧接着,立刻执行这段代码(让加载更多按钮显示,让等待指示器隐藏),
相当于,你刚刚把它隐藏(加载更多按钮),把这个显示(等待指示器),然后呢,后来又把第一个显示(加载更多按钮),又把第二个隐藏(等待指示器),
是不是在很短很短的时间内,是不是它又恢复到原来的状态了,
所以,这样写,是不行的,
那么,我们为了模拟这个效果,是不是需要让它隔一段时间以后,再执行这段代码(让加载更多按钮显示,让等待指示器隐藏),
如果你希望,隔一段时间以后,再执行另外一段代码,这个时候就可以使用,dispatch,
5.隔一段时间再执行代码,dispatch,

注意,dispatch,是不是又这么多个dispatch啊,
用哪个呢,就找这个GCD的,
记的时候,你就记住,第一个单词是dispatch,后面有个GCD,
这些看都别看,直接选这个,
直接就选这个,然后回车,


直接就选这个GCD的dispatch,
然后这个地方,注意这个delayInSeconds,

这个就是延迟多少秒以后,执行,延迟多少秒以后,我们希望它延迟1.0秒钟以后,

延迟1.0秒钟以后,
延迟1.0秒钟以后,执行什么呢,
code to be executed after a specified delay,
在一个指定的、特定的,延迟以后,要执行代码啊,
我们这个,在1.0秒钟之后,然后再开始,加载我们的数据,

别一点它(“加载更多”按钮),立刻就把那个“红烧肉”显示出来,好像给人感觉很假的样子,其实本来就很假,但是我们为了模拟的像一些,
所以说,当点完“加载更多”以后,在1秒钟之后,我们再执行这个代码,
执行这个代码,是不是调用代理,调用代理,是不是把那个tableView的数据是不是刷新出来了,数据刷新出来了以后,紧接着,然后再把这两个按钮、这两个控件,恢复到原来的状态,让“加载更多”按钮显示,隐藏“等待指示器”,

这就是,当你点击这个按钮的时候(btnLoadMore),

1)首先要“加载更多”按钮隐藏,
2)让我们下面“等待指示器”显示,
3)隔一秒钟之后,刷新一下tableView,

给它增加一条新数据,当新数据增加完毕以后,
4)再把“加载更多”按钮,让它显示,
5)再让“等待指示器”隐藏,
运行一下看看,它会不会等待一秒钟,

是不是等待一秒钟了,
这个是不是也回来了(加载更多按钮),
这是不是就是这个效果吧,
这就是我们“加载更多”,模拟这么一个效果,
大家记这个dispatch的时候,你就先记dispatch,后面有个GCD,
用这个就OK了,
用这个,直接就双击,双击完毕以后,第一个参数,这里写的是,时间间隔,不是间隔,是“等待1秒钟”之后执行,里面的这个代码,

ok,这就是我们这里所说的这么一个延迟,
好,然后,紧接着我们再看一下,这个地方,
二、我们这个创建这个footerView,

1.我们这里创建这个footerView的代码,大家觉得有问题吗,在ViewController.m文件中,
我们这里创建这个footerView,这个代码,是不是直接通过xib,加载footerView的代码,是不是直接写在这个地方了,
把通过xib加载footerView,的代码,直接写在这里,
这样写好吗,不好,有什么不好,
还是那两个原因,
1)如果说,我有100个地方,用到了这个footerView,我是不是在100个地方,得手动去加载一下xib,是不是,这是第一个问题,
2)第二个问题是,你这个封装的不够,那么现在这个footerView,是通过xib来写的,可以这么写,一旦你footerView,它不再通过xib来创建了,这个时候,你把这个代码一改,是不是100个地方都得改,
因为100个地方,都是直接通过xib来加载的啊,
所以说,我们还是希望封装一个类方法,那么把这个通过xib加载这个自定义控件的代码,封装到这个类方法里面,
然后你需要用到这个footerView的地方,直接调这个类方法,创建一个footerView,就OK了,
就是,把这个也是封装到一个类方法里面,
把通过xib创建footerView控件的代码,封装到一个类方法当中,
CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@“CZFooterView” owner:nil options:nil] lastObject];

2.所以说,我们把这个代码,再给它封装一下,来,找到我们的这个footerView,找到CZFooterView.h文件,

来一个类方法,+ (instancetype)footerView;

实现一下,在CZFooterView.m文件中,

+ (instancetype)footerView{
//在里面,其实很简单,就是通过xib,创建一个footerView,然后把它返回,
CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@“CZFooterView” owner:nil options:nil] lastObject];
return footerView;
}

有人说,你这里面不就也是这句话吗,对,也是这句话,但是,你如果这么写的话,在控制器里面,调的时候,就没有必要这样调了吧,

是不是直接调它的类方法,就OK了,
CZFooterView *footerView = [CZFooterView footerView];

然后,如果说,哪一天,这个xib,这个footerView不是根据xib来创建的,你只要把footerView里面,把这个方法中的代码,改了就OK了,

对于控制器来说,控制器的这儿的代码,需要改吗,不需要吧,因为它是不是调这个方法去了,

这样就达到了,当需要修改的时候,只需要修改你特定的地方,不需要修改项目中其他的地方,

这样就尽量降低了耦合度,控制器就不会依赖于,对应的这个footerView了,footerView的改变,不会影响控制器的代码,这是我们的一种设计思想,
所以说,我们把footerView这里,再做一次封装,
好,到此为止,我们这个加载更多,也OK了,
三、然后,这里再给大家提一点,在我们这个代理方法当中,
1.找到控制器里面这个代理方法,控制器里面,有个footerView这个代理方法,


在控制器的这个代理方法当中,
我们是不是,
1)首先,创建了一个模型,
2)然后,把模型加到了这个集合当中,
3)然后,刷新了一下tableView,
刷新tableView,我这里调的是什么方法,调的是reload,方法吧,
reload,方法,是不是直接刷新整个的tableView,
那么这个时候,大家可能会想到,还记得咱们昨天说过两种刷新tableView的方法吗,是不是那个是局部刷新,只是刷新某一行吧,
修改了某一行,就刷新某一行,
大家觉得,这个时候,用那个好,还是用这个好,
为什么用这个好,
我觉得用那个好吧,那个只是刷新一行,这个是刷新整个数据啊,
我们这里其实不是说用哪个好的问题,那个就不能用,
只能用这个,

只能用这个,reloadData,
为什么呢,
咱们昨天说过,那个reloadRows,是不是那种刷新方式,只适用于总行数没有发生变化的情况下吧,
就是说,一开始是10行,然后呢,比如说我们昨天是有个“暴走萝莉”,咱们把它的名字给它改成“暴走”了,
那行,一开始就有那行,我只是把那行里面的内容改了,
那么,整体上,UITableView,整体的行数,是不是没有变啊,
UITableView的总行数没有变,
在这种情况下,是可以使用reloadRows,那个方式的,
就是,总行数没有发生变化,只是改了某一行中的内容,
如果说,总行数发生了变化,比如说,原来是10行,你LoadMore,是不是又增加了几行,
又增加了几行,tableView中的总行数,发生变化了吧,
当它的总行数,发生变化了以后,就不能使用这个reloadRows,局部刷新,必须使用reloadData,整体刷新了,
如果我这里,偏偏就要使用局部刷新的话,我们看看会报一个什么错,为什么会报这么一个错误,

//局部刷新(只适用于UITableView总行数没有发生变化的情况)
这里依然是调用tableView,对象的reload,不是Data,

self.tableView reloadRowsAtIndexPaths : (NSArray *) withRowAnimation : (UITableViewRowAnimation)

这个里面,首先,需要一个数组,这个数组,是不是你要刷新哪些行吧,
是不是把那些行对象,给我啊,
这里,你要刷新的那些行,我们都用indexPath,来封装吧,
就是第几组的第几行,这里,其实就增加了一行,这里只增加了一行吧,

这里是不是传一个对象过来,就可以了,
但是,我们是不是得创建一个组对象,
怎么创建,还记得吗,
NSIndexPath *idxPath = [NSIndexPath indexPath

是不是有一个类方法吧,
indexPathForRow,

第几个section,呢,我们这里是不是只有1组,
是不是第0个section,

然后,第几行呢,是不是最后一行,
是不是始终是最后一行,我们新加新行,是不是始终在最后一行,
最后一行,怎么获取最后一行,
当前,集合中,有多少条数据,
self.goods.count,
count,减去,1,
是不是就是最后一行的索引,
self.goods.count - 1 ,
如果当前是10条,最后一行的索引,就是10 减 1 ,就是几,9 吧,
所以说,这是不是就拿到了最后一行的行对象啊,

然后,把它写在这个里面,

然后,后面这个参数,需要动画吗,
UITableViewRowAnimation,

UITableViewRowAnimationLeft,

这样的话,是不是通过一个动画的方式,来刷新,
咱们试试,看我现在能不能使用这个东西,

能行吗,报错了吧,咱们看一看,报了什么错,
上面,这一堆,你需要看吗,

根本不需要看,只看哪里,

只看这个,reason,吧,
reason,是不是出错的原因吧,
只看这个reason,看一下这里告诉你,
attempt to delete row 13 from section 0 which only contains 13 rows before the update,
尝试去删除,索引为13,的这一行,从第0组,里面,尝试去删除,13这么一行,但是呢,这个组里面,仅仅只有13行,在你更新的时候,仅仅包含13行,
什么意思呢,
一开始,这个section里面,有13个商品吧,13个商品,最大的索引,是多少,12,最大的索引是12,
它这句话的意思是,通过这个局部刷新,
其实它局部刷新,是啥意思啊,
比如说,你想把这个“将太无二”,改成“将太无三”,

当你点它,是不是弹出一个对话框,把“将太无二”,改成“将太无三”,了,
改成“将太无三”,当点“确定”的时候,如果你是通过局部刷新,来更新,这个时候,其实它的意思是,
先把“将太无二”,原来的旧行给它删掉,然后创建一个新的cell,把这个新的cell,给显示过来,
它这个刷新,是这个意思,
先把旧的删掉,再把新的给你刷新过来,
而那个reloadData,
全部刷新的意思是,直接把这个tableView,就不要了,
全都重新创建一个,
而局部刷新,它是先把旧的行,删掉,然后再把新的单元格再给你创建一个啊,
是这么来做的,
如果你是把“将太无二”,改成“将太无三”,
因为这行本来就存在,
你也不用把它删掉,
总行数没有变,
只是把它的内容改了,这时候,没有问题,把旧行删掉,来一个新行,
但是,当你加载更多,的时候,它这个时候,是不是要加一条新数据啊,
加一条新数据,本来是13行,加一个新数据以后,应该变成14行了吧,
变成14行,但是,只有当我们这个reloadRow,或者局部刷新,调用完毕以后,这个界面上,才会有那个第14行啊,
当局部刷新方法调用完毕以后,界面上才会有第14行,
但是,它在刷新方法里面,它就会尝试,先把第14行删掉,它会找索引为13的行,把它删掉,然后它会再给它加一个新行,
但是,这个方法,还没有调用完毕,整个UITableView里面,现在是不是只有13行,最大的索引是12,
那么也就不存在索引13,
当你刷新完毕,才存在索引为13的这行,
所以说,一开始,在调这个方法的时候,UITableView上,只有13行,
最大的索引是12,这个时候根本就没有这个索引为13的行,所以,你这个时候,尝试去删索引为13的行,它就告诉你,你尝试去删除第0组里面,第13、索引为13的行,但是,第0组里面,只有13行啊,
在你更新之前,只有13行啊,
最大的索引,是12,
所以报错了,
局部刷新,当我们的行发生变化、UITableView的总行数发生变化的时候,还能调这个局部刷新吗,不能了吧,

好,所以我们这个时候,就不能调这个局部刷新了,

还是可以选择上面这种方式,
[self.tableView reloadData];

当然,我们也有一些其他办法,比如说,我们可以调insert,方法,
给它在界面上,手动插入一行,
这样也行,今天我们就先介绍这么一个,
就是刷新,这个效果,
就是用这个reloadData,全部刷新,
四、还有一个小问题,点加载更多,注意看,

点完以后,

看到了吗,它是在这里,

希望点完以后,它立刻变成这个效果,

点完以后,它再向上滚一下吧,
把这个再滚出来,
不然是不是还得手动滚一下,
我希望点完以后,你把这个“驴肉火烧”加载出来,同时是不是再向上滚一下,滚到这个地方,能显示出来“加载更多”吧,
2.要实现这个效果,就需要我们手动,我们说,TableView,默认,我们是不是只能通过手指,或者通过鼠标键,这样来滚动啊,
但是,我希望通过程序来让它滚动,
让我们最后这行数据,滚上来,

2.在ViewController.m文件的footerViewUpdateData:,方法中,
当数据刷新完毕以后,我们这里再来一步,
//5.把UITableView中的最后一行的数据滚动到最上面,

怎么来滚呢,我们希望让谁滚,是不是UITableView来滚,
所以,我们就找到这个tableView,对象,

[self.tableView
滚动,大家猜猜是哪个单词,scroll,吧,

scroll,什么,scrollTo,scrollTo哪里,

scrollToRowAtIndexPath,
这个方法,
滚动到指定的行, 滚动到指定的行,
我们是不是希望,滚动到最后一行吧,

所以说,我们这里是不是要拿到最后一行,这个行对象,

NSIndexPath *idxPath = [NSIndexPath indexPathForRow:self.goods.count - 1 inSection : 0];
这是最后一行,拿到最后一行,

把最后一行,传到这个地方,

[self.tableView scrollToRowAtIndexPath:idxPath atScrollPosition:
(UITableViewScrollPosition) animated:(BOOL)];
它把最后一行,滚到什么位置呢,是不是需要动画呢,YES,吧,

来,看一下,我们滚动,让把最后一行,滚到什么位置呢,
这里有一个Position,
Position,就是位置,
那么,这里把这个最后一行,滚到什么位置呢,
看,UITableViewScrollPosition,
一看就是个枚举,没有星号,对吧,

哪个,
1)Bottom,
2)Middle,
3)None,
4)Top,
是不是把它滚到最上面啊,

把当前这行,滚到最上面,
运行一下看看,对不对,

对吧,
首先,是不是实现我说的那个效果了,把这个显示出来了,
但是,这个时候,大家可能会问一个问题,
你不是说把它滚到最上面吗,Top,
你看它到最上面了吗,
哪里是最上面,

这才是最上面呢吧,
它其实是可以到最上面的,但是因为你后面还有数据吗,
你后面没数据了,它有必要滚到最上面吗,
没有必要,
所以说,我们这儿,我如果选的是这个“俏巴蜀”的话,

它就会把“俏巴蜀”滚到这里,

因为它是可以滚到这儿的,
因为,最后一行,滚到最上面,就是滚到这里了,
你尝试继续往上滚,

但是发现,后面没有数据吧,
没有必要滚到这个地方,
那大家来,给大家看一下那个Middle,

来,看一下Middle,是什么效果,

滚到中间,是不是也行吧,

但是滚到Bottom,行吗,

是不是滚到最下面,

文章讲述了在iOS开发中如何实现UITableView的“加载更多”功能,包括使用自定义cell、通过xib加载视图、设置代理来传递数据更新请求以及使用GCD进行延迟操作以模拟加载效果。同时,讨论了局部刷新和整体刷新的区别,以及如何在数据加载后自动滚动到新增内容。

2144

被折叠的 条评论
为什么被折叠?



