前言
蘋果在IOS7以后給導(dǎo)航控制器增加了一個Pop的手勢抛猫,只要手指在屏幕邊緣滑動孩灯,當(dāng)前的控制器的視圖就會跟隨你的手指移動峰档,當(dāng)用戶松手后,系統(tǒng)會判斷手指拖動出來的大小來決定是否要執(zhí)行控制器的Pop操作掀亩。
這個操作的想法非常好槽棍,但是系統(tǒng)給我們規(guī)定的范圍必須是屏幕左側(cè)邊緣才可以觸發(fā)抬驴,這樣實際使用過程中對于有些產(chǎn)品會產(chǎn)生不便布持,于是有些app就采取整個屏幕都響應(yīng)這個手勢并且pop動畫還是用系統(tǒng)原生的,這樣操作起來確實方便好多按傅。
開始大家一定會有疑問唯绍,給控制器的View加個手勢然后拖動控制器的View時改變它的frame不就可以了嗎灌侣?沒錯,加手勢這個想法是正確的牛柒。但是痊乾,由我們自己來改變控制器視圖的位置是比較麻煩的哪审,細(xì)心的朋友一定發(fā)現(xiàn)了,我們自定義pop手勢上面的導(dǎo)航欄也是在隨著你的手勢拖拽而變動的滴须,所以這樣做還需要負(fù)責(zé)導(dǎo)航欄的動畫扔水,而且有一個重點問題,如果單獨拖動view主届,這個view下面會是黑黑的一片待德,因為控制器的push和pop層級是由系統(tǒng)管理的将宪。
所以走這條路雖然可以,但實現(xiàn)起來會比較艱辛簸喂。那么,如何實現(xiàn)這個效果呢扼倘?今天就給大家提供兩套實現(xiàn)方案再菊。
方案一:自定義UIViewControllerInteractiveTransitioning對象,實現(xiàn)導(dǎo)航控制器代理方法秉剑。
這個是蘋果官方推薦的做法稠诲,在WWDC 2013 218 - Custom Transitions Using View Controllers中有說明臀叙。
這套方案雖然實現(xiàn)比較麻煩,但是動畫相對靈活渊涝,你可以實現(xiàn)這樣的效果,
也可以有這種效果。
其實這個拖動過程屬于導(dǎo)航控制器的動畫岁疼,所以我們需要重寫UINavigationController的兩個代理方法蚯姆,navigationController:animationControllerForOperation:fromViewController:toViewController:(名字很長下面就稱為方法1)和
navigationController:interactionControllerForAnimationController:(方法2)龄恋。
解釋一下他們的作用,方法1是蘋果提供給我們用來重寫控制器之間轉(zhuǎn)場動畫的(pop或者push)它碎。方法2你可以這樣理解显押,蘋果讓我們返回一個交互的對象乘碑,用來實時管理控制器之間轉(zhuǎn)場動畫的完成度,通過它我們可以讓控制器的轉(zhuǎn)場動畫與用戶交互(注意一點套腹,如果方法1返回是nil电禀,方法2是不會調(diào)用的笤休,也就是說,只有我們自定義的動畫才可以與控制器交互)政基。
下面我們來看一下實現(xiàn)過程腋么。為了便于大家理解亥揖,我會盡量在Demo中的注釋寫的最清晰明了。
同時摧扇,我們先用最簡單的代碼實現(xiàn)扛稽,在這篇文章的最后我會對本例中的Demo提供一個相對合理的寫法。
首先在方法1中用含,我們返回一個遵守了UIViewControllerAnimatedTransitioning協(xié)議的對象啄骇,它就是自定義的動畫對象瘟斜,我們給它起名PopAnimation螺句,在這個類中實現(xiàn)兩個方法來自定義轉(zhuǎn)場動畫。
再來看方法2芽唇,我們需要返回一個遵守了UIViewControllerInteractiveTransitioning協(xié)議的對象(提示一下披摄,這兩個協(xié)議容易混淆勇凭,要注意區(qū)分虾标,一個是負(fù)責(zé)動畫灌砖,一個是負(fù)責(zé)交互過程)基显,蘋果已經(jīng)有一個類專門處理這個功能,它叫UIPercentDrivenInteractiveTransition库继,當(dāng)然你也可以自定義一個這樣的類宪萄。我們可以這樣理解它的作用:前面在方法1中返回的動畫,會在執(zhí)行的過程中被系統(tǒng)分解以用于用戶交互静汤,這個交互過程的動畫完成度就由它來調(diào)控居凶。下面我們來看一下如何使用它侠碧。(為了讓控制器視圖拖動舆床,我們給控制器的視圖加了一個拖動手勢,在拖動方法里我們對這個對象進行操作)
最后在視圖控制器里重寫導(dǎo)航欄的兩個方法谷暮。
有兩點不要忘記:
- 設(shè)置導(dǎo)航控制器的代理為當(dāng)前控制器。
- 給控制器加手勢颊埃。
OK蝶俱,這樣我們就完成了這個過程。
方案二:Runtime+KVC
要了解這樣的做法罗标,需要有Runtime的一些知識闯割,會涉及到私有變量竿拆、私有方法的獲取宙拉,但是這樣做比較簡單也比較有趣谢澈,如果你感興趣就繼續(xù)看下去吧澳化。關(guān)于Runtime的知識,今后我會分享到博客里井濒,朋友們敬請期待列林。
為了方便大家閱讀下面的代碼希痴,我們需要先了解系統(tǒng)的這個手勢。
前面我們了解到虏缸,這個手勢屬于UINavigationController刽辙,我們就跳到它的頭文件里看看能不能找到線索甲献。這個思路是正確的晃洒,確實有一個手勢叫做interactivePopGestureRecognizer。屬性為readonly氧骤,就是說我們不能給他換成自定義的手勢筹陵,但是可以設(shè)置enable=NO际歼。ok鹅心,既然找到了它纺荧,就打印一下看看它到底是一個什么手勢。
通過log议泵,我們看到他屬于UIScreenEdgePanGestureRecognizer這個類(之前我是沒有用到過)先口,它繼承自UIPanGestureRecognizer瞳收,出現(xiàn)在IOS7以后螟深,是專門處理在屏幕邊緣觸發(fā)的手勢類型,并且只有一個屬性叫edges凡蜻,用來設(shè)置它的觸發(fā)邊緣(上划栓、下舰讹、左月匣、右、全部)素标⊥吩猓看到這里一些朋友會想癣诱,直接改它的edges為全部可不可以撕予?經(jīng)過試驗了解到,改這個屬性是沒用的实抡,它只能用來觸發(fā)邊緣,設(shè)為全部的意思是四個方向的邊緣會觸發(fā)踩寇,而且用來做控制器POP手勢的只有左邊緣六水。
我們繼續(xù)看它的log缩擂。控制臺除了打印了它的類懈费,還打印了它的觸發(fā)target:_UINavigationInteractiveTransition(這是一個私有類憎乙,看來是專門用來做導(dǎo)航控制器交互動畫的)叉趣,和action:handleNavigationTransition(這是它的一個私有方法)疗杉,我們要做的就是新建一個UIPanGestureRecognizer烟具,讓它的觸發(fā)和系統(tǒng)的這個手勢相同,這就需要利用runtime獲取系統(tǒng)手勢的target和action嗡午。
那么如何獲取這個target呢荔睹?一開始我用kvc想直接獲取這個手勢的target言蛇,程序崩潰了腊尚,原來它根本沒有這樣一個屬性。所以我能想到的是丢胚,先利用runtime遍歷它的所有成員變量携龟,看看系統(tǒng)是怎么存儲這個屬性的峡蟋,
通過log我們可以看到蕊蝗,UIGestureRecognizer有一個叫_targets的屬性蓬戚,它的類型為NSMutableArray宾抓。
它是用數(shù)組來存儲每一個target-action幢泼,所以可以動態(tài)的增加手勢觸發(fā)對象讲衫。那么又是什么存儲每一個target-action呢涉兽?為了了解這個我們拿到這個屬性的名字"_targets"通過kvc獲取它花椭,接著打印出來。
可以看到,由于系統(tǒng)重寫了它的description方法雕蔽,所以我們沒辦法通過打印獲取這個對象是什么類型批狐。既然不能打印,那么我們就用斷點調(diào)試承冰,來看它的真實類型困乒,
我們看到娜搂,原來每一個target-action是用UIGestureRecognizerTarget這樣一個類來存儲的吱抚,它也是一個私有類秘豹。
蘋果把許多的類做私有化也是有原因所在既绕,其實在平時我們拿到這個類也是沒有用的岸更,他們的目的之一是避免對開發(fā)者公開無用的類,影響了封裝性谭企。所以在類的設(shè)計上债查,還是要向蘋果學(xué)習(xí)盹廷。
下面直接看代碼。
我們在控制器的ViewDidLoad加上這段代碼久橙,并且它只需要執(zhí)行一次俄占。
優(yōu)化
這個demo我會提供給大家,下面簡單說下程序的優(yōu)化思路淆衷。
優(yōu)化點一:對于方案一缸榄,其實不應(yīng)該把導(dǎo)航控制器的代理方法以及手勢處理的方法交給視圖控制器,因為這段代碼不是屬于某一個視圖控制器祝拯,而是全局的導(dǎo)航控制器甚带,所以我們應(yīng)該參考蘋果的設(shè)計思想:新建一個專門管理交互過程的對象,這個類我們叫做NavigationInteractiveTransition。
優(yōu)化點二:再來看之前的ViewDidLoad中只執(zhí)行一次的代碼晴氨,其實寫在這里也不夠妥當(dāng),同樣的碉输,這段代碼也不屬于某一個Controller瑞筐,優(yōu)化方案是新建一個導(dǎo)航控制器,在這個導(dǎo)航控制器的viewDidLoad中寫上這些代碼腊瑟,這樣也并不需要dispatch once。
-
優(yōu)化點三:由于我們自定義的手勢是加在一個私有view上块蚌,這個view是一個全局的闰非,所以當(dāng)這個控制器為根控制器時,我們的手勢還是在起作用峭范,這就相當(dāng)于對根控制器做了pop操作财松,這會出現(xiàn)一個錯誤nested pop animation can result in corrupted navigation bar。導(dǎo)致這個錯誤的原因還有一個纱控,如果我們pop的動畫正在執(zhí)行辆毡,再去觸發(fā)一次手勢,會導(dǎo)致導(dǎo)航控制器和導(dǎo)航條的動畫混亂甜害。為了避免問題出現(xiàn)我們需要成為手勢的代理舶掖,判斷當(dāng)前控制器是否為根控制器并且pop或者push動畫是否在執(zhí)行(這個變量是私有的,需要用kvc來獲榷辍)眨攘。
經(jīng)過最后的優(yōu)化,視圖控制器可以什么都不寫嚣州,想使用這個效果鲫售,只要使用我們自定義的導(dǎo)航控制器就可以了,這樣的好處是手勢動畫與控制器完全解耦该肴,并且不用給每一個控制器都addGesture情竹。
給大家推薦一個倉庫https://github.com/nst/iOS-Runtime-Headers,這個倉庫可以調(diào)取蘋果的所有私有方法頭文件匀哄,相當(dāng)強大秦效。
最后放上這個demo的地址:https://github.com/zys456465111/CustomPopAnimation(使用時,切換工程的scheme就能切換不同方案涎嚼。對于方案二棉安,只需要導(dǎo)航控制器的類就可以了。)
感謝大家铸抑,輕松學(xué)習(xí)系列還會繼續(xù)下去贡耽,我會盡量寫出更多通俗易懂的文章,讓開發(fā)變得輕松起來,我的微博:http://weibo.com/JazysYu 蒲赂。