天天看點

使用appearance proxy定制控件的預設外觀(詳解)

控件的外觀,受到tint color,background image, background color等屬性的共同影響,通常要修改某個控件對象的外觀,就去調用上面屬性的相關setter方法(或者其他可以修改它們的方法)就可以了。但是,如果希望整個app中的控件都保持一緻的風格,比如所有button的風格(指的是大小,背景圖,形狀等)都一樣,那麼一個一個去重複設定每個button的風格,就顯得太麻煩了。如果可以給Button類設定一個預設外觀,就友善多了。appearance proxy就可以實作。

對于導航欄,tabBarController等等管理多個視圖控制器的控件,要對它們的全局外觀進行修改,通常比較便捷的方法也是定制預設外觀,也就是使用appearance proxy。下面就來詳細介紹appearance proxy。

一、首先,要了解一個概念appearance proxy外觀代理:

外觀代理,它是一個管理可視化類(比如控件)的預設外觀的對象。我們可以通過這個類的外觀代理,來修改這個類的預設外觀(與外觀有關的屬性的預設值),或者這個類在某種場合下的預設外觀。

二、需要遵守兩個協定

一個類要能夠使用外觀代理,需要遵守兩個協定,一個是UIAppearance,一個是UIAppearanceContainer協定。

1. UIAppearance協定

該協定定義了4個方法,用于擷取類的外觀代理,分别對應4種外觀代理。

2. UIAppearanceContainer協定

該協定定義了一個常量UI_APPEARANCE_SELECTOR,用來辨別外觀代理可以調用的方法。既然外觀代理是用來管理類的預設外觀的,那麼能夠被它調用的方法,或者說它需要調用的方法,往往就是類的某個外觀特征的setter和getter方法。也就是說,為一個方法設定UI_APPEARANCE_SELECTOR标簽,就表示這個方法可以為外觀代理所用。當然,方法的格式是有規定的,基本格式和一般屬性的setter和getter方法格式一樣,在這個基礎上可以增加更多的參數。參數的類型隻能是NSInteger或NSUInteger,否則會報錯。

三、外觀代理的工作時間

官網上簡潔的表述:

所有通過外觀代理對預設外觀進行的修改,隻會在view layout time 時生效。在此之後,仍可以通過對象的屬性和方法來覆寫此預設外觀。

我的詳細解釋:

我們可以随時讓外觀代理修改預設外觀,準确的了解是讓外觀代理記錄好我們對預設外觀的定制要求。但是外觀代理隻會在view layout time時工作一次,也就是讓預設外觀生效。在此之後,調用外觀代理來修改預設外觀,是不會即時生效的,換言之,必須要等到下次view layout time的時候才會讓這個新定制的預設外觀生效。什麼時候是view layout time?從運作的效果來看,我暫且了解為加載window(或從其他window傳回到本window)的時候,如果有更準确的了解,望賜教。

預設外觀對類的所有的對象起作用(所謂預設,就是這個意思)。如果要在預設外觀的基礎上對某個對象的外觀進行修改,就和平常一樣,調用這個對象有關外觀設定的方法即可。

四、外觀代理的擷取及使用

下面詳細了解UIAppearance協定中定義的4種擷取外觀代理的方法,及其對應的4種外觀代理:

1.全局外觀代理

功能:代理中的Big Boss,它可以管理控件在任何情況下的預設外觀。

擷取代理的方法:+ ( instancetype )appearance

比如:要擷取UINavigationBar的全局外觀代理:

UINavigation *appearanceProxy=[UINavigation appearance];

2.隻負責指定場合的外觀的外觀代理

功能:管理控件被包含在指定的類(及其子類)中時的預設外觀;管理控件被包含在指定的視圖層級關系中時的預設外觀。

擷取代理的方法:

+ ( instancetype )appearanceWhenContainedIn:(Class<UIAppearanceContainer>)ContainerClass, …     
           

解釋:

這個方法的參數指定了某個類或者多個類。參數個數不定,最後一個參數必須是nil。

例子:

比如,擷取 作用于“放在UIToolbar類中的UIBarButtonItem對象”的外觀代理,這樣寫:

注意:

此時除了放在UIToolbar類中的UIBarButtonItem對象會受這個外觀代理管理以外,放在UIToolbar的子類中的UIBarButtonItem對象也會受這個代理管理。

如果要指定層級關系,則傳入一個層級關系的對象。

上面說的方法從iOS 5.0開始啟用,到iOS 9.0丢棄,iOS 9.0及以後用下面的方法替代:

+(instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes
           

比如,設定導航欄被放在UISplitViewController或者UITabBarController裡面時的預設BarTintColor:

[[UINavigationBar appearanceWhenContainedInInstancesOfClasses:  @[[UISplitViewController class], [UITabBarController class]]] setBarTintColor:myColor];
           

3.隻負責控件在某種系統環境下的外觀的外觀代理

功能:管理類在某種系統環境下(比如橫屏、豎屏等)的預設外觀。系統環境由UITraitCollection對象來指定。(檢視UITraitCollection Class Reference)

擷取代理的方法:

+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait
           

4.隻負責控件在某種系統環境、某種場合下的外觀的外觀代理

功能:管理控件在某種系統環境、某種場合下的預設外觀

擷取代理的方法:

+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait  whenContainedIn:(Class<UIAppearanceContainer>)ContainerClass
           

以上方法從5.0開始啟用,在iOS 9.0被丢棄,iOS 9.0及以後用下面這個方法替代:

+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes