隔離導(dǎo)航控制器帶來的坑(iOS)

題外話:最近一直很閑野瘦,項(xiàng)目上基本沒有啥大的需求描沟。對于程序員來說,如果沒有需求其實(shí)是一件很難受的事情鞭光,之前好多次在項(xiàng)目中沒事找事吏廉,該優(yōu)化的優(yōu)化,該整理的整理惰许∠玻可能好多程序員都遇到過與我類似的情況。但是程序員真的需要通過刷項(xiàng)目去提高自己嗎啡省?程序員的功力體現(xiàn)在處理項(xiàng)目的細(xì)節(jié)上娜睛,有可能為了改進(jìn)一點(diǎn)點(diǎn)的體驗(yàn)就要付出很大的代價(jià),就要接觸更多的知識點(diǎn)卦睹。做的項(xiàng)目多,但是不精那么只能代表自己在比較淺顯的領(lǐng)域比較熟練而已方库。以上觀點(diǎn)是我在最近思考得來的结序,并不一定正確,甚至明天一早我就會推翻這種想法纵潦,請大家謹(jǐn)慎參考??徐鹤。

一、什么是隔離導(dǎo)航控制器邀层?

這個(gè)名詞純粹是我自己瞎編的返敬,我不知道有沒有這種說法,至少我沒有看到寥院。我所說的隔離導(dǎo)航控制器是指:在導(dǎo)航欄樣式不同的頁面采用不同的導(dǎo)航控制器【⒃現(xiàn)在同一個(gè)導(dǎo)航欄發(fā)生變化主要體現(xiàn)在:隱藏導(dǎo)航欄頁面跳轉(zhuǎn)到非隱藏導(dǎo)航欄的頁面、A顏色導(dǎo)航欄的頁面跳轉(zhuǎn)到B顏色導(dǎo)航欄的頁面秸谢、可跟隨滑動做動畫的頁面跳轉(zhuǎn)到固定導(dǎo)航欄的頁面凛澎。隔離導(dǎo)航控制器就是:當(dāng)頁面跳轉(zhuǎn)到導(dǎo)航欄不同的頁面時(shí)不再使用同一個(gè)導(dǎo)航控制器,而是彈出一個(gè)新的導(dǎo)航控制器估蹄,這個(gè)導(dǎo)航控制器的導(dǎo)航欄固定不變塑煎,如果發(fā)生變化那么再彈出一個(gè)新的導(dǎo)航控制器。

二臭蚁、為什么要隔離導(dǎo)航控制器最铁?

很簡單讯赏,因?yàn)槲乙呀?jīng)受夠了頁面的來回跳轉(zhuǎn)導(dǎo)致狀態(tài)欄儲存、變化冷尉、恢復(fù)這樣的過程待逞。尤其在側(cè)滑或者全屏滑動返回時(shí),導(dǎo)航欄的變化會有不好的體驗(yàn)网严。所以在很久以前我就在醞釀识樱,如果present出來一個(gè)導(dǎo)航控制器就好了(隔離導(dǎo)航控制器并不是一個(gè)很好地解決辦法,但是可以試一試震束,畢竟我很閑)怜庸。因此我在項(xiàng)目中試了一下,結(jié)果遇到了一些問題垢村,自己挖的坑割疾,跪著也要填滿了。

三嘉栓、實(shí)現(xiàn)右進(jìn)右出的present

ViewController 的present樣式一共有4種:

typedef NS_ENUM(NSInteger, UIModalTransitionStyle) {
    UIModalTransitionStyleCoverVertical = 0,//默認(rèn)
    UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED,//翻轉(zhuǎn)
    UIModalTransitionStyleCrossDissolve,//透明度漸變
    UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED,//翻書
};
[vc setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];    //present 前加上這句就行
[self presentViewController:vc animated:YES completion:nil];

沒有我們需要的效果宏榕,因此我們需要利用iOS 7 以后的轉(zhuǎn)場動畫來實(shí)現(xiàn)。說到轉(zhuǎn)場動畫不怕大家笑話侵佃,我已經(jīng)看了好多遍了麻昼,但是依然記不住那些名字。每次要用都要去大神的博客里再學(xué)習(xí)一邊馋辈。我的代碼也是使用大神的代碼抚芦,因?yàn)橛X得沒有必要再寫一遍。只不過我做了略微的修改迈螟。

