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 之後,也是可以做些有趣的事情。例如-

有加分

嗯。有加分。

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

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.