ios UIAppearance 協議

一、前言

iOS 上提供了一個比較強大的工具UIAppearance,我們通過UIAppearance設置一些UI的全局效果,這樣就可以很方便的實現UI的自定義效果又能最簡單的實現統一界面風格。

+ (id)appearance ; 這個是這個協議里最重要的方法了 .
這個方法是統一全部改,比如你可以設置UIView.appearance().backgroundColor = UIColor.orange, 這樣所有View默認顏色就都是橘色。

二、主題設置的前提

為什么我們可以給它們設置主題屬性呢?哪些對象 哪些屬性 可以設置主題屬性呢?

1. 那些控件和類,可以設置主題呢?

回答:只要遵守了UIAppearance協議的類,都可以設置主題

查看UIView的頭文件,可得,UIView可以設置主題,那么不是所有繼承UIView的控件就都可以設置主題了嗎?是的

觀看可得,不僅,只是控件可以設置主題,UIBarItem等只要遵守了UIAppearance協議的類,都可以設置主題

這里列舉了一部分:

  • UIView
  • UIActivitiIndicatorView
  • UIBarButtonItem
  • UIBarItem
  • UINavgationBar
  • UIPopoverControll
  • UIProgressView
  • UISearchBar
  • UISegmentControll
  • UISlider
  • UISwitch
  • UITabBar
  • UITabBarItem
  • UIToolBar
  • UIViewController

2.遵守UIAppearance協議的類的,那些屬性可以設置主題呢?

通過主題對象設置屬性的前提:?屬性后面是否帶有UI_APPEARANCE_SELECTOR的方法

- (void)setTitleTextAttributes:(NSDictionary *)attributes forState:(UIControlState)state
NS_AVAILABLE_IOS(5_0) UI_APPEARANCE_SELECTOR;

因為并不是所以屬性可以設置主題,設置主題屬性是有前提的。

查看頭文件可得,可以設置UITabBarItem類,發現setTitleTextAttributes:可以設置UITabBarItem文字主題

  • 案例:設置所有的UITabBarItem,普通與選中狀態下的文字顏色

三、運用主題appearance,是否會生效,何時會生效

1、主題會生效的場景:先設置控件主題,后添加控件到視圖上

  • 添加控件時,添加的那一刻會檢查主題,會根據主題設置控件 =》主題會生效

2、主題不會生效的場景:先添加控件,后設置主題

  • 控件已經添加,后設置主題,對以前的添加的控件不起作用了

如果先添加控件,后設置主題,主題失效,我們該如何解決呢?

最優方案:當然是改下調用順序,先設置appearance,在添加控件。

當然作為腦洞或者探究原理,也可以重新把View移除,然后在添加一次,觸發一次渲染,實測是生效的。(但是這樣的代碼,誰看誰撓頭,還是不要在上線版本中寫。)

總結一下:
1、控件遵守了UIAppearance協議,才能對控件進行appearance設置
2、被UI_APPEARANCE_SELECTOR這個宏修飾的屬性一定能使用appearance進行設置,其他屬性則不保證具備該功能
3、appearance設置需要在該控制顯示之前設置完成,否則會無效
?

三、實現原理

UIApearance?實際上是一個協議(Protocol),我們可以用它來獲取一個類的外觀代理(Appearance Proxy)。該協議需實現這幾個方法:

+ (instancetype)appearance;
+ (instancetype)appearanceWhenContainedInInstancesOfClasses:(NSArray<Class <UIAppearanceContainer>> *)containerTypes NS_AVAILABLE_IOS(9_0);
// 詳細方法見 UIKit/UIAppearance.h

另外一個與之對應的協議是?UIAppearanceContainer,該協議并沒有任何約定方法。因為它只是作為一個容器。

常見的,如 UIView 實現了?UIAppearance?這兩種協議,既可以獲取外觀代理,也可以作為外觀容器。 而 UIViewController 則是僅實現了?UIAppearanceContainer?協議,很簡單,它本身是控制器而不是 view,作為容器,為 UIView 等服務。

事實上,在使用中,我們所有的視圖類都繼承自 UIView,UIView 的容器也基本上是 UIView 或 UIController,基本不需要自己去實現這兩個協議。對于需要支持使用 appearance 來設置的屬性,在屬性后增加?UI_APPEARANCE_SELECTOR?宏聲明即可。 文檔中也有解釋?UI_APPEARANCE_SELECTOR?用來標記屬性用于外觀代理,支持哪些類型等等。

To participate in the appearance proxy API, tag your appearance property selectors in your header with UI_APPEARANCE_SELECTOR.Appearance property selectors must be of the form:- (void)setProperty:(PropertyType)property forAxis1:(IntegerType)axis1 axis2:(IntegerType)axis2 axisN:(IntegerType)axisN;- (PropertyType)propertyForAxis1:(IntegerType)axis1 axis2:(IntegerType)axis2 axisN:(IntegerType)axisN;You may have no axes or as many as you like for any property. PropertyType may be any standard iOS type: id, NSInteger, NSUInteger, CGFloat, CGPoint, CGSize, CGRect, UIEdgeInsets or UIOffset. IntegerType must be either NSInteger or NSUInteger; we will throw an exception if other types are used in the axes.

翻譯一下:

要參與外觀代理 API,請在頭文件中用UI_APPEARANCE_SELECTOR標記您的外觀屬性選擇器。

外觀屬性選擇器必須采用以下形式:

- (void) setProperty:(屬性類型) property forAxis1:(整數類型) axis1 axis2:(整數類型) axis2 axisN:(整數類型) axisN;
- (屬性類型) propertyForAxis1:(整數類型) axis1 axis2:(整數類型) axis2 axisN:(整數類型) axisN;

對于任何屬性,您可以沒有參數,也可以有任意多個參數。屬性類型可以是任何標準的 iOS 類型:id、NSInteger、NSUInteger、CGFloat、CGPoint、CGSize、CGRect、UIEdgeInsets 或 UIOffset。整數類型必須是 NSInteger 或 NSUInteger;如果在參數中使用其他類型,我們將拋出異常。

demo驗證原理

寫一個簡單的小 Demo,自定義 CardView,有兩個 subview: headerView 和 footerView,聲明 2 個屬性:

@property (nonatomic, strong) UIColor *headerColor UI_APPEARANCE_SELECTOR;
@property (nonatomic, strong) UIColor *bodyColor UI_APPEARANCE_SELECTOR;

Setter 方法都加斷點調試:

- (void)setHeaderColor:(UIColor *)headerColor
{_headerColor = headerColor;self.headerView.backgroundColor = _headerColor;
}- (void)setBodyColor:(UIColor *)bodyColor
{_bodyColor = bodyColor;self.bodyView.backgroundColor = _bodyColor;
}

在 ViewController 的 view 中加一個按鈕,點擊則創建并添加 CardView,每行代碼均加斷點:

- (IBAction)createButtonTouched:(id)senderCardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(20, 100, 80, 120)];[self.view addSubview:cardView];cardView.headerColor = [UIColor greenColor];
}

另外,在較早的時候,添加 appearance 設置:

[CardView appearance].headerColor = [UIColor redColor];
[CardView appearance].bodyColor = [UIColor orangeColor];

運行發現,在通過 appearance 設置屬性的時候,并沒有調用 setter 方法,由此可知 appearance 并不會生成實例,立即賦值。當 cardView 被添加到主視圖(即視圖樹)中去的時候,才依次調用兩個 setter 方法,調用棧如下

ios_uiappearance_image_1

從 15 至 11 可以看出確實是加入到視圖樹中才觸發的,從 7 至 2 可以基本猜測出,appearance 設置的屬性,都以 Invocation 的形式存儲到 _UIApperance 類中(事實上 _UIApperance 類中就有一個 _appearanceInvocations 數組),等到視圖樹 performUpdates 的時候,會去檢查有沒有相關的屬性設置,有則 invoke。(這里可以看看 NSInvocation)

緊接著,它進入了 bodyColor 的 setter

ios_uiappearance_image_2

然后,當手動設置屬性的時候,它是直接進入 setter 的。

ios_uiappearance_image_3

到這里,基本清晰了。

每一個實現 UIAppearance 協議的類,都會有一個 _UIApperance 實例,保存著這個類通過 appearance 設置屬性的 invocations,在該類被添加或應用到視圖樹上的時候,它會檢查并調用這些屬性設置。這樣就實現了讓所有該類的實例都自動統一屬性。

當然,如果后面又手動設置了屬性,肯定會覆蓋了。從上面可以知道,appearance 生效是在被添加到視圖樹時,所以,在此之后設置 appearance,則不會起作用,而在手動設置屬性之后被添加到視圖樹上,手動設置的會被覆蓋。appearance 只是起到一個代理作用,在特定的時機,讓代理替所有實例做同樣的事。

嘗試一下,去掉?UI_APPEARANCE_SELECTOR?宏聲明,然后通過 appearance 設置屬性,會怎么樣呢? 測試后發現,結果是一樣的。也就是說?UI_APPEARANCE_SELECTOR?并沒有干什么事,正如文檔所說,只是 tag 一下。看?UI_APPEARANCE_SELECTOR?宏定義如下

	#define UI_APPEARANCE_SELECTOR __attribute__((annotate("ui_appearance_selector")))

由此可見,UI_APPEARANCE_SELECTOR?真的啥都沒干。。只是為了代碼可讀性,方便開發者使用,還是在需要的地方加上它。

