UIKit 的鬼設計

弄清楚程式語言只是開發軟體的第一步,在程式語言之後,你需要習慣這個平台上已經有了那些既有的慣例,而既有的慣例除了寫作風格之外,還要適應原廠的 Bug 及其莫名其妙的設計。像 UIKit 這套 Framework 有些地方實在是設計得莫名其妙,而且是讓你在工作的時候忍不住想要罵人的那種。

比方說,不管你寫什麼程式,你都有極大的機率會用到問人家好不好,要不要繼續的對話框,像是 Javascript 的 alert() 或 .Net Framework 裡頭的 Message.Show(),這種對話框幾乎都是在你呼叫了 function 之後,就把到底按了哪個按鈕回傳給你。UIKit 偏偏就不是這樣設計,在呼叫了 UIAlertView 之後,偏偏要用 delegate method (或是所謂的 callback)的方式回傳。

雖然在 Cocoa 或是 UIKit 裡頭到處都在用 delegate,但是用這種方式處理 UIAlertView 實在很討厭-最常見的問題就是,假如說我在同一個 controller class 裡頭用上了一堆的 UIAlertView,而每個 UIAlertView 在處理回傳的按鍵的時候,要做不同的工作,而 delegate method 卻又全都是同一個名字,那要怎樣判斷到底是那個 UIAlertView 回傳的結果?

直覺可以想到一種方法是,生出很多不同的物件,分別作為不同的 UIAlertView 的 delegate-人活的好好的幹什麼生出一堆只負責處理對話框 delegate method 的 class?另外一種方法則是,在 alertView:clickedButtonAtIndex: 等 method 中,因為會把 UIAlertView 物件傳進來,那麼,只要比對指標,就可以知道是那個對話框了-如此一來,當你每次建立 UIAlertView 物件,都必須要把這個物件找個地方放起來,如果你用到了十個對話框,就代表你需要建立十個 class 為 UIAlertView 的成員變數,如此一來,絕對可以有效的讓你的程式變得無比醜陋。

有天實在覺得很生氣,寫了一個負責管理 UIAlertView 的 Singleton controller class(以下稱為 ZBAlertViewController)。簡單講,就是所有需要呼叫 UIAlertView 的時候,都由這個 class 生出 UIAlertView,然後將 UIAlertView 的 delegate 只到它身上。而外部在呼叫 ZBAlertViewController 的時候,同時指定 ZBAlertViewController 的 delegate 與一個 selector,在 ZBAlertViewController 收到 alertView:clickedButtonAtIndex: 之後,再對自己的 delegate 來 perform 指定的 selector。收工。

雖然這種麻煩是有辦法解決,但是 UIKit 本身幹嘛製造這種麻煩?

而你想要在你的 iPhone/iPod Touch 程式裡頭播放一段影片,自然是會用上 MPMoviePlayerController。用這個工具播放影片是很方便,只要指定影片的 URL,生出物件,馬上就可以開始播放,然後你還可以指定播放器的背景顏色、影片內容的縮放模式,播放介面也可以選擇三種模式-沒有任何操作介面、完整介面或只能調整音量。另外,MPMoviePlayerController 也會在影片改變縮放模式、已經載入足夠資料可以播放以及播放完畢的時候發出通知。

Picture 1

然後你會突然發現一件事情-

在完整播放模式下,播放器上面有跳到前一部與下一部影片的按鈕。當使用者按到這兩個按鈕的時候,完全沒有送給你任何事件,而且在文件上面,沒有告訴你怎樣可以隱藏這兩顆按鈕。整個 MPMoviePlayerController 都被包裝起來,叫你不要深究裡頭,但是 MPMoviePlayerController 卻沒有幫你處理這兩顆按鈕。太棒了。

如果你試著監聽所有的 Notification,是可以在使用者按下這兩顆按鈕的時候,收到一些由不在文件上的 class(如 MPAVController)所發出的一些不在文件上的通知(如 MPAVControllerPlaybackStateChangedNotification),但是呢,在按下前一部或下一部按鈕的時候,會收到的通知,可說完全一模一樣,這條路行不通。

從網路上的討論來看,目前國外幾家軟體-如vSNAX,要實作這兩顆按鈕的功能,方法都是,在 MPMoviePlayerController 的播放畫面出現的時候,軟體會多出一個用來播放影片的 UIWindow 物件,於是你可以從這個 UIWindow 物件裡頭的 subview 裡頭的 subview 裡頭的 subview 裡頭的 subview…慢慢去找這兩顆按鈕到底在哪裡,找到之後,你就可以或是把這兩顆按鈕換掉,或是再做兩顆按鈕蓋在上面,或是要這兩顆按鈕去做別的事情-總之,你的程式裡頭就是要一段極其醜陋的部份,用來找到這個 UIWindow 物件裡頭的 subview 裡頭的 subview 裡頭的 subview 裡頭的你想要的 subview。

