随着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