对于iOS13适配汇总以及遇到的问题
注意:以下适配内容,必须适配的会以"必须"标出
1. Dark Model(必须)
iOS 13
推出了暗黑模式Dark Model
,UIKit
提供新的系统颜色和Api
来适配不同模式。Dark Model
主要从两个方面来适配,一是颜色
,二是资源文件
,xcassets
里面对于图片也要做相应调整。
颜色
/* Create a dynamic color with a provider.
* When methods are called on this color that need color component values,
* the provider is called with UITraitCollection.currentTraitCollection.
* The provider should use that trait collection to decide a more fundamental UIColor to return.
* As much as possible, use the given trait collection to make that decision, not other state.
*/
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
- (UIColor *)initWithDynamicProvider:(UIColor * (^)(UITraitCollection *traitCollection))dynamicProvider API_AVAILABLE(ios(13.0), tvos(13.0)) API_UNAVAILABLE(watchos);
Exp:
[UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
if (traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
return UIColorRGB(0x000000);
} else {
return UIColorRGB(0xFFFFFF);
}
}];
资源文件
- 打开现有
Assets.xcassets
(或创建Assets
文件)。 - 新建一个图片资源文件(或者颜色资源文件、或者其他资源文件)。
- 在不同模式下添加相应资源文件。
- 代码默认执行时,就可以正常通过名字使用了,系统会根据当前模式自动获取对应的资源文件。
注意⚠️:同一工程内多个Assets
文件在打包后,就会生成一个Assets.car
文件,所以要保证Assets
内资源文件的名字不能相同。
注意:如果不适配暗黑模式(必须)
如果不适配暗黑模式有以下两种方法:
方法1:在Info.plist
中添加一栏:User Interface Style
: Light
,即可在应用内禁用暗黑模式,这种方法简单暴力。
方法1: 修改info.plist,即
UIUserInterfaceStyle
Light
方法2:
方法2: 修改代码
if (@available(iOS 13.0, *)) {
[UIApplication sharedApplication].keyWindow.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
在iOS 13
,为UIViewController
和UIView
扩展了一个新的API
,即overrideUserInterfaceStyle
,使用方法,官方文档大致是这么说的:
通过设置
overrideUserInterfaceStyle
属性以使该视图及其子视图具有特定的UIUserInterfaceStyle
。但如果想要获取当前的UIUserInterfaceStyle
,需要改用traitCollection.userInterfaceStyle
。
尽可能使用UIViewController
上的overrideUserInterfaceStyle
属性。仅在以下时间使用此属性:
- 在单个视图或小视图层次结构上局部使用特定样式。
- 您希望在整个
UIWindow
及其视图控制器和模态弹出的ViewController
上使用特定样式,且不希望强制更改整个应用程序具有样式。 (如果您确实希望整个应用程序具有某种样式,请不要使用它,而是在Info.plist
中设置UIUserInterfaceStyle
键。)当设置在普通的UIView上:
- 此属性仅影响此视图及其子视图的特征。
- 它不会影响任何视图控制器或其他视图控制器的子视图。
在UIWindow上设置时:
- 此属性会影响
rootViewController
,从而影响整个视图控制器和视图层次结构。- 它还会影响该
window
模态出来的界面。由此可见,
overrideUserInterfaceStyle
不仅会影响自己,还会影响自己的子视图,换做window
就会影响整个window
中的所有视图及视图控制器,包括模态跳转出来的视图控制器。
推送DeviceToken(必须)
之前:只需要将deviceToken(NSData类型)
转换成NSString
,然后替换掉多余的符号,代码如下:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [deviceToken description];
for (NSString *str in @[@" ", @"<", @">", @"-"]) {
token = [token stringByReplacingOccurrencesOfString: str withString:@""];
}
}
iOS 13:经过上面方法,deviceToken(NSData类型)
转换成NSString
,结果并不是我们想要的。
{length=32,bytes=0xf3381d274a14fa76f2531ce253cb00ea...7829824093258ea5}
参考友盟获取设备的 DeviceToken的做法如下:
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
if (![deviceToken isKindOfClass:[NSData class]]) return;
const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
NSLog(@"deviceToken:%@",hexToken);
}
私有方法KVC可能导致崩溃(必须)
注意⚠️:iOS 13
通过KVC
方式修改私有属性,有Crush
风险,谨慎使用!
在iOS 13
中部分方法属性不允许使用valueForKey
、setValue:forKey:
来获取或者设置私有属性,如果使用的话运行时会直接崩溃并提示以下崩溃信息:
-
获取UITextField的
_placeholderLabel
不会崩溃,但是获取_placeholderLabel
里的属性就会崩溃[textField setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"]; [textField setValue:[UIFont systemFontOfSize:20] forKeyPath:@"_placeholderLabel.font"];
错误提示:
'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug'
解决:
注意⚠️:建议第二种,第一种不确定会不会影响审核
1.去掉下划线,访问
placeholderLabel
[textField setValue:[UIColor blueColor] forKeyPath:@"placeholderLabel.textColor"]; [textField setValue:[UIFont systemFontOfSize:20] forKeyPath:@"placeholderLabel.font"];
2.使用
attributedPlaceholder
,定义富文本来达到我们需要的结果textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"输入" attributes:@{ NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont systemFontOfSize:20] }];
-
UISearchBar
一些属性修改崩溃1:
UITextField *textField = [searchBar valueForKey:@"_searchField"]; // 替代方案 1,使用 iOS 13 的新属性 searchTextField searchBar.searchTextField.placeholder = @"search"; // 替代方案 2,遍历获取指定类型的属性 - (UIView *)findViewWithClassName:(NSString *)className inView:(UIView *)view{ Class specificView = NSClassFromString(className); if ([view isKindOfClass:specificView]) { return view; } if (view.subviews.count > 0) { for (UIView *subView in view.subviews) { UIView *targetView = [self findViewWithClassName:className inView:subView]; if (targetView != nil) { return targetView; } } } return nil; } // 调用方法 UITextField *textField = [self findViewWithClassName:@"UITextField" inView:_searchBar]; //替代方案 3,去掉下划线 UITextField *textField = [searchBar valueForKey:@"searchField"];
崩溃2:
[searchBar setValue:@"取消" forKey:@"_cancelButtonText"];
1.同上
UIButton *cancelButton = [self findViewWithClassName:NSStringFromClass([UIButton class]) inView:searchBar]; [cancelButton setTitle:@"取消" forState:UIControlStateNormal];
2.去掉下划线
[searchBar setValue:@"取消" forKey:@"cancelButtonText"];
-
KVC
获取状态栏statusBar
会导致崩溃UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
UISearchBar黑线处理导致崩溃(必须)
以前我们为了处理搜索框的黑线问题,一般通过遍历searchBar
的subViews
,找到并删除UISearchBarBackground
。
for (UIView *view in _searchBar.subviews.lastObject.subviews) {
if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
[view removeFromSuperview];
break;
}
}
或者
[[[[ self.searchbar.subviews objectAtIndex : 0 ] subviews ] objectAtIndex : 0 ] removeFromSuperview ];
在iOS13
中会导致UI
渲染失败,然后直接崩溃,崩溃信息如下:
错误提示:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Missing or detached view for search bar layout'
解决:
-
设置
UISearchBarBackground
的layer.contents
为nil
。for (UIView *view in _searchBar.subviews.lastObject.subviews) { if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) { view.layer.contents = nil; break; } }
-
设置
UISearchBar
的背景图片为空:[_searchBar setBackgroundImage:[UIImage new]];
控制器的modalPresentationStyle默认值变了,即presentationController方法弹出默认交互改变(必须)
查阅了下UIModalPresentationStyle
枚举定义,发现iOS 13
新加了一个枚举值UIModalPresentationAutomatic
:
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) {
UIModalPresentationFullScreen = 0,
UIModalPresentationPageSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
UIModalPresentationFormSheet API_AVAILABLE(ios(3.2)) API_UNAVAILABLE(tvos),
UIModalPresentationCurrentContext API_AVAILABLE(ios(3.2)),
UIModalPresentationCustom API_AVAILABLE(ios(7.0)),
UIModalPresentationOverFullScreen API_AVAILABLE(ios(8.0)),
UIModalPresentationOverCurrentContext API_AVAILABLE(ios(8.0)),
UIModalPresentationPopover API_AVAILABLE(ios(8.0)) API_UNAVAILABLE(tvos),
UIModalPresentationBlurOverFullScreen API_AVAILABLE(tvos(11.0)) API_UNAVAILABLE(ios) API_UNAVAILABLE(watchos),
UIModalPresentationNone API_AVAILABLE(ios(7.0)) = -1,
UIModalPresentationAutomatic API_AVAILABLE(ios(13.0)) = -2,
};
并且直接修改了modalPresentationStyle
属性的默认值为UIModalPresentationAutomatic
:
/*
Defines the presentation style that will be used for this view controller when it is presented modally. Set this property on the view controller to be presented, not the presenter.
If this property has been set to UIModalPresentationAutomatic, reading it will always return a concrete presentation style. By default UIViewController resolves UIModalPresentationAutomatic to UIModalPresentationPageSheet, but system-provided subclasses may resolve UIModalPresentationAutomatic to other concrete presentation styles. Participation in the resolution of UIModalPresentationAutomatic is reserved for system-provided view controllers.
Defaults to UIModalPresentationAutomatic on iOS starting in iOS 13.0, and UIModalPresentationFullScreen on previous versions. Defaults to UIModalPresentationFullScreen on all other platforms.
*/
@property(nonatomic,assign) UIModalPresentationStyle modalPresentationStyle API_AVAILABLE(ios(3.2));
这样就会导致某些页面显示内容出现显示不全,如下图
如果显示效果不影响,那么就无需修改.
-
如果是单纯的一个页面需要修改:
self.modalPresentationStyle = UIModalPresentationOverFullScreen; 或者 - (UIModalPresentationStyle)modalPresentationStyle { return UIModalPresentationFullScreen; }
-
如果项目中有很多地方,那么一个一个改很麻烦,可以使用方法交换,判断一下
modalPresentationStyle
属性,并且进行修改。+ (void)load { Method carshMethod = class_getInstanceMethod([self class], @selector(presentViewController: animated: completion:)); Method newMethod = class_getInstanceMethod([self class], @selector(custompresentViewController: animated: completion:)); method_exchangeImplementations(carshMethod, newMethod); } - (void)custompresentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { if (@available(iOS 13.0, *)) { // iOS13以后style默认UIModalPresentationAutomatic,以前版本xcode没有这个枚举,所以只能写-2 if (viewControllerToPresent.modalPresentationStyle == -2 || viewControllerToPresent.modalPresentationStyle == UIModalPresentationPageSheet) { viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverFullScreen; } } [self custompresentViewController:viewControllerToPresent animated:flag completion:completion]; }
代码(放入项目即可)
注意⚠️:当modalPresentationStyle
是UIModalPresentationAutomatic
时,presentationController
是不会消失的。所以关闭模态窗口的时候,presentationController
的生命周期方法viewWillAppear:
和viewDidAppear:
都不会触发,如果里面写了相应方法就不会执行了。
UIStatusBarStyle(必须)
iOS 13
中,状态栏新增了一个样式:
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {
UIStatusBarStyleDefault = 0, // Automatically chooses light or dark content based on the user interface style
UIStatusBarStyleLightContent API_AVAILABLE(ios(7.0)) = 1, // Light content, for use on dark backgrounds
UIStatusBarStyleDarkContent API_AVAILABLE(ios(13.0)) = 3, // Dark content, for use on light backgrounds
UIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,
UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
} API_UNAVAILABLE(tvos);
在iOS 13
之前有两种状态,default
(黑色)和lightContent
(白色)
在iOS 13
有三种状态,default
(根据情况自动选择darkContent
和lightContent
), darkContent
和(黑色)lightContent
(白色)。
注意⚠️: 项目中以后修改状态栏颜色一律使用JWTools
,这样写的好处的万一以后有增加红色、绿色之类的,只需要增加一个枚举值,外面只需要设置好我想要的颜色,不需要关心style类型以及对应的颜色,只需要在这个方法里做对应的修改即可。
typedef NS_ENUM(NSUInteger, JWStatusBarTextColor)
{
JWStatusBarTextWhiteColor,
JWStatusBarTextBlackColor,
};
+ (void)statusBarTextColor:(JWStatusBarTextColor)statusBarTextColor
{
[self statusBarTextColor:statusBarTextColor animated:NO];
}
+ (void)statusBarTextColor:(JWStatusBarTextColor)statusBarTextColor animated:(BOOL)animated
{
switch (statusBarTextColor) {
case JWStatusBarTextBlackColor: {
if (@available(iOS 13.0, *)) {
// iOS13 以后 default会根据状态自动设置黑色还是白色, 需要用 UIStatusBarStyleDarkContent 设置黑色 低版本xcode没有这个枚举 只能用3
[[UIApplication sharedApplication] setStatusBarStyle:3];
} else {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
}
break;
}
case JWStatusBarTextWhiteColor: {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:animated];
break;
}
}
}
UIActivityIndicatorView loading菊花修改(必须)
iOS 13
之前的三种样式:
UIActivityIndicatorViewStyleGray
灰色
UIActivityIndicatorViewStyleWhite
白色、
UIActivityIndicatorViewStyleWhiteLarge
白色(大型)
iOS 13
废弃了以上三种样式,用以下两种样式代替:
UIActivityIndicatorViewStyleLarge
(大型)
UIActivityIndicatorViewStyleMedium
(中型)
iOS 13
通过color
属性设置颜色
[UIApplication sharedApplication].keyWindow废弃(必须)
@property(nullable, nonatomic,readonly) UIWindow *keyWindow API_DEPRECATED("Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes", ios(2.0, 13.0));
解决:
[[[UIApplication sharedApplication] windows] objectAtIndex:0]
UISearchDisplayController彻底弃用,导致崩溃(必须)
在iOS 8
之前,我们在UITableView
上添加搜索框需要使用UISearchBar + UISearchDisplayController
的组合方式。
在iOS 8
之后,苹果就已经推出了UISearchController
来代替这个组合方式。在iOS 13
中,如果还继续使用UISearchDisplayController
会直接导致崩溃,崩溃信息如下:
Terminating app due to uncaught exception 'NSGenericException', reason: 'UISearchDisplayController is no longer supported when linking against this version of iOS. Please migrate your application to UISearchController.'
解决:使用UISearchController
替换UISearchBar + UISearchDisplayController
UISegmentedControl 默认样式改变(必须)
默认样式变为白底黑字,如果设置修改过颜色的话,页面需要修改。
原本设置选中颜色的tintColor
已经失效,新增了selectedSegmentTintColor
属性用以修改选中的颜色。如果想保持原来的修改如下代码:
UISegmentedControl *segmentControl = [[UISegmentedControl alloc] initWithItems:@[@"asdasd", @"奥术大师多", @"123456"]];
segmentControl.frame = CGRectMake(30, 200, 300, 50);
[self.view addSubview:segmentControl];
UIColor *color = [UIColor redColor];
if (@available(iOS 13.0, *)) {
// 修改选中标题颜色
[segmentControl setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]} forState:UIControlStateSelected];
// 修改未选中标题颜色
[segmentControl setTitleTextAttributes:@{NSForegroundColorAttributeName : color} forState:UIControlStateNormal];
// 修改未选中背景颜色 color转image方法上面代码里有
[segmentControl setBackgroundImage:[self imageWithColor:[UIColor whiteColor]] forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// 修改选中背景颜色
[segmentControl setBackgroundImage:[self imageWithColor:color] forState:UIControlStateSelected barMetrics:UIBarMetricsDefault];
// 修改分割线颜色
[segmentControl setDividerImage:[self imageWithColor:color] forLeftSegmentState:UIControlStateSelected rightSegmentState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
// 设置描边
segmentControl.layer.borderWidth = 1;
segmentControl.layer.borderColor = color.CGColor;
// 设置圆角,区分iOS版本号时可以不写圆角
segmentControl.layer.cornerRadius = 5;
segmentControl.layer.masksToBounds = YES;
} else {
[segmentControl setTintColor:color];
}
UINavigationBar 设置按钮边距导致崩溃(必须)
从iOS 11
开始,UINavigationBar
使用了自动布局,左右两边的按钮到屏幕之间会有16
或20
的边距。
为了避免点击到间距的空白处没有响应,通常做法是:定义一个UINavigationBar
子类,重写layoutSubviews
方法,在此方法里遍历subviews
获取 _UINavigationBarContentView
,并将其layoutMargins
设置为UIEdgeInsetsZero
。
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"_UINavigationBarContentView"]) {
subview.layoutMargins = UIEdgeInsetsZero;
break;
}
}
}
这种做法在iOS 13
中会导致崩溃,崩溃信息如下:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Client error attempting to change layout margins of a private view'
解决:
使用设置frame
的方式,让_UINavigationBarContentView
向两边伸展,从而抵消两边的边距。
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView *subview in self.subviews) {
if ([NSStringFromClass([subview class]) containsString:@"_UINavigationBarContentView"]) {
if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
UIEdgeInsets margins = subview.layoutMargins;
subview.frame = CGRectMake(-margins.left, -margins.top, margins.left + margins.right + subview.frame.size.width, margins.top + margins.bottom + subview.frame.size.height);
} else {
subview.layoutMargins = UIEdgeInsetsZero;
}
break;
}
}
}
textfield.leftview(必须)
如下方式,直接给textfield.leftView
赋值一个UILabel
对象,他的宽高会被sizeToFit
,而不是创建时的值。
// left view label
UILabel *phoneLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 63, 50)];
phoneLabel.text = @"手机号";
phoneLabel.font = [UIFont systemFontOfSize:16];
// set textfield left view
self.textfieldName.leftView = phoneLabel;
如所看到,实际leftview
的width为59
,height为19
:
通过监听
leftView
的
frame
变化,发现是
layoutSubview
之后变化的。
解决方法:嵌套一个UIView
// label
UILabel *phoneLabel = [[UILabel alloc] init];
phoneLabel.text = @"手机号";
phoneLabel.font = [UIFont systemFontOfSize:16];
[phoneLabel sizeToFit];
phoneLabel.centerY = 50/2.f;
// left view
UIView *leftView = [[UIView alloc] initWithFrame:(CGRect){0, 0, 63, 50}];
[leftView addSubview:phoneLabel];
// set textfield left view
self.textfieldName.leftView = leftView;
调用navigationBar:shouldPopItem:
崩溃(必须)
点击导航栏返回的时候Crash
了,控制台输出提示:
Теrmіnаtіng арр due to uncaught exception' NSInternalInconsistencyException' ,
reason : ' Override of -navigationBar : shouldPopItem: returned YES after
manually popping a view controller ( navigat ionController=) '
因为我们工程里,基本上所有的Controller
是继承基类BaseViewController
并实现- (BOOL)naviBack:
方法,用于实现在用户点击返回和侧滑返回时,一些不能返回的特殊处理。
原理:通过实现UINavgationBar
的代理方法- (BOOL)navigationBar:shouldPopItem:
来做的控制:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPopItem:(UINavigationItem *)item {
// 默认可以返回
BOOL canGoBack = YES;
// BaseViewController定义协议方法判断能否能点击返回上一层
UIViewController *vc = self.viewControllers.lastObject;
if ([vc isKindOfClass:BaseViewController.class]) {
canGoBack = [(BaseViewController *)vc naviBack:nil];
}
if (canGoBack) {
[self popViewControllerAnimated:YES];
}
return canGoBack;
}
但是我实现的时候有 Return YES
啊!想了想,试着注释了[self popViewControllerAnimated:YES]
,发现没有崩溃了。但是在iOS 12
上,会发现控制器没有回到上一层。
修改方法:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar
shouldPopItem:(UINavigationItem *)item {
// 判断 iOS 版本低于13
BOOL bellow13 = !@available(iOS 13.0, *);
// 默认可以返回
BOOL canGoBack = YES;
// BaseViewController定义协议方法判断能否能点击返回上一层
UIViewController *vc = self.viewControllers.lastObject;
if ([vc isKindOfClass:BaseViewController.class]) {
canGoBack = [(BaseViewController *)vc naviBack:nil];
}
if (canGoBack && bellow13) {
// 如果低于13且可以返回,就执行popViewController
[self popViewControllerAnimated:YES];
}
return canGoBack;
}
新增蓝牙的权限申请(包含,必须)
iOS 13
以前,使用需要使用蓝牙时直接调用即可,不需要请求权限;在iOS 13
中,苹果将原来申请权限用的NSBluetoothPeripheralUsageDescription替换为NSBluetoothAlwaysUsageDescription。
For apps with a deployment target of iOS 13 and later, use NSBluetoothAlwaysUsageDescription instead.
如果继续在iOS 13
中使用旧的权限字段获取蓝牙权限就会导致崩溃,信息如下:
This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with a string value explaining to the user how the app uses this data.
如果将没有新字段的包提交审核,将会收到包含ITMS-90683
的邮件,并提示审核不通过。
解决:在info.plist
里增加相应蓝牙申请的权限字段。
NSBluetoothAlwaysUsageDescription
我们需要一直使用您的蓝牙 `
LaunchImage将被废弃(目前不需要,提示必须)
LaunchScreen.storyboard适配
UILaunchImages has been deprecated; use Xcode launch storyboards instead. For more information on how to construct and format your launch storyboard, seeLaunch Screen
iOS 8
之前我们使用LaunchImage
来设置启动图,但是每当苹果推出新的屏幕尺寸的设备,我们需要在assets
里面加入相应尺寸的启动图,这就显得比较呆板。因此引入了LaunchScreen
,LaunchScreen
是支持AutoLayout+SizeClass
的,可以直接在Storyboard
上设置启动界面样式,这样可以适配各种屏幕。
注意⚠️:
- 从
2020年4月
开始,所有使⽤iOS 13 SDK
的App
将必须提供LaunchScreen
,LaunchImage
将被废弃,所有支持iOS 13
的App
必须提供LaunchScreen.storyboard
,否则将无法提交到App Store
进行审批。
UIWebView将被废弃(目前不需要,提示必须)
UIKIT_EXTERN API_DEPRECATED("No longer supported; please adopt WKWebView.", ios(2.0, 12.0)) API_UNAVAILABLE(tvos, macos) @interface UIWebView : UIView
目前,如果开发者将包含UIWebView api
的应用更新上传到App Store
审核后,其将会收到包含ITMS-90809
信息的回复邮件,提示你在下一次提交时将应用中UIWebView
的api
移除。
解决:用
WKWebView
替代
UIWebView
,确保所有
UIWebView
的
api
都要移除,如果需要适配
iOS 7
的可以通过
openURL
的方式在
Safari
打开。
SceneDelegate(Xcode 11 创建的工程在低版本设备上运行黑屏)
In iOS 13 and later, use UISceneDelegate objects to respond to life-cycle events in a scene-based app.
In iOS 12 and earlier, use the UIApplicationDelegate object to respond to life-cycle events.
-
基于
Xcode11
新建的工程,如何展示?- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; ViewController *view = [[ViewController alloc]init]; UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:view]; self.window.rootViewController = nav; // [self.window makeKeyAndVisible]; }
-->
-
基于
Xcode11
新建的工程,在iOS 13
以前的版本不展示window
,如何解决?在
AppDelegate.h
中添加UIWindow
#import
@interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow * window; @end 如果不使用场景功能,在
info.plist
文件中删除Application Scene Manifast
- 注释
AppDelegate
中UISceneSession lifecycle
Sign in with Apple
如果你的应用有第三方登录,那你需要加上sign in with apple
。这是iOS 13
新增的功能,是苹果推出一种在App
和网站上快速、便捷登录的方式。关于应用是否要求接入此登录方式,如下
Sign In with Apple will be available for beta testing this summer. It will be required as an option for users in apps that support third-party sign-in when it is commercially available later this year.
Apps that exclusively use a third-party or social login service (such as Facebook Login, Google Sign-In, Sign in with Twitter, Sign In with LinkedIn, Login with Amazon, or WeChat Login) to set up or authenticate the user’s primary account with the app must also offer Sign in with Apple as an equivalent option.
如果应用使用了第三方或社交账号登录服务(如Facebook
、Google
、Twitter
、LinkedIn
、Amazon
、WeChat
等)来设置或验证用户的主账号,就必须把Sign In With Apple
作为同等的选项添加到应用上。如果是下面这些类型的应用则不需要添加:
- 仅仅使用公司内部账号来注册和登录的应用;
- 要求用户使用现有的教育或企业账号进行登录的教育、企业或商务类型的应用;
- 使用政府或业界支持的公民身份识别系统或电子标识对用户进行身份验证的应用;
- 特定第三方服务的应用,用户需要直接登录其邮箱、社交媒体或其他第三方帐户才能访问其内容。
注意⚠️:
Starting today, new apps submitted to the App Store must follow these guidelines. Existing apps and app updates must follow them by April 2020.
2019年9月12日
起,提交到App Store
的新应用必须按照应用审核指南中的标准进行接入;现有应用和应用更新必须也在2020年4月
前完成接入。
首页frame莫名下移20像素问题
ContainerViewController
的xib size
为Inferred view
的y
会莫名变成20
把 size 改成FreeForm
即可
MPMoviePlayerController 被废弃
'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
iOS 9
之前可以使用MediaPlayer.framework
中的MPMoviePlayerController
来播放视频,但是在iOS 9
开始被弃用,在iOS 13
中继续使用的话会直接抛出异常:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'MPMoviePlayerController is no longer available. Use AVPlayerViewController in AVKit.'
解决:使用AVFoundation
里的AVPlayer
作为视频播放控件。
UIScrollView
滚动条异常偏移
解决:
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
self.automaticallyAdjustsScrollIndicatorInsets = NO;
}
#endif
H5的适配
参考以下链接:参考链接
NSAttributedString
优化
对于UILabel
、UITextField
、UITextView
,在设置NSAttributedString
时也要考虑适配Dark Mode
,否则在切换模式时会与背景色融合,造成不好的体验
不建议的做法:
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
推荐的做法:
// 添加一个NSForegroundColorAttributeName属性
NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor labelColor]};
NSAttributedString *str = [[NSAttributedString alloc] initWithString:@"富文本文案" attributes:dic];
WKWebView中测量页面内容高度的方式变更
iOS 13
以前 document.body.scrollHeight
, iOS 13
中 document.documentElement.scrollHeight
两者相差55
应该是浏览器定义高度变了
UITabBarButton不同状态下结构不同
在 iOS 13 中,UITabBarButton
的控件结构会随着其选中状态的变化而变化,主要体现为 UITabBarSwappableImageView
和UITabBarButtonLabel
的位置变化。在选中时和以前一样,是 UITabBarButton
的子控件。而在未选中状态下放到了UIVisualEffectView
的 _UIVisualEffectContentView
里面。具体可以看下图的对比:
我们在自定义UITabBar
时,通常会遍历UITabBarButton
的子控件获取 UITabBarSwappableImageView
,比如自定义红点时添加到这个ImageView
的右上角,这在iOS 13
中可能就会导致异常。
问题1:UITabBar
红点偏移
问题2:UITabBar
红点不选中显示颜色不对。
原因:如果之前有通过UITabBar
上图片位置来设置红点位置,在iOS13
上会发现显示位置都在最左边去了。遍历UITabBarButton
的subViews
发现只有在UITabBar
选中状态下才能取到UITabBarSwappableImageView
。
问题1解决办法:修改为通过UITabBarButton
的位置来设置红点的frame
问题2解决办法:把红点添加到UITabBarButton
上,位置再根据UITabBarSwappableImageView
调整即可
CNCopyCurrentNetworkInfo 使用要求更严格
从 iOS 12
开始,CNCopyCurrentNetworkInfo
函数需要开启Access WiFi Information
的功能后才会返回正确的值。在iOS 13
中,这个函数的使用要求变得更严格,根据CNCopyCurrentNetworkInfo
文档说明,应用还需要符合下列三项条件中的至少一项才能得到正确的值:
- 使用
Core Location
的应用, 并获得定位服务权限。 - 使用
NEHotspotConfiguration
来配置WiFi
网络的应用。 - 目前正处于启用状态的
VPN
应用。
参考:
iOS 13 适配要点总结
iOS 13 适配