而你也知道,用這個方法解決問題的後遺症就是-假如哪天蘋果又做了一個版本的 OS 升級,把 MPMoviePlayerController 裡頭每個 view 的順序又改過一次,你的程式就爆炸了。而以現在蘋果的上架程序,你大概在改過之後的一兩週之後,才能將改好的版本送到用戶手上。

話說,你找到了播放畫面是那個 view 之後,也是可以做些有趣的事情。例如-

有加分

嗯。有加分。

Be Sociable, Share!

17 thoughts on “UIKit 的鬼設計

  1. 你好,
    我目前想寫個streaming audio player
    mpmovieplayercontroller可以播放http的audio file
    不過我又想客製ui介面,讓他不要變成fullscreen
    不知道有什麼方法可以操作movieplayer的view?
    謝謝

  2. 問題是AVAudioPlayer只能播放local file
    要播放http的streaming audio,似乎只能用AudioFileStream來寫
    如果可以客製MoviePlayer的UI介面就好了 XD
    請問要怎麼抓取MoviePlayer中的view阿?

  3. 用async的方式,才能確保螢幕上該動的東西會繼續動,這是為什麼大多數的ui framework都用這樣的方式來處理,尤其是compile的成品越接近機械碼越是如此,因為沒有東西可以在中間緩衝。就連Flash actionscript 3這樣有好幾層(layer)緩衝空間的東西,也都是如此要求人撰寫了。

    至於解決這種問題,多半都是用:「直覺可以想到一種方法是,生出很多不同的物件,分別作為不同的 UIAlertView 的 delegate」的方法。這個方法在design pattern的討論中可以進一步演化出一些作法,如mediator或是strategy。

    當程式越寫越大,妳會發現這些delegate的組合關係多半就是那幾個形狀,於是就可以獨立成一個一個的class,用factory pattern解決生成的問題。

    一點感想

  4. Hi, 我最近也碰到这个头痛的问题,我的解决方案是这样的:UIView 有一个可以作为唯一标识的 tag property, 我给不同的 UIAlertView 物件赋以不同的的 tag, 这样在 delegate method 里就可以知道是哪个物件呼叫了。

  5. 請問MPMoviePlayerController 或 AVAudioPlayer 可以做到live streaming嗎
    查了一下資料目前只有wowza 跟 real player系列的media server 才支援

  6. 您好,我想請教一下。
    您在文中提到" UIWindow 物件裡頭的 subview 裡頭的 subview 裡頭的 subview 裡頭的 subview",這個部分要怎麼找到前一個/後一個事件的按鈕呢。
    請問可以提示我一下嗎?謝謝~

  7. 程式碼是寫給客戶的,所以程式碼就沒辦法提供了。

    [UIApplication sharedApplicaiton] 有一個叫做 windows 的屬性,裡頭是目前這個應用程式用到了哪些 UiWindow 物件,當播放器開始播放的時候,就會多出一個 window,而UiWindow 是繼承自 UIView,UiView 上面疊了什麼,都在subViews 屬性裡頭,反正就是一路 enumerate NSArray 裡頭的東西,把這個 view 的 class 倒出來看看(用NSLog(@”class:%@”, NSStringFromClass([aView class]))..之類的),看看某個 view 的 class 是不是像是某種 button 那樣的東西。

    需要注意的是,在 2.0 OS 上,當你產生這個 player 物件的時候,就會開始建立這個 UIWindow以及相關的 view 物件,但是,在 3.0 上面,則是會確定等到開始載入影片之後,才會開始產生播放器 UI,所以,當你要做上面說的找 subview 那件事情的時候,要有一定的 delay,但是影片會花多久才會載入,又是不確定的。

    所以呢,你就可能需要監控一下 [UIApplication sharedApplicaiton].windows 裡頭的變化,可以試試看用 KVO 做這件事情。

  8. 謝謝你的回應~
    "在 3.0 上面,則是會確定等到開始載入影片之後,才會開始產生播放器 UI。"
    我的解決方式是去註冊 UIWindowDidBecomeKeyNotification,
    等撥放器變成 key window 後在去找裡面的物件,如此就可以找到Previous/Next event 的按鈕了。

    目前還遇到一個問題,我想取代掉原本Previous/Next event 的 delegate。
    我的做法是先試著找出那個事件,然後移除預設的關閉視窗的事件。
    我列舉出該按鈕 UIControl 所有註冊的事件,發現找不到任何註冊的事件。
    因此無法移除原本預設的 delegate (關閉視窗),請問你之前有遇過這個問題嗎?

    • 我覺得比對 title 是個頂爛的主意。

      title 不是一個 unique 的東西,同樣的alert 提示,應該要做相同的事情,但是很有可能會拿到不一樣的 title-title 可能因為在不同語系下會有不同的翻譯,例頭的訊息也可能跟著情境不同而有所改變,不太可能拿這種東西來做判斷。

  9. 樓上提到了Title,而我想到和使用的方法則是幫AlertView設定tag
    雖然多此一舉,但或許只要規格統一,還是可以有效率的辨識AlertView

Leave a Reply