隨著ios 13的發布,公司的項目也勢必要著手適配了。現匯總一下ios 13的各種坑
1. kvc訪問私有屬性
這次ios 13系統升級,影響范圍最廣的應屬kvc訪問修改私有屬性了,直接禁止開發者獲取或直接設置私有屬性。而kvc的初衷是允許開發者通過key名直接訪問修改對象的屬性值,為其中最典型的 uitextfield 的 _placeholderlabel、uisearchbar 的 _searchfield。
造成影響:在ios 13下app閃退
錯誤代碼:
1
2
3
4
5
6
|
// placeholderlabel私有屬性訪問 [textfield setvalue:[uicolor redcolor] forkeypath:@ "_placeholderlabel.textcolor" ]; [textfield setvalue:[uifont boldsystemfontofsize:16] forkeypath:@ "_placeholderlabel.font" ]; // searchfield私有屬性訪問 uisearchbar *searchbar = [[uisearchbar alloc] init]; uitextfield *searchtextfield = [searchbar valueforkey:@ "_searchfield" ]; |
解決方案:
使用 nsmutableattributedstring 富文本來替代kvc訪問 uitextfield 的 _placeholderlabel
1
|
textfield.attributedplaceholder = [[nsattributedstring alloc] initwithstring:@ "placeholder" attributes:@{nsforegroundcolorattributename: [uicolor darkgraycolor], nsfontattributename: [uifont systemfontofsize:13]}]; |
因此,可以為uitextfeild創建category,專門用于處理修改placeholder屬性提供方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
#import "uitextfield+changeplaceholder.h" @implementation uitextfield (change) - ( void )setplaceholderfont:(uifont *)font { [self setplaceholdercolor:nil font:font]; } - ( void )setplaceholdercolor:(uicolor *)color { [self setplaceholdercolor:color font:nil]; } - ( void )setplaceholdercolor:(nullable uicolor *)color font:(nullable uifont *)font { if ([self checkplaceholderempty]) { return ; } nsmutableattributedstring *placeholderattristring = [[nsmutableattributedstring alloc] initwithstring:self.placeholder]; if (color) { [placeholderattristring addattribute:nsforegroundcolorattributename value:color range:nsmakerange(0, self.placeholder.length)]; } if (font) { [placeholderattristring addattribute:nsfontattributename value:font range:nsmakerange(0, self.placeholder.length)]; } [self setattributedplaceholder:placeholderattristring]; } - ( bool )checkplaceholderempty { return (self.placeholder == nil) || ([[self.placeholder stringbytrimmingcharactersinset:[nscharacterset whitespaceandnewlinecharacterset]] length] == 0); } |
關于 uisearchbar,可遍歷其所有子視圖,找到指定的 uitextfield 類型的子視圖,再根據上述 uitextfield 的通過富文本方法修改屬性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#import "uisearchbar+changeprivatetextfieldsubview.h" @implementation uisearchbar (changeprivatetextfieldsubview) /// 修改searchbar系統自帶的textfield - ( void )changesearchtextfieldwithcompletionblock:( void (^)(uitextfield *textfield))completionblock { if (!completionblock) { return ; } uitextfield *textfield = [self findtextfieldwithview:self]; if (textfield) { completionblock(textfield); } } /// 遞歸遍歷uisearchbar的子視圖,找到uitextfield - (uitextfield *)findtextfieldwithview:(uiview *)view { for (uiview *subview in view.subviews) { if ([subview iskindofclass:[uitextfield class ]]) { return (uitextfield *)subview; } else if (subview.subviews.count > 0) { return [self findtextfieldwithview:subview]; } } return nil; } @end |
ps:關于如何查找自己的app項目是否使用了私有api,可以參考ios查找私有api 文章
2. 模態彈窗 viewcontroller 默認樣式改變
模態彈窗屬性 uimodalpresentationstyle 在 ios 13 下默認被設置為 uimodalpresentationautomatic新特性,展示樣式更為炫酷,同時可用下拉手勢關閉模態彈窗。
若原有模態彈出 viewcontroller 時都已指定模態彈窗屬性,則可以無視該改動。
若想在 ios 13 中繼續保持原有默認模態彈窗效果。可以通過 runtime 的 method swizzling 方法交換來實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#import "uiviewcontroller+changedefaultpresentstyle.h" @implementation uiviewcontroller (changedefaultpresentstyle) + ( void )load { static dispatch_once_t oncetoken; dispatch_once(&oncetoken, ^{ class class = [self class ]; //替換方法 sel originalselector = @selector(presentviewcontroller:animated:completion:); sel newselector = @selector(new_presentviewcontroller:animated:completion:); method originalmethod = class_getinstancemethod( class , originalselector); method newmethod = class_getinstancemethod( class , newselector);; bool didaddmethod = class_addmethod( class , originalselector, method_getimplementation(newmethod), method_gettypeencoding(newmethod)); if (didaddmethod) { class_replacemethod( class , newselector, method_getimplementation(originalmethod), method_gettypeencoding(originalmethod)); } else { method_exchangeimplementations(originalmethod, newmethod); } }); } - ( void )new_presentviewcontroller:(uiviewcontroller *)viewcontrollertopresent animated:( bool )flag completion:( void (^)( void ))completion { viewcontrollertopresent.modalpresentationstyle = uimodalpresentationfullscreen; [self new_presentviewcontroller:viewcontrollertopresent animated:flag completion:completion]; } @end |
3. 黑暗模式的適配
針對黑暗模式的推出,apple官方推薦所有三方app盡快適配。目前并沒有強制app進行黑暗模式適配。因此黑暗模式適配范圍現在可采用以下三種策略:
- 全局關閉黑暗模式
- 指定頁面關閉黑暗模式
- 全局適配黑暗模式
3.1. 全局關閉黑暗模式
方案一:在項目 info.plist 文件中,添加一條內容,key為 user interface style,值類型設置為string并設置為 light 即可。
方案二:代碼強制關閉黑暗模式,將當前 window 設置為 light 狀態。
1
2
3
|
if (@available(ios 13.0,*)){ self.window.overrideuserinterfacestyle = uiuserinterfacestylelight; } |
3.2 指定頁面關閉黑暗模式
從xcode 11、ios 13開始,uiviewcontroller與view新增屬性 overrideuserinterfacestyle,若設置view對象該屬性為指定模式,則強制該對象以及子對象以指定模式展示,不會跟隨系統模式改變。
- 設置 viewcontroller 該屬性, 將會影響視圖控制器的視圖以及子視圖控制器都采用該模式
- 設置 view 該屬性, 將會影響視圖及其所有子視圖采用該模式
- 設置 window 該屬性, 將會影響窗口中的所有內容都采用該樣式,包括根視圖控制器和在該窗口中顯示內容的所有控制器
3.3 全局適配黑暗模式
配黑暗模式,主要從兩方面入手:圖片資源適配與顏色適配
圖片資源適配
打開圖片資源管理庫 assets.xcassets,選中需要適配的圖片素材item,打開最右側的 inspectors 工具欄,找到 appearances 選項,并設置為 any, dark模式,此時會在item下增加dark appearance,將黑暗模式下的素材拖入即可。關于黑暗模式圖片資源的加載,與正常加載圖片方法一致。
顏色適配
ios 13開始uicolor變為動態顏色,在light mode與dark mode可以分別設置不同顏色。若uicolor色值管理,與圖片資源一樣存儲于 assets.xcassets 中,同樣參照上述方法適配。若uicolor色值并沒有存儲于 assets.xcassets 情況下,自定義動態uicolor時,在ios 13下初始化方法增加了兩個方法
1
2
|
+ (uicolor *)colorwithdynamicprovider:(uicolor * (^)(uitraitcollection *))dynamicprovider api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); - (uicolor *)initwithdynamicprovider:(uicolor * (^)(uitraitcollection *))dynamicprovider api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); |
這兩個方法要求傳一個block,block會返回一個 uitraitcollection 類
當系統在黑暗模式與正常模式切換時,會觸發block回調
示例代碼:
1
2
3
4
5
6
7
8
9
|
uicolor *dynamiccolor = [uicolor colorwithdynamicprovider:^uicolor * _nonnull(uitraitcollection * _nonnull traincollection) { if ([traincollection userinterfacestyle] == uiuserinterfacestylelight) { return [uicolor whitecolor]; } else { return [uicolor blackcolor]; } }]; [self.view setbackgroundcolor:dynamiccolor]; |
當然了,ios 13系統也默認提供了一套基本的黑暗模式uicolor動態顏色,具體聲明如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@property ( class , nonatomic, readonly) uicolor *systembrowncolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *systemindigocolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *systemgray2color api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemgray3color api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemgray4color api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemgray5color api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemgray6color api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *labelcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *secondarylabelcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *tertiarylabelcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *quaternarylabelcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *linkcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *placeholdertextcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *separatorcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *opaqueseparatorcolor api_available(ios(13.0), tvos(13.0)) api_unavailable(watchos); @property ( class , nonatomic, readonly) uicolor *systembackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *secondarysystembackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *tertiarysystembackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemgroupedbackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *secondarysystemgroupedbackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *tertiarysystemgroupedbackgroundcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *systemfillcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *secondarysystemfillcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *tertiarysystemfillcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); @property ( class , nonatomic, readonly) uicolor *quaternarysystemfillcolor api_available(ios(13.0)) api_unavailable(tvos, watchos); |
監聽模式的切換
當需要監聽系統模式發生變化并作出響應時,需要用到 viewcontroller 以下函數
1
2
3
4
5
|
// 注意:參數為變化前的traitcollection,改函數需要重寫 - ( void )traitcollectiondidchange:(uitraitcollection *)previoustraitcollection; // 判斷兩個uitraitcollection對象是否不同 - ( bool )hasdifferentcolorappearancecomparedtotraitcollection:(uitraitcollection *)traitcollection; |
示例代碼:
1
2
3
4
5
6
7
|
- ( void )traitcollectiondidchange:(uitraitcollection *)previoustraitcollection { [super traitcollectiondidchange:previoustraitcollection]; // trait has changed? if ([self.traitcollection hasdifferentcolorappearancecomparedtotraitcollection:previoustraitcollection]) { // do something... } } |
系統模式變更,自定義重繪視圖
當系統模式變更時,系統會通知所有的 view以及 viewcontroller 需要更新樣式,會觸發以下方法執行(參考apple官方適配鏈接):
nsview
1
2
3
4
|
- ( void )updatelayer; - ( void )drawrect:(nsrect)dirtyrect; - ( void )layout; - ( void )updateconstraints; |
uiview
1
2
3
4
5
|
- ( void )traitcollectiondidchange:(uitraitcollection *)previoustraitcollection; - ( void )layoutsubviews; - ( void )drawrect:(nsrect)dirtyrect; - ( void )updateconstraints; - ( void )tintcolordidchange; |
uiviewcontroller
1
2
3
4
|
- ( void )traitcollectiondidchange:(uitraitcollection *)previoustraitcollection; - ( void )updateviewconstraints; - ( void )viewwilllayoutsubviews; - ( void )viewdidlayoutsubviews; |
uipresentationcontroller
1
2
3
|
- ( void )traitcollectiondidchange:(uitraitcollection *)previoustraitcollection; - ( void )containerviewwilllayoutsubviews; - ( void )containerviewdidlayoutsubviews; |
4. launchimage即將廢棄
使用 launchimage 設置啟動圖,需要提供各類屏幕尺寸的啟動圖適配,這種方式隨著各類設備尺寸的增加,增加了額外不必要的工作量。為了解決 launchimage 帶來的弊端,ios 8引入了 launchscreen 技術,因為支持 autolayout + sizeclass,所以通過 launchscreen 就可以簡單解決適配當下以及未來各種屏幕尺寸。
apple官方已經發出公告,2020年4月開始,所有使用ios 13 sdk 的app都必須提供 launchscreen。創建一個 launchscreen 也非常簡單
(1)new files創建一個 launchscreen,在創建的 viewcontroller 下 view 中新建一個 image,并配置 image 的圖片
(2)調整 image 的 frame 為占滿屏幕,并修改 image 的 autoresizing 如下圖,完成
5. 新增一直使用藍牙的權限申請
在ios13之前,無需權限提示窗即可直接使用藍牙,但在ios 13下,新增了使用藍牙的權限申請。最近一段時間上傳ipa包至app store會收到以下提示。
解決方案:只需要在 info.plist 里增加以下條目:
1
2
|
<key>nsbluetoothalwaysusagedescription</key> <string>這里輸入使用藍牙來做什么</string>` |
6. sign with apple
在ios 13系統中,apple要求提供第三方登錄的app也要支持「sign with apple」,具體實踐參考 ios sign with apple實踐
7. 推送device token適配
在ios 13之前,獲取device token 是將系統返回的 nsdata 類型數據通過 -(void)description; 方法直接轉換成 nsstring 字符串。
ios 13之前獲取結果:
ios 13之后獲取結果:
適配方案:目的是要將系統返回 nsdata 類型數據轉換成字符串,再傳給推送服務方。-(void)description; 本身是用于為類調試提供相關的打印信息,嚴格來說,不應直接從該方法獲取數據并應用于正式環境中。將 nsdata 轉換成 hexstring,即可滿足適配需求。
1
2
3
4
5
6
7
8
9
|
- (nsstring *)gethexstringfordata:(nsdata *)data { nsuinteger length = [data length]; char *chars = ( char *)[data bytes]; nsmutablestring *hexstring = [[nsmutablestring alloc] init]; for (nsuinteger i = 0; i < length; i++) { [hexstring appendstring:[nsstring stringwithformat:@ "%0.2hhx" , chars[i]]]; } return hexstring; } |
8. uikit 控件變化
主要還是參照了apple官方的 uikit 修改文檔聲明。ios 13 release notes
8.1. uitableview
ios 13下設置 cell.contentview.backgroundcolor 會直接影響 cell 本身 selected 與 highlighted 效果。建議不要對 contentview.backgroundcolor 修改,而對 cell 本身進行設置。
8.2. uitabbar
badge 文字大小變化
ios 13之后,badge 字體默認由13號變為17號。建議在初始化 tabbarcontroller 時,顯示 badge 的 viewcontroller 調用 setbadgetextattributes:forstate: 方法
1
2
3
4
|
if (@available(ios 13, *)) { [viewcontroller.tabbaritem setbadgetextattributes:@{nsfontattributename: [uifont systemfontofsize:13]} forstate:uicontrolstatenormal]; [viewcontroller.tabbaritem setbadgetextattributes:@{nsfontattributename: [uifont systemfontofsize:13]} forstate:uicontrolstateselected]; } |
8.2. uitabbaritem
加載gif需設置 scale 比例
1
2
3
4
5
6
7
8
9
10
11
|
nsdata *data = [nsdata datawithcontentsoffile:path]; cgimagesourceref gifsource = cgimagesourcecreatewithdata(cfbridgingretain(data), nil); size_t gifcount = cgimagesourcegetcount(gifsource); cgimageref imageref = cgimagesourcecreateimageatindex(gifsource, i,null); // ios 13之前 uiimage *image = [uiimage imagewithcgimage:imageref] // ios 13之后添加scale比例(該imageview將展示該動圖效果) uiimage *image = [uiimage imagewithcgimage:imageref scale:image.size.width / cgrectgetwidth(imageview.frame) orientation:uiimageorientationup]; cgimagerelease(imageref); |
無文字時圖片位置調整
ios 13下不需要調整 imageinsets,圖片會自動居中顯示,因此只需要針對ios 13之前的做適配即可。
1
2
3
|
if (ios_version < 13.0) { viewcontroller.tabbaritem.imageinsets = uiedgeinsetsmake(5, 0, -5, 0); } |
8.3. 新增 diffable datasource
在 ios 13下,對 uitableview 與 uicollectionview 新增了一套 diffable datasource api。為了更高效地更新數據源刷新列表,避免了原有粗暴的刷新方法 - (void)reloaddata,以及手動調用控制列表刷新范圍的api,很容易出現計算不準確造成 nsinternalinconsistencyexception 而引發app crash。api 官方鏈接
9. statusbar新增樣式
statusbar 新增一種樣式,默認的 default 由之前的黑色字體,變為根據系統模式自動選擇展示 lightcontent 或者 darkcontent
針對ios 13 sdk適配,后續將會持續收集并更新
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://juejin.im/post/5da033756fb9a04e37316872