// NormalDismissAnimation
// 4. Do animate now
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    toVC.view.transform = CGAffineTransformMakeTranslation(-100, 0);//主要是為了上下兩個(gè)控制器有聯(lián)動的效果
    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        fromVC.view.frame = finalFrame;
        toVC.view.transform = CGAffineTransformIdentity;
 
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
 
 
//BouncePresentAnimation
  // 4. Do animate now
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        toVC.view.frame = finalFrame;
        fromVC.view.transform = CGAffineTransformMakeTranslation(-100, 0);//主要是為了有上下兩個(gè)控制器聯(lián)動的效果
    } completion:^(BOOL finished) {
        fromVC.view.transform = CGAffineTransformIdentity;
        [transitionContext completeTransition:YES];
    }];
 
 
//SwipeUpInteractiveTransition
- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer {
    CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
       switch (gestureRecognizer.state) {
        case UIGestureRecognizerStateBegan:
            // 1. Mark the interacting flag. Used when supplying it in delegate.
            self.interacting = YES;
            [self.presentingVC dismissViewControllerAnimated:YES completion:nil];
            break;
        case UIGestureRecognizerStateChanged: {
            // 2. Calculate the percentage of guesture
            CGSize screenSize = [UIScreen mainScreen].bounds.size;
            CGFloat fraction = translation.x / screenSize.width;
            //Limit it between 0 and 1
            fraction = fminf(fmaxf(fraction, 0.0), 1.0);
            self.shouldComplete = (fraction > 0.4);
 
            [self updateInteractiveTransition:fraction];
            break;
        }
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled: {
            // 3. Gesture over. Check if the transition should happen or not
            self.interacting = NO;
            if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
                [self cancelInteractiveTransition];
            } else {
                [self finishInteractiveTransition];
            }
            break;
        }
        default:
            break;
    }
}

以上部分很簡單而且并不是我們所關(guān)心的部分(雖然名字有點(diǎn)難記憶叉抡,但是不難理解)。完成以上代碼答毫,就可以實(shí)現(xiàn)滑動返回+右進(jìn)右出的present樣式了褥民。

//還有一點(diǎn)需要注意的是,需要禁止全屏手勢在導(dǎo)航控制器棧里有多個(gè)元素時(shí)響應(yīng)
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
    if ([self.presentingVC isKindOfClass:[BaseNavigationController class]]) {
        BaseNavigationController *nav = (BaseNavigationController*)self.presentingVC;
        if (nav.viewControllers.count >=2) {
            return NO;
        }
    }
    return YES;
}

四洗搂、遇到的問題

我們(或者說大多數(shù)APP)都有這樣的需求消返,當(dāng)收到推送時(shí)需要從底部彈出一個(gè)ViewController。 但是因?yàn)槲覀兪莗resent 出來了一些頁面蚕脏,當(dāng)收到推送的時(shí)候我們并不能一下子知道從哪個(gè)ViewController 上present 出來一個(gè)需要用戶響應(yīng)的控制器侦副。比方說 HomeViewController ---(右進(jìn)右出present)---->ReportMessageViewController后,從代碼的角度我們不能一下子拿到展現(xiàn)在用戶面前的控制器(因?yàn)槟悴恢朗欠褚呀?jīng)present了驼鞭,也不知道誰調(diào)用的present)秦驯。因此我遇到了第一個(gè)問題(很多的項(xiàng)目可以通過tab、nav 來確定挣棕,而且present 出控制器的場景比較固定):

1译隘、到底誰是當(dāng)前正在響應(yīng)的控制器亲桥?

要想解決這個(gè)問題我們首先要知道響應(yīng)者鏈條。即當(dāng)我們點(diǎn)擊了屏幕上的一個(gè)按鈕固耘,事件是怎么傳遞的题篷。UIView 和 UIViewController 都是繼承自UIResponder的,他們都可以成為響應(yīng)者厅目。因此我們只要遍歷屏幕上最上方的那些View(葉子節(jié)點(diǎn))番枚,縱向循環(huán)找到他們的nextResponder,直至找到為UIViewController類的響應(yīng)者损敷。

