UIAppearcance 使用指北

歡迎登錄個人我的網站: www.sketchk.xyzswift

UIAppearance 使用指北

UIAppearance 的簡介

在 UIAppearance 出現以前,開發者若是想統一修改 app 內某一個控件的 UI 樣式時,只能經過去修改每一個控件的實例屬性,對於只有幾個實例的 UI 控件來講,這樣的修改還能夠接受,但若是整個 app 中有幾十個,甚至上百個實例的時候,這樣的修改就顯得至關笨拙了,固然你也能夠考慮使用一些黑魔法來實現,不過這或多或少都給開發者帶來了很多麻煩。bash

除了上面提到的場景外,還有一種場景就是在 app 內提供多種多樣的主題來知足用戶的需求,例如手淘在 app 內提供的主題切換功能。app

上面這兩個實際開發中的應用場景都映射出這樣一個問題:如何在整個 app 中高效,統一,即時的定製 UI 控件樣式。框架

在 iOS 5.0 以後,Apple 爲開發者提供了名爲 UIAppearance 和 UIAppearanceContainer 的類,它們就是蘋果爲開發者提供的一種官方解決方案。post

爲了在現有的 UIKit 框架裏面去作這個事,蘋果的思路是: 讓 UIAppearance 成爲一個能夠返回代理的協議,經過它能夠把任何配置轉發給特定類的實例。網站

但爲何不直接在 UIView 裏面搞個屬性或者方法來作這件事兒呢?ui

由於諸如 UIBarButtonItem 這樣的控件並非 UIView 的子類,它只是持有一個視圖實例而已。經過這種設計,UIAppearance 能夠處理全部類型的 UI 控件,不管它是 UIView 的子類,仍是包含了視圖實例的非 UIView 控件。atom

閱讀 UIAppearance 和 UIAppearanceContainer 的頭文件

咱們剛纔說過 UIApearance 其實是一個協議,該協議需實現如下幾個方法:spa

+ (instancetype)appearance;
+ (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0);
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait NS_AVAILABLE_IOS(8_0);
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes  NS_AVAILABLE_IOS(9_0);
// 已經廢棄的兩個方法
+ (instancetype)appearanceWhenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION NS_DEPRECATED_IOS(5_0, 9_0, "Use +appearanceWhenContainedInInstancesOfClasses: instead") __TVOS_PROHIBITED;
+ (instancetype)appearanceForTraitCollection:(UITraitCollection *)trait whenContainedIn:(nullable Class <UIAppearanceContainer>)ContainerClass, ... NS_REQUIRES_NIL_TERMINATION NS_DEPRECATED_IOS(8_0, 9_0, "Use +appearanceForTraitCollection:whenContainedInInstancesOfClasses: instead") __TVOS_PROHIBITED;
複製代碼

在這個協議中,須要簡單解釋一下第 3 和第 4 個方法,這個兩個方法是用於解決 Size Classes 的問題而誕生的,經過這兩個 API,咱們能夠控制在不一樣屏幕尺寸下的樣式。設計

另一個與之對應的協議是 UIAppearanceContainer,該協議並無任何約定方法。由於它只是做爲一個容器。

常見的,如 UIView 實現了 UIAppearance 這兩種協議,既能夠獲取外觀代理,也能夠做爲外觀容器。而 UIViewController 則是僅實現了 UIAppearanceContainer 協議,很簡單,它自己是控制器而不是 view,做爲容器,爲 UIView 等服務。

UIAppearance 的使用

哪些屬性能夠被 UIAppearance 調用?

在 UIKit 中被 UI_APPEARANCE_SELECTOR 宏標註的屬性能夠被 UIAppearance 調用。 例如 UIBarButtonItem 裏的 tintColor 屬性。

@property(nullable, nonatomic,strong) UIColor *tintColor NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
複製代碼

在 swift 中沒有宏的概念,因此屬性沒法被 UI_APPEARANCE_SELECTOR 標註,若是想讓某個屬性支持 UIAppearance 能夠爲該屬性使用 dynamic 關鍵字

何使用 UIAppearance 修改 UI 控件的屬性?

