iOS13原生端适配攻略(推荐)

2025-05-29 0 64

随着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,将黑暗模式下的素材拖入即可。关于黑暗模式图片资源的加载,与正常加载图片方法一致。

iOS13原生端适配攻略(推荐)

颜色适配

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 如下图,完成

iOS13原生端适配攻略(推荐)

5. 新增一直使用蓝牙的权限申请

在ios13之前,无需权限提示窗即可直接使用蓝牙,但在ios 13下,新增了使用蓝牙的权限申请。最近一段时间上传ipa包至app store会收到以下提示。

iOS13原生端适配攻略(推荐)

解决方案:只需要在 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之前获取结果:

iOS13原生端适配攻略(推荐)

ios 13之后获取结果:

iOS13原生端适配攻略(推荐)

适配方案:目的是要将系统返回 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适配,后续将会持续收集并更新

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持快网idc。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 iOS13原生端适配攻略(推荐) https://www.kuaiidc.com/89070.html

相关文章

发表评论
暂无评论