// 獲取某個(gè)view 的葉子 View(一般為Window)
/*
 
*/
+(NSMutableArray *)getTopSubViewsWithParentView:(UIView *)rootView{
    NSMutableArray *stack = [NSMutableArray array];
    NSMutableArray *leafNodes = [NSMutableArray array];//存放葉子節(jié)點(diǎn)
    if (rootView.subviews.count == 0) {
        return nil;
    }
    [stack addObjectsFromArray:rootView.subviews];//把根視圖的所有第一層子視圖入棧
    while (stack.count != 0) {
        UIView *subView = [stack lastObject];//取出頂部元素并判斷是否為葉子節(jié)點(diǎn)
        [stack removeLastObject];
        if (subView.subviews.count != 0) {//不是葉子節(jié)點(diǎn)的話將其子視圖繼續(xù)入棧
            [stack addObjectsFromArray:subView.subviews];
        }else{
            [leafNodes addObject:subView];//如果是葉子節(jié)點(diǎn)則將其入棧(葉子節(jié)點(diǎn)的棧)
        }
    }
    return leafNodes;
}
 
//獲取某個(gè)視圖在哪個(gè)控制器上
+(UIViewController*)getViewControllerWithView:(UIView*)view{
     
    UIResponder *res = view;
    while (res) {
        if (res.nextResponder) {
            res = res.nextResponder;
        }
        if ([res isKindOfClass:[UIViewController class]]) {
             
            UIViewController *vc = (UIViewController*)res;
            return vc;
        }
    }
    return nil;
}
 
//這段代碼的意思是葫笼,如果我能判斷的更精確就精確些。比如某個(gè)導(dǎo)航控制器拗馒,你說他在響應(yīng)也行路星,他的top元素在響應(yīng)也行,顯然我想精確到top元素
+(UIViewController*)getCurrentVC{
    UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
     
    NSMutableArray *array = [self getTopSubViewsWithParentView:keyWindow];
    UINavigationController *nav = nil;
    UITabBarController *tab = nil;
    for (UIView *subView in array) {
         
        UIViewController *vc = [self getViewControllerWithView:subView];
        if (!([vc isKindOfClass:[UINavigationController class]] || [vc isKindOfClass:[UITabBarController class]])) {
            return vc;
        }
        if ([vc isKindOfClass:[UINavigationController class]]) {
            nav = (UINavigationController*)vc;
        }
        if ([vc isKindOfClass:[UITabBarController class]]) {
            tab = (UITabBarController *)vc;
        }
    }
    if (nav) {
        return nav;
    }
    if (tab) {
        return tab;
    }
    return nil;
}

有了這些代碼诱桂,問題一就解決了洋丐。

2、全屏滑動遇到了可以左右滑動的ScrollView怎么辦?

首先說明一下,這個(gè)問題我解決的并不完美项乒,因?yàn)槿コ薭ounces 效果。因?yàn)榛瑒覵crollView時(shí)九榔,ScrollView 的pan 手勢會優(yōu)先響應(yīng),并阻止其他手勢響應(yīng)涡相。首先我們先看一下手勢代理,看看都有哪些方法:

 1 @protocol UIGestureRecognizerDelegate <NSObject>
 2 @optional
 3 // called when a gesture recognizer attempts to transition out of UIGestureRecognizerStatePossible. returning NO causes it to transition to UIGestureRecognizerStateFailed
 4 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
 5 
 6 // called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other
 7 // return YES to allow both to recognize simultaneously. the default implementation returns NO (by default no two gestures can be recognized simultaneously)
 8 //
 9 // note: returning YES is guaranteed to allow simultaneous recognition. returning NO is not guaranteed to prevent simultaneous recognition, as the other gesture's delegate may return YES
10 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;//手勢1剩蟀、手勢2 是否可以共存催蝗,即兩者都響應(yīng),收到事件繼續(xù)傳遞下去
11 
12 // called once per attempt to recognize, so failure requirements can be determined lazily and may be set up between recognizers across view hierarchies
13 // return YES to set up a dynamic failure requirement between gestureRecognizer and otherGestureRecognizer
14 //
15 // note: returning YES is guaranteed to set up the failure requirement. returning NO does not guarantee that there will not be a failure requirement as the other gesture's counterpart delegate or subclass methods may return YES
16 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);//在other 響應(yīng)的情況下育特,自己是否不響應(yīng)
17 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
18 
19 // called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch. return NO to prevent the gesture recognizer from seeing this touch
20 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
21 
22 // called before pressesBegan:withEvent: is called on the gesture recognizer for a new press. return NO to prevent the gesture recognizer from seeing this press
23 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;