UIAppearance 不只能夠修改某一類型控件的所有實例,也能夠修改部分實例,開發者只須要使用正確的 API 便可。仍是以 UIBarButtonItem 爲例。

當咱們想改變全部 UIBarButtonItem 實例的 tintColor 時,代碼以下:

[[UIBarButtonItem appearance] setTintColor:myColor];
複製代碼

當咱們想在某些指定容器類裏改變 UIBarButtonItem 的 tintColor 時,咱們能夠這麼作:

[[UIBarButtonItem appearanceWhenContainedInInstancesOfClasses:@[[UINavigationBar class]]] setTintColor:myColor];
複製代碼

UIAppearance 裏與 UITraitCollection 相關的兩個方法與上面兩個方法的使用規則類似,在這裏就不作贅述了

如何讓 UIAppearance 生效?

使用 UIAppearance 只有在視圖添加到 window 時纔會生效,對於已經在 window 中的視圖並不會生效。因此,對於已經在 window 裏的視圖,能夠採用從視圖裏移除並再次添加回去的方法使得 UIAppearance 的設置生效。

因此若是你發現本身的設置沒有生效的話,不妨參考下這個規則。

如何讓自定義 view 的屬性支持 UIAppearance

在實際使用過程當中,咱們絕大多數的視圖類都繼承自 UIView,UIView 的容器也基本上是 UIView 或 UIController,因此基本不須要開發者去實現這兩個協議。

對於須要支持使用 UIAppearance 來設置的屬性,在屬性後增長 UI_APPEARANCE_SELECTOR 宏聲明便可。

須要說明的是,在遵循 UIAppearanceContainer 協議的類中,聲明與 UIAppearance 相關屬性的方法時,要遵循兩個代碼規範:

  • 在方法的最後用 UI_APPEARANCE_SELECTOR 標註
  • 方法命名要遵照下面的格式
//You may have no axes or as many as you like for any property.
- (void)setProperty:(PropertyType)property forAxis1:(IntegerType)axis1 axis2:(IntegerType)axis2 axisN:(IntegerType)axisN;
- (PropertyType)propertyForAxis1:(IntegerType)axis1 axis2:(IntegerType)axis2 axisN:(IntegerType)axisN;
//Example
- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
- (nullable UIImage *)backgroundImageForState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;
複製代碼

UIAppearance 的探討

Apple 官方對於 UIAppearance 的介紹並很少,網上的文章也大多都停留在使用層面上,今天咱們來深刻討論一下 UIAppearance 內部的祕密。

沒有什麼卵用的 UI_APPEARANCE_SELECTOR

查看 Apple 的官方文檔後,你就會發現它其實什麼也沒幹…..

#define UI_APPEARANCE_SELECTOR
複製代碼

既然它什麼都沒幹,那麼對於沒有被 UI_APPEARANCE_SELECTOR 標記的屬性,咱們是否也能用 UIAppearance 進行統一設置呢?

答案是能夠的。

此時你心裏充滿了疑惑,

咱們不妨將這個問題分紅兩個部分:

  • 第一部分就是爲何會有這個宏定義,若是真的沒有它會有什麼問題麼?
  • 第二部分就是 UIAppearance 是如何實現統一修改控件樣式的呢?

真沒問題麼?

事實證實, UIAppearance 確實能夠對一些沒有被 UI_APPEARANCE_SELECTOR 標記的屬性進行設置,甚至某些方法也是支持的 UIAppearance,例如 UISegmentedControl 的 setWidth:forSegmentAtIndex 方法。

但這種作法應該被避免,緣由很簡單:這些沒有被標記的屬性不必定會在將來的 iOS 版本中適用。

Understanding UIAppearance 這篇文章裏也給出了一樣的觀點。

如何實現的?

那麼 UIAppearance 究竟是如何實現的呢?國外的大神們已經對此有了研究結論,我直接給出連接,感興趣的朋友能夠閱讀一下 UIAppearance and Custom Views。

友善的提醒!

在自定義 View 中使用 UIAppearance 仍是有一些須要注意的事項,具體的內容能夠參考 UIAppearance for Custom Views

參考文獻

個人博客原文:sketchk.xyz/2018/01/25/…

相關文章
相關標籤/搜索