樣式需求展示-京東導(dǎo)航條
需求說明:
1.導(dǎo)航條隱藏功能
2.界面向上滾動(dòng)的時(shí)候,導(dǎo)航條隱藏
3.界面向下滾動(dòng)的時(shí)候,導(dǎo)航條顯示
層次結(jié)構(gòu)分析:
核心思路:導(dǎo)航條必須隱藏,顯示的頂部的類似于導(dǎo)航條的控件,是我們自定義的UIView,才能實(shí)現(xiàn)效果!
層級(jí)結(jié)構(gòu)分析:
思路①:使用圖中 - 原諒色的View - 導(dǎo)航條View - 替代navigationBar
==>問題出現(xiàn) - 這種整個(gè)導(dǎo)航條View隱藏的時(shí)候,頂部時(shí)間View也隱藏了!不符合要求
思路②.頂部分成三個(gè)模塊部分相互獨(dú)立:
- 頂部時(shí)間工具條自己一個(gè)View
- 導(dǎo)航條自己一個(gè)View
- 按鈕VIew自己一個(gè)獨(dú)立的View
- 內(nèi)容tableView自己獨(dú)立一個(gè)View就不用說了
隱藏導(dǎo)航條 && 界面移動(dòng)的原理解釋
①.界面上移的時(shí)候 - 導(dǎo)航View隱藏:
- 原理色的導(dǎo)航條View隱藏
- 按鈕View上移
- tableView上移
- tabView高度 ++ (加上導(dǎo)航條View的高度)
①.界面上移的時(shí)候 - 隱藏的導(dǎo)航View顯示:
- 原理色的導(dǎo)航條View顯示
- 按鈕View下移
- tableView下移
- tabView高度 -- (減去剛剛++的導(dǎo)航條View高度)
問題難點(diǎn):如果知道 下方的tableView滾動(dòng)方法(怎么知道是向上滾還是向下滾動(dòng))
思路1:tableVIew本質(zhì)是scrollview,判斷scrollview的滾動(dòng)方向,通過contentOffset
思路2:
使用KVO,監(jiān)聽tableVIew的滾動(dòng),監(jiān)聽兩個(gè)值 - NSKeyValueObservingOptionOld && NSKeyValueObservingOptionNew,通過新舊值的 .y值,判斷滾動(dòng)方向。
這里使用的就是思路2的方法:
a.隱藏系統(tǒng)默認(rèn)的導(dǎo)航條View,然后自定義和導(dǎo)航條一模一樣的UIView上去
[self.navigationController setNavigationBarHidden:YES];
b.tableView添加KVO監(jiān)聽滑動(dòng)方向
[_tableView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
c.通過觀察者監(jiān)聽值的變化
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
CGFloat oldOffsetY = [change[NSKeyValueChangeOldKey] CGPointValue].y;
CGFloat newOffsetY = [change[NSKeyValueChangeNewKey] CGPointValue].y;
CGFloat deltaY = newOffsetY - oldOffsetY;
if(deltaY >= 0) {
//向上滾動(dòng)}
else{
//向下滾動(dòng)
}
c.在向上滾動(dòng)的時(shí)候 - 設(shè)置導(dǎo)航條隱藏 + View上移
if(deltaY >= 0) { //向上滾動(dòng)
[UIView animateWithDuration:0.25 animations:^{
//隱藏導(dǎo)航條
_navigationView.hidden = YES;
//按鈕View上移導(dǎo)航條View的高度 - Y值改變
CGRect tempShowViewFrame = _showView.frame;
tempShowViewFrame.origin.y -= navigationBarH;
_showView.frame = tempShowViewFrame;
//tableView上移導(dǎo)航條View的高度 - Y值改變 && 高度 增加導(dǎo)航條的view的高度
CGRect tempTableViewFrame = _tableView.frame;
tempTableViewFrame.origin.y -= navigationBarH;
tempTableViewFrame.size.height += navigationBarH;
_tableView.frame = tempTableViewFrame;
}];
}
d.在界面向下滾動(dòng)的時(shí)候 - 設(shè)置導(dǎo)航條View顯示 + View下移
else {
//向下滾動(dòng) - show
[UIView animateWithDuration:0.25 animations:^{
_navigationView.hidden = NO;
CGRect tempShowViewFrame = _showView.frame;
tempShowViewFrame.origin.y += navigationBarH;
_showView.frame = tempShowViewFrame;
CGRect tempTableViewFrame = _tableView.frame;
tempTableViewFrame.origin.y += navigationBarH;
tempTableViewFrame.size.height -= navigationBarH;
_tableView.frame = tempTableViewFrame;
}];
}
核心代碼如上,其實(shí)本質(zhì)就是通過KVO觀察tableView的滾動(dòng)方向,然后設(shè)置對(duì)應(yīng)的View顯示 && 位置變化
Demo展示:
進(jìn)階篇:跨控制器改變View的顯示
demo中的View都在同一個(gè)界面,可以直接在observeValueForKeyPath
方法中,直接通過 UIView的成員變量改View的狀態(tài),但是如果跨控制器呢?
如圖:此界面的頂部三個(gè)按鈕,分別對(duì)應(yīng)響應(yīng)的三個(gè)控制器[‘全部’,‘測(cè)試1’,‘測(cè)試2’],控制器結(jié)構(gòu)分析:
- 導(dǎo)航View && 按鈕View && 按鈕在外層的控制器上
- 每個(gè)按鈕對(duì)應(yīng)各自的單獨(dú)一個(gè)控制器,顯示內(nèi)容
- 按鈕對(duì)應(yīng)的內(nèi)部VC的view 添加到外層的VC的View上,才能得到顯示
- 最終顯示的tableView,其實(shí)是按鈕VC里面的- [btn->內(nèi)部VC的view addsubView:tableVIew]
- 所以簡(jiǎn)單的說,就是tableVIew和其他的View不在同一個(gè)控制器里
思路:跨控制器傳值
(這里打算使用 - 代理模式)
==>思路:
- 因?yàn)槭峭ㄟ^tableView滾動(dòng)方向,判斷View的顯示隱藏 && 位置,所以判斷在tableView對(duì)應(yīng)的控制器上;
- 外部控制器根據(jù)tableView控制器的滾動(dòng)方向而做出相應(yīng)的變化,所以外部控制器要成為代理對(duì)象,協(xié)議聲明寫在tableView控制器上
- 外部控制要顯示還是隱藏,tableView控制器要告訴他,所以代理方法要傳值
//tableView控制器的.h文件
@protocol HZOrderNavigationViewDelegate <NSObject>
- (void)changeNavigationViewShow:(BOOL)hidden;
@end
tableView控制器,還是一樣通過KVO監(jiān)聽tableView的contentOffSet,然后在observeValueForKeyPath
方法中,由于無法直接控制外部VC的界面屬性,所以通過代理傳值,告訴外部控制器,界面要發(fā)生的變化
//tableView控制器的.m文件
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
CGFloat oldOffsetY = [change[NSKeyValueChangeOldKey] CGPointValue].y;
CGFloat newOffsetY = [change[NSKeyValueChangeNewKey] CGPointValue].y;
CGFloat deltaY = newOffsetY - oldOffsetY;
if(deltaY >= 0) { //向上滾動(dòng)
//變成執(zhí)行代理方法,通知外部VC的界面發(fā)生改變
if ([_delegate respondsToSelector:@selector(changeNavigationViewShow:)]) {
[_delegate changeNavigationViewShow:YES];
}
}
//外部控制器接收到tableView控制器傳來的值之后,做出的界面改變
-(void)changeNavigationViewShow:(BOOL)hidden{
if (hidden) {
//導(dǎo)航條隱藏
[UIView animateWithDuration:0.25 animations:^{
_navigationView.hidden = YES;
//按鈕View位置改變
CGRect tempShowViewFrame = _topBtnsView.frame;
tempShowViewFrame.origin.y -= navigationBarH;
_topBtnsView.frame = tempShowViewFrame;
//修改scrollview - 按鈕VC對(duì)應(yīng)的view添加到這個(gè)scrollview上的
CGRect tempScrollViewFrame = _contentScrollView.frame;
tempScrollViewFrame.origin.y -= navigationBarH;
tempScrollViewFrame.size.height += navigationBarH;
_contentScrollView.frame = tempScrollViewFrame;
}];
}
原理其實(shí)和同一個(gè)控制器里面改變UIView的屬性一樣,只是這里跨了控制器,無法拿到屬性,所以是通過代理傳值告訴外部的那個(gè)控制器做出相應(yīng)的改變而已,本質(zhì)核心不變。
幾個(gè)小細(xì)節(jié):
-
細(xì)節(jié)1:
所以界面變化的步驟應(yīng)該如下:
- 導(dǎo)航View隱藏
- 按鈕View上移
- 外層scrollview上移,高度 ++
- tableView要和scrollview一樣高度++,但是Y不用移動(dòng)!!
-
細(xì)節(jié)2:判斷界面的顯示or隱藏,如果導(dǎo)航條View已經(jīng)隱藏了,再怎么上拉,也不能再調(diào)用使界面再次隱藏的辦法,同時(shí)不能再讓下方兩個(gè)View的Y值 --;
如圖所示,要添加判斷,如果導(dǎo)航條已經(jīng)隱藏了,按鈕View 和 tableView就不要再一直往上跑了,最多就上移一個(gè)View的位置就夠了,所以要添加判斷;
如果是在同一個(gè)控制器中,可以添加 - _navigationView.hidden 屬性判斷
if(deltaY >= 0) { //向上滾動(dòng)
NSLog(@"向上滾動(dòng) - hidden");
if (_navigationView.hidden == YES) {
return;
}
但是這里是跨控制器的,_navigationView屬性是在外部控制器上,而不是在tableView的控制器上,所以拿不到!
-
根據(jù)y值判斷?
6.png
問題出現(xiàn):tableView是添加到外部控制的內(nèi)容ScrollView上的,Y值永遠(yuǎn)是0!所以不能用y值判斷!
解決辦法:Y值雖然是0無法進(jìn)行判斷,但是可以通過tableView的高度進(jìn)行判斷!
if(deltaY >= 0) { //向上滾動(dòng)
if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH )) {
//說明tableView上方?jīng)]有導(dǎo)航條View - 導(dǎo)航條已經(jīng)隱藏了,此時(shí)上滾就不用再改變位置了
return;
}
同理:向下滾動(dòng)的時(shí)候也要添加判斷
else{
if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH + navigationBarH)) return;
}
-
細(xì)節(jié)3:判斷的值最好不要用0,不然稍微一碰tableView,界面就發(fā)生變化
if(deltaY >= 50) { //向上滾動(dòng)
}
else if (deltaY <= -50){
}
進(jìn)階 - 下拉刷新導(dǎo)致的Bug
bug說明:如圖,只要一使用下拉刷新,就自動(dòng)調(diào)用 - 導(dǎo)航條View隱藏 并且 外部控制器上移效果
下拉刷新的時(shí)候,本質(zhì)上也是拖動(dòng)tableView,一樣會(huì)進(jìn)tableView的監(jiān)聽方法!
項(xiàng)目需求:下拉刷新的時(shí)候,不要和gif顯示的一樣,導(dǎo)致導(dǎo)航條隱藏并使得界面上移!
解決方案 :
如圖,我們發(fā)現(xiàn),下拉的時(shí)候,跑出來的 mj_headerView - 高度54,就等于,直接讓tableView的contentOffset.y = 54了!
解決如下:所以要進(jìn)代理方法,判斷y的位移量,一定要大于54!大于54才讓進(jìn)入代理方法,例如取個(gè)80,否則每次下拉刷新都會(huì)進(jìn)入代理方法改變界面
if(deltaY >= 80) { //向上滾動(dòng)
if (_tableView.frame.size.height == ScreenH - (topTimeToolH + CarInsOrderTopViewH )) {
return;
}
最終效果演示: