輕松學習之二——iOS利用Runtime自定義控制器POP手勢動畫

前言

蘋果在IOS7以后給導航控制器增加了一個Pop的手勢,只要手指在屏幕邊緣滑動,當前的控制器的視圖就會跟隨你的手指移動,當用戶松手后,系統會判斷手指拖動出來的大小來決定是否要執行控制器的Pop操作。

nav_pop_origin.gif

這個操作的想法非常好,但是系統給我們規定的范圍必須是屏幕左側邊緣才可以觸發,這樣實際使用過程中對于有些產品會產生不便,于是有些app就采取整個屏幕都響應這個手勢并且pop動畫還是用系統原生的,這樣操作起來確實方便好多。

nav_pop_custom.gif

開始大家一定會有疑問,給控制器的View加個手勢然后拖動控制器的View時改變它的frame不就可以了嗎?沒錯,加手勢這個想法是正確的。但是,由我們自己來改變控制器視圖的位置是比較麻煩的,細心的朋友一定發現了,我們自定義pop手勢上面的導航欄也是在隨著你的手勢拖拽而變動的,所以這樣做還需要負責導航欄的動畫,而且有一個重點問題,如果單獨拖動view,這個view下面會是黑黑的一片,因為控制器的push和pop層級是由系統管理的。

nav_pop_failed.gif

所以走這條路雖然可以,但實現起來會比較艱辛。那么,如何實現這個效果呢?今天就給大家提供兩套實現方案。


[1]

方案一:自定義UIViewControllerInteractiveTransitioning對象,實現導航控制器代理方法。

這個是蘋果官方推薦的做法,在WWDC 2013 218 - Custom Transitions Using View Controllers中有說明。

這套方案雖然實現比較麻煩,但是動畫相對靈活,你可以實現這樣的效果,

nav_pop_cube.gif

也可以有這種效果。

nav_pop_flip.gif

其實這個拖動過程屬于導航控制器的動畫,所以我們需要重寫UINavigationController的兩個代理方法,navigationController:animationControllerForOperation:fromViewController:toViewController:(名字很長下面就稱為方法1)和
navigationController:interactionControllerForAnimationController:(方法2)。
解釋一下他們的作用,方法1是蘋果提供給我們用來重寫控制器之間轉場動畫的(pop或者push)。方法2你可以這樣理解,蘋果讓我們返回一個交互的對象,用來實時管理控制器之間轉場動畫的完成度,通過它我們可以讓控制器的轉場動畫與用戶交互(注意一點,如果方法1返回是nil,方法2是不會調用的,也就是說,只有我們自定義的動畫才可以與控制器交互)。

下面我們來看一下實現過程。為了便于大家理解,我會盡量在Demo中的注釋寫的最清晰明了。
同時,我們先用最簡單的代碼實現,在這篇文章的最后我會對本例中的Demo提供一個相對合理的寫法。

首先在方法1中,我們返回一個遵守了UIViewControllerAnimatedTransitioning協議的對象,它就是自定義的動畫對象,我們給它起名PopAnimation,在這個類中實現兩個方法來自定義轉場動畫。

屏幕快照 2015-03-28 下午6.49.05.png

再來看方法2,我們需要返回一個遵守了UIViewControllerInteractiveTransitioning協議的對象(提示一下,這兩個協議容易混淆,要注意區分,一個是負責動畫,一個是負責交互過程),蘋果已經有一個類專門處理這個功能,它叫UIPercentDrivenInteractiveTransition,當然你也可以自定義一個這樣的類。我們可以這樣理解它的作用:前面在方法1中返回的動畫,會在執行的過程中被系統分解以用于用戶交互,這個交互過程的動畫完成度就由它來調控。下面我們來看一下如何使用它。(為了讓控制器視圖拖動,我們給控制器的視圖加了一個拖動手勢,在拖動方法里我們對這個對象進行操作)

屏幕快照 2015-03-29 下午12.33.59.png

最后在視圖控制器里重寫導航欄的兩個方法。


屏幕快照 2015-03-29 下午12.37.51.png

有兩點不要忘記:

  1. 設置導航控制器的代理為當前控制器。
  2. 給控制器加手勢。

OK,這樣我們就完成了這個過程。

nav_pop_own.gif

[2]

方案二:Runtime+KVC

要了解這樣的做法,需要有Runtime的一些知識,會涉及到私有變量、私有方法的獲取,但是這樣做比較簡單也比較有趣,如果你感興趣就繼續看下去吧。關于Runtime的知識,今后我會分享到博客里,朋友們敬請期待。

為了方便大家閱讀下面的代碼,我們需要先了解系統的這個手勢。

前面我們了解到,這個手勢屬于UINavigationController,我們就跳到它的頭文件里看看能不能找到線索。這個思路是正確的,確實有一個手勢叫做interactivePopGestureRecognizer。屬性為readonly,就是說我們不能給他換成自定義的手勢,但是可以設置enable=NO。ok,既然找到了它,就打印一下看看它到底是一個什么手勢。

屏幕快照 2015-03-26 下午5.17.35.png

通過log,我們看到他屬于UIScreenEdgePanGestureRecognizer這個類(之前我是沒有用到過),它繼承自UIPanGestureRecognizer,出現在IOS7以后,是專門處理在屏幕邊緣觸發的手勢類型,并且只有一個屬性叫edges,用來設置它的觸發邊緣(上、下、左、右、全部)。看到這里一些朋友會想,直接改它的edges為全部可不可以?經過試驗了解到,改這個屬性是沒用的,它只能用來觸發邊緣,設為全部的意思是四個方向的邊緣會觸發,而且用來做控制器POP手勢的只有左邊緣。

我們繼續看它的log。控制臺除了打印了它的類,還打印了它的觸發target:_UINavigationInteractiveTransition(這是一個私有類,看來是專門用來做導航控制器交互動畫的),和action:handleNavigationTransition(這是它的一個私有方法),我們要做的就是新建一個UIPanGestureRecognizer,讓它的觸發和系統的這個手勢相同,這就需要利用runtime獲取系統手勢的target和action。

那么如何獲取這個target呢?一開始我用kvc想直接獲取這個手勢的target,程序崩潰了,原來它根本沒有這樣一個屬性。所以我能想到的是,先利用runtime遍歷它的所有成員變量,看看系統是怎么存儲這個屬性的,


屏幕快照 2015-03-29 下午3.25.02.png

通過log我們可以看到,UIGestureRecognizer有一個叫_targets的屬性,它的類型為NSMutableArray。


屏幕快照 2015-03-29 下午3.25.09.png

它是用數組來存儲每一個target-action,所以可以動態的增加手勢觸發對象。那么又是什么存儲每一個target-action呢?為了了解這個我們拿到這個屬性的名字"_targets"通過kvc獲取它,接著打印出來。
屏幕快照 2015-03-29 下午3.33.54.png

屏幕快照 2015-03-29 下午3.34.01.png

可以看到,由于系統重寫了它的description方法,所以我們沒辦法通過打印獲取這個對象是什么類型。既然不能打印,那么我們就用斷點調試,來看它的真實類型,

屏幕快照 2015-03-29 下午3.37.32.png

我們看到,原來每一個target-action是用UIGestureRecognizerTarget這樣一個類來存儲的,它也是一個私有類。
蘋果把許多的類做私有化也是有原因所在,其實在平時我們拿到這個類也是沒有用的,他們的目的之一是避免對開發者公開無用的類,影響了封裝性。所以在類的設計上,還是要向蘋果學習。

下面直接看代碼。

我們在控制器的ViewDidLoad加上這段代碼,并且它只需要執行一次。


屏幕快照 2015-03-29 下午4.07.48.png

優化

這個demo我會提供給大家,下面簡單說下程序的優化思路。

  • 優化點一:對于方案一,其實不應該把導航控制器的代理方法以及手勢處理的方法交給視圖控制器,因為這段代碼不是屬于某一個視圖控制器,而是全局的導航控制器,所以我們應該參考蘋果的設計思想:新建一個專門管理交互過程的對象,這個類我們叫做NavigationInteractiveTransition。

  • 優化點二:再來看之前的ViewDidLoad中只執行一次的代碼,其實寫在這里也不夠妥當,同樣的,這段代碼也不屬于某一個Controller,優化方案是新建一個導航控制器,在這個導航控制器的viewDidLoad中寫上這些代碼,這樣也并不需要dispatch once。

  • 優化點三:由于我們自定義的手勢是加在一個私有view上,這個view是一個全局的,所以當這個控制器為根控制器時,我們的手勢還是在起作用,這就相當于對根控制器做了pop操作,這會出現一個錯誤nested pop animation can result in corrupted navigation bar。導致這個錯誤的原因還有一個,如果我們pop的動畫正在執行,再去觸發一次手勢,會導致導航控制器和導航條的動畫混亂。為了避免問題出現我們需要成為手勢的代理,判斷當前控制器是否為根控制器并且pop或者push動畫是否在執行(這個變量是私有的,需要用kvc來獲取)。


    屏幕快照 2015-03-30 下午5.06.24.png

經過最后的優化,視圖控制器可以什么都不寫,想使用這個效果,只要使用我們自定義的導航控制器就可以了,這樣的好處是手勢動畫與控制器完全解耦,并且不用給每一個控制器都addGesture。


給大家推薦一個倉庫https://github.com/nst/iOS-Runtime-Headers,這個倉庫可以調取蘋果的所有私有方法頭文件,相當強大。

最后放上這個demo的地址:https://github.com/zys456465111/CustomPopAnimation(使用時,切換工程的scheme就能切換不同方案。對于方案二,只需要導航控制器的類就可以了。)

感謝大家,輕松學習系列還會繼續下去,我會盡量寫出更多通俗易懂的文章,讓開發變得輕松起來,我的微博:http://weibo.com/JazysYu


  1. 方案一。 ?

  2. 方案二。 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • 轉載自:http://www.cocoachina.com/ios/20150401/11459.html 前言 ...
    梅西121閱讀 573評論 1 2
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,151評論 4 61
  • 看電影其實是一種生活的體驗,是自己無法在現實中實現的體驗,或者是曾經有過的回憶,只是大多數人都希望去體驗繁華,而不...
    八音先生閱讀 258評論 0 0
  • “這一板條拍下去,我覺得他再也爬不起來了”但是他沒有,在靜靜的血腥味里,他又站了起來。 一 小四初三開學后不久,隔...
    笑眼朋朋閱讀 1,011評論 2 3
  • 三奇自從在一次交通事故中廢了一只腿,就知道自己的后半生只能坐在輪椅上度過了。這次交通事故不僅在生活上給他帶來了翻天...
    小閑云閱讀 815評論 6 9