建立自註冊的Swift UI 控制項
對於自訂控制項來說,在不破壞原有的訊息機制的前提下,如何響應事件通知?在本文中,我將示範一個通知代理類,通過一個簡單的例子,我們用該類向已有的iOS UI控制項中增加了自己的新功能:為Text View控制項增加placeholder文本。
問題:缺失的Placeholder
Placeholder文本是用於在某些控制項中提示使用者輸入指定類型的資料的良好方式。UIKit的UITextField控制項的placeholder屬性就是用來幹這個的。例如,中,在Twitter的選項設定中,User Name欄位就使用了placeholder文本。通過這個placeholder文本,使用者可以知道應當在這裡輸入一個包含了@符號的Twitter使用者名稱。而在Password欄位的placeholder文本則標明該欄位是必填的。
圖 1 - 設定程式中的Placeholder 文本
儘管Text Filed有placeholder屬性,但它的兄弟控制項Text View卻沒有這個屬性。
問題的解決
我準備這樣解決這個問題:
建立一個UITextView的擴充,添加一個placeholder的計算屬性 在擴充中,當TextView中沒有輸入任何文字的情況下,顯示一個Label,如果輸入有文字,則隱藏這個Label。
就像我在前面的文章中提到的,我更喜歡使用擴充,而不是子類。因為擴充允許我在App中使用“盒子之外”的UIKit控制項。我需要做的僅僅是在項目中加入某個類的擴充檔案,然後這個類會自動獲得新的特性。
方式1:UITextViewDelegate
當使用者在TextView中輸入文本時,iOS有幾種通知App的方式。其中一種就是通過UITextViewDelegate協議。在這個擴充中有一個對TextView的引用儲存在它自己的delegate屬性中。當使用者輸入或刪除字元時,TextView的協議方法會被調用。我們可以在協議方法中加入隱藏和顯示placeholder標籤的代碼。
這個解決方案的缺點是,Text View太過於依賴delegate屬性,因為在一個UIControl中你只能註冊一個委派物件。如果你需要向TextView中加入更多的委派物件,你就會比較麻煩。
方式2:NSNotificationCenter
NSNotificationCenter通過UITextViewTextDidChangeNotification通知來告訴你使用者在TextView中輸入或刪除了某些字元。這是一種更好的選擇,因為它不再依賴於delegate屬性。缺點是需要向通知中樞進行登出。一般,我們在對象的deinit方法中向NSNotificationCenter登出該對象。但是在Swift中,我們無法在擴充中使用deinit方法。那我們怎樣才能突破這個限制呢?
建立一個通知代理
我們通過建立一個輕量級的代理對象來突破這個限制,這個代理對象代替TextView來向通知中樞進行註冊。
我已經在我的項目中建立了一個UITextView擴充以及一個代理類,你可以從這裡下載。
雙擊.xcodeproj檔案,用Xcode開啟這個項目。在項目導航視窗中選中mmTextViewExtensions.swift檔案,你可以找到這個通知代理類(如你所見,其中也包含了上一篇文章中的mmDyamicTypeExtensions類)。
下面是位於檔案頭部的通知代理類:
你可以看到,它是UIView子類。這樣我們就可以將它當成subview添加到TextView中。addObserverForName:usingBLock:方法通知中樞的方法擁有一樣的簽名。這個方法中只有一行代碼,就是將TextView註冊到通知中樞並將參數傳遞過去,包括TextView的閉包,這樣當通知發生時該閉包被執行。通知中樞會返回一個NSObjectProtocol對象,稍後我們會用來進行通知的登出操作。
deinit方法也只有一行代碼,僅僅是向通知中樞進行對象的登出。
通知代理類是可重用的。你可以用於任何類型的通知以及你想接收通知的任何類型的對象。
然後是UITextView的擴充,在擴充中有一個placeholderLabel屬性,如你所想,它引用了一個placeholder標籤對象。
此外還有一個placeholder的字串屬性。在後面你將看到,所有的奇蹟將在運行時發生,當placeholder屬性被設定,這個計算屬性的代碼就會執行。
現在開啟Main.storyboard檔案。2所示,故事板中只有一個Scene,在這個Scene的頂部,有一個Text View。
圖 2 - 主 scene 中包含了一個 text view
當你選擇Text View,然後開啟屬性面板,你將看見一個Placeholder屬性,它是擴充中增加的屬性,這個屬性的預設值是“Enter your text here!”。在設計時不會顯示這段文本(要在設計時可見,我們需要花費大量的工作,因此這不是本文的主題),但在運行時它會顯示。
The Notification Proxy at Run Time運行時的通知代理
圖3中的UML順序圖表顯示了所有對象之間發生的訊息傳遞的順序。
圖 3 - 運行時對象間訊息傳遞的順序
每一步的解釋如下:
當運行時,UITextView的placeholder文本被改變時,該擴充的placeholder計算屬性中的代碼被觸發。 擴充的addPlaceholderLabel方法被調用。 addPlaceholderLabel方法建立一個placeholder標籤並設定標籤文本。 placeholder標籤被添加進UITextView。 一個通知代理對象被建立。 擴充調用通知代理的addObserverForName:withBlock:方法,並指定對UITextViewTextDidChangeNotification通知感興趣。此外閉包代碼會在通知發生時執行。 通知代理對象將UITextView註冊到通知中樞,同時將通知時間傳遞給TextView,當通知發生時,UITextView的閉包代碼將被執行。 UITextView以subview的方式添加通知代理對象。 當UITextViewTextDidChangeNotification通知發生,通知中樞調用TextView擴充的閉包。 UITextView擴充將placeholder標籤的hidden屬性設定為true和false,取決於TextView中的文本是否有內容。
11.當TextView在運行時解構時,placeholder標籤和通知代理對象都被解構。 當通知代理對象解構時,將TextView對象從通知中樞中登出。Let’s Take it for a Test Run!運行測試
In Xcode’s Scheme control, select one of the simulators such as iPhone 6.
在Xcode的Scheme下拉式清單中,選擇一個模擬器,例如iPhone6。
Click Xcode’s Run button.
點擊Xcode的Run按鈕。
When the app appears in the Simulator, you can see the placeholder text (Figure 4).
當App在模擬器中運行後,我們可以看到placeholder文本(圖4)。
圖 4 - 運行時的 placeholder 文本
4.Now type some code in the text view. As soon as you begin typing, the placeholder label disappears (Figure 5).現在在TextView中輸入任一字元。此時,placeholder將消失(圖5)。
圖 5 -placeholder 文字消失了
如果將輸入的字元刪除乾淨,placeholder文字將重新出現。
結束語
要解決編程問題有許多方法。最好的方法是先看一下擺在你目前的選擇,然後逐個分析其中的利弊。我希望你能看到本文中順序圖表的好處。它能讓你將對象之間在運行時的互動畫出來。我在編寫代碼時也使用了這個方法。你會發現它們能協助你找出潛在的問題,更能幫你找出編程問題中的新的、更高效的解決方案。