原理小結:

1. 調用 +appearance 獲得 proxy 對象,這個 appearance 對象并不是真正的?UIView?的實例,而是 UIKit 給你的一個 proxy,它不會馬上設置屬性,而是記錄這個調用。

2.?UIKit 使用 NSInvocation 保存調用信息,包括方法名和參數值。UIAppearance 會創建一個 NSInvocation,記錄你設置了 selector: 和?value, 在合適的時機重新調用設置。

3.在控件顯示時統一“回放”設置,當后續創建某個 UIView?實例時,UIKit 會:

  • 檢查當前 UIView 類型是否有 appearance 設置;

  • 遍歷已記錄的 selector;

  • 創建 NSInvocation,將目標值更新為這個實例的屬性

使用方式:?iOS筆記之Appearance方法 - 代碼先鋒網
實現原理:?iOS UIAppearance 探秘 | 流光不加少

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91382.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91382.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91382.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

進階數據結構:用紅黑樹實現封裝map和set

? 嘿,各位技術潮人!好久不見甚是想念。生活就像一場奇妙冒險,而編程就是那把超酷的萬能鑰匙。此刻,陽光灑在鍵盤上,靈感在指尖跳躍,讓我們拋開一切束縛,給平淡日子加點料,注入滿滿的 passion。準備好和我一起沖進代碼的奇幻宇宙了嗎?Let’s go! 我的博客:yuanManGa…

【數據結構初階】--二叉樹(五)

&#x1f525;個人主頁&#xff1a;草莓熊Lotso &#x1f3ac;作者簡介&#xff1a;C研發方向學習者 &#x1f4d6;個人專欄&#xff1a; 《C語言》 《數據結構與算法》《C語言刷題集》《Leetcode刷題指南》 ??人生格言&#xff1a;生活是默默的堅持&#xff0c;毅力是永久的…

redis布隆過濾器解決緩存擊穿問題

在電商系統中&#xff0c;商品詳情頁是一個典型的高頻訪問場景。當用戶請求某個商品的詳情時&#xff0c;系統會優先從緩存中獲取數據。如果緩存中沒有該商品的詳情&#xff0c;系統會去數據庫查詢并更新緩存。然而&#xff0c;如果某個熱門商品的緩存失效&#xff0c;大量請求…

1+1>2!特征融合如何讓目標檢測更懂 “場景”?

來gongzhonghao【圖靈學術計算機論文輔導】&#xff0c;快速拿捏更多計算機SCI/CCF發文資訊&#xff5e;在多模態大模型&#xff08;MLLM&#xff09;時代&#xff0c;特征融合與目標檢測的研究方向正變得愈發關鍵。從紅外與可見光圖像的融合&#xff0c;到語音活動檢測中的特征…

詳解賽靈思SRIO IP并提供一種FIFO封裝SRIO的收發控制器仿真驗證

概述RapidIO標準定義為三層&#xff1a;邏輯層、傳輸層、物理層。邏輯層&#xff1a;定義總體協議和包格式&#xff0c;包含設備發起/完成事務的必要信息。傳輸層&#xff1a;提供包傳輸的路由信息&#xff08;對頂層不可見&#xff09;。物理層&#xff1a;描述設備級接口細節…

深度學習:簡介與任務分類總覽

一、什么是深度學習&#xff1f;1.1 深度學習的定義深度學習&#xff08;Deep Learning&#xff09;是機器學習的一種特殊形式&#xff0c;它依賴于具有多層結構的神經網絡自動從數據中學習特征并完成任務&#xff0c;如圖像識別&#xff0c;語音識別&#xff0c;自然語言處理等…

MSPM0開發學習筆記:二維云臺畫圖(2025電賽 附源代碼及引腳配置)

前言 今年的電賽&#xff08;2025&#xff09;&#xff0c;很多題都與云臺相關&#xff0c;因此為備戰電賽&#xff0c;博主這邊也是準備了一個由兩個42步進電機驅動的云臺并提前進行調試&#xff0c;避免賽題出來之后手忙腳亂的&#xff0c;這邊的兩個42步進電機采用同一個驅…

借助 Wisdom SSH 的 AI 助手構建 Linux 開發環境

借助Wisdom SSH的AI助手構建Linux開發環境 在Linux系統的開發場景中&#xff0c;快速、準確地搭建開發環境至關重要。Wisdom SSH憑借其強大的AI助手&#xff0c;能極大簡化這一過程&#xff0c;其官網為ssh.wisdomheart.cn。以下以在Ubuntu 22.04服務器上構建Python開發環境&am…

Python 程序設計講義(44):組合數據類型——集合類型:創建集合