通過打印scrollView 的pan手勢我們會發(fā)現(xiàn) pan手勢的代理是scrollView 丙号,我嘗試過改變pan 的delegate 但是發(fā)現(xiàn)會崩潰,apple 是不允許改變這個(gè)delegate 的缰冤。所以我想到了寫一個(gè)UIScrollView的子類犬缨。

//
//  PanScrollView.m
//  Property
//
//  Created by 高雅馨on 16/7/25.
//  Copyright ? 2016年  高雅??. All rights reserved.
//
 
#import "PanScrollView.h"
 
@implementation PanScrollView
 
-(instancetype)init{
    if (self = [super init]) {
        self.bounces = NO;
    }
    return self;
}
 
/*
 是否將相應(yīng)傳遞給other
  
  
 當(dāng)偏移量X值為0的時(shí)候全屏手勢和pan 手勢同時(shí)響應(yīng)。全屏手勢向右滑動時(shí) 棉浸,由于bounces效果已經(jīng)被去掉了怀薛,所以偏移量不變,ViewController 只有dismiss 效果迷郑。當(dāng)向左滑動時(shí)全屏手勢沒有任何效果枝恋,只要scroll效果创倔。
 */
 
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
     
    if (gestureRecognizer == self.panGestureRecognizer  && otherGestureRecognizer == self.otherGes){
        if (self.contentOffset.x==0) {
            return YES;
        }
    }
    return NO;
}
 
@end

至此,大部分可見的問題解決了焚碌,可能并不完美畦攘,也不適合,但是嘗試也是一種美好的回憶十电,希望能對大家有幫助知押。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鹃骂,隨后出現(xiàn)的幾起案子台盯,更是在濱河造成了極大的恐慌,老刑警劉巖偎漫,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爷恳,死亡現(xiàn)場離奇詭異,居然都是意外死亡象踊,警方通過查閱死者的電腦和手機(jī)温亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杯矩,“玉大人栈虚,你說我怎么就攤上這事∈仿。” “怎么了魂务?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長泌射。 經(jīng)常有香客問我粘姜,道長,這世上最難降的妖魔是什么熔酷? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任孤紧,我火速辦了婚禮,結(jié)果婚禮上拒秘,老公的妹妹穿的比我還像新娘号显。我一直安慰自己,他們只是感情好躺酒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布押蚤。 她就那樣靜靜地躺著,像睡著了一般羹应。 火紅的嫁衣襯著肌膚如雪揽碘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音钾菊,去河邊找鬼帅矗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛煞烫,可吹牛的內(nèi)容都是我干的浑此。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼滞详,長吁一口氣:“原來是場噩夢啊……” “哼凛俱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起料饥,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蒲犬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后岸啡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體原叮,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年巡蘸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奋隶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悦荒,死狀恐怖唯欣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搬味,我是刑警寧澤境氢,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站碰纬,受9級特大地震影響萍聊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悦析,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一脐区、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧她按,春花似錦、人聲如沸炕柔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匕累。三九已至陵刹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欢嘿,已是汗流浹背衰琐。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工也糊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人羡宙。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓狸剃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狗热。 傳聞我的和親對象是個(gè)殘疾皇子钞馁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件匿刮、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評論 4 62
  • 軌道運(yùn)營交通線路維修,設(shè)備檢查環(huán)節(jié)是維修質(zhì)量的一個(gè)非常重要的流程僧凰。檢查分手工檢查和儀器檢查。 手工檢查熟丸,主要是檢查...
    155守時(shí)待命閱讀 337評論 0 0
  • 我愿意 在夜晚 與月亮眉目傳情 卻不太想與人說話 我愿意 在清晨 聽小鳥在枝頭歌唱 卻不太想與人說話 我愿意 在林...
    吳森迪閱讀 169評論 0 0