Python 程序設計講義&#xff08;44&#xff09;&#xff1a;組合數據類型——集合類型&#xff1a;創建集合 目錄Python 程序設計講義&#xff08;44&#xff09;&#xff1a;組合數據類型——集合類型&#xff1a;創建集合一、集合的特征二、創建集合&#xff1a;使用set()函…

10 - 大語言模型 —Transformer 搭骨架,BERT 裝 “雙筒鏡”|解密雙向理解的核心

目錄 1、為什么 BERT 能 “懂” 語言&#xff1f;先看它的 “出身” 2、核心邏輯 2.1、“自學階段”—— 預訓練&#xff0c;像嬰兒學說話一樣積累語感 2.1.1、簡述 2.1.2、核心本事&#xff1a;“雙向注意力”&#xff0c;像人一樣 “聚焦重點” 2.2、“專項復習”—— …

【Spring Boot 快速入門】四、MyBatis

目錄MyBatis&#xff08;一&#xff09;入門簡介MyBatis 入門LombokMyBatis 基礎操作數據準備刪除預編譯新增更新查詢XML 映射文件MyBatis&#xff08;一&#xff09;入門 簡介 MyBatis 是一款 優秀的持久層框架&#xff0c;它支持 自定義 SQL、存儲過程以及高級映射&#xf…

Spring IOC 基于Cglib實現含構造函數的類實例化策略

作者&#xff1a;小凱 分享、讓自己和他人都能有所收獲&#xff01; 一、前言 技術成長&#xff0c;是對場景設計細節不斷的雕刻&#xff01; 你覺得自己的技術什么時候得到了快速的提高&#xff0c;是CRUD寫的多了以后嗎&#xff1f;想都不要想&#xff0c;絕對不可能&#xf…

composer 常用命令

### 設置鏡像源全局設置composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/當個項目設置composer config repo.packagist composer https://mirrors.aliyun.com/composer/恢復官方源composer config -g --unset repos.packagist### 常用源阿里云…

【python】Python爬蟲入門教程:使用requests庫

Python爬蟲入門教程&#xff1a;使用requests庫 爬蟲是數據獲取的重要手段&#xff0c;下面我將通過一個完整的示例&#xff0c;教你如何使用Python的requests庫編寫一個簡單的爬蟲。我們將以爬取豆瓣電影Top250為例。 【python】網絡爬蟲教程 - 教你用python爬取豆瓣電影 Top…

OpenCV圖像縮放:resize

圖像縮放是圖像處理中的基礎操作之一。無論是圖像預處理、數據增強還是圖像金字塔構建&#xff0c;cv::resize 都是我們最常用的函數之一。但你是否注意到&#xff0c;在 OpenCV 中同時還存在一個名為 cv::Mat::resize 的方法&#xff1f;這兩個函數雖然名字類似&#xff0c;但…

汽車、航空航天、適用工業虛擬裝配解決方案

一、現狀在制造業數字化轉型浪潮中&#xff0c;傳統裝配過程仍面臨諸多挑戰&#xff1a;物理樣機試錯成本高、裝配周期冗長、工藝優化依賴經驗、跨部門協作效率低下……如何打破“試錯-返工”的惡性循環&#xff1f;目前總裝工藝通過DELMIA、NX、Creo等工程軟件進行工藝裝配驗證…

頁面跳轉和前端路由的區別

傳統方式&#xff1a;通過改變瀏覽器地址欄的 URL 來實現window.location.href /new-page<a href"/new-page">跳轉到新頁面</a>會導致整個頁面重新加載會觸發瀏覽器向服務器發送新的請求頁面狀態不會保留&#xff0c;所有資源重新加載可以避免新上線的內…

C/C++核心知識點詳解

C/C核心知識點詳解 1. 變量的聲明與定義&#xff1a;內存分配的本質區別 核心概念 在C/C中&#xff0c;變量的聲明和定義是兩個完全不同的概念&#xff1a; 聲明&#xff08;Declaration&#xff09;&#xff1a;告訴編譯器變量的名稱和類型&#xff0c;但不分配內存空間定義&a…

物聯網發展:從概念到應用的演變歷程

物聯網的發展歷程是一部技術革新與社會需求共同驅動的進化史&#xff0c;其演變可劃分為概念萌芽、技術積累、應用拓展和智能融合四個階段&#xff0c;每個階段均以關鍵技術突破或社會需求變革為標志&#xff0c;最終形成萬物互聯的智能生態。以下是具體演變歷程&#xff1a;一…

一個人開發一個App(數據庫)

后端要保存數據&#xff0c;我還是選擇了關系型數據庫Mysql, 因為其它的不熟悉。 flutter端這次我選擇的是ObjectBox&#xff0c;以前都是直接用的sqlite3&#xff0c;看對比ObjectBox效率比sqlite3高許多&#xff0c;這次前端為了用戶體驗&#xff0c;我需要緩存數據&#xff…