iOS13适配(更新中)

对于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);
    }
}];

资源文件

  1. 打开现有Assets.xcassets(或创建Assets文件)。
  2. 新建一个图片资源文件(或者颜色资源文件、或者其他资源文件)。
  3. 在不同模式下添加相应资源文件。
添加资源文件
  1. 代码默认执行时,就可以正常通过名字使用了,系统会根据当前模式自动获取对应的资源文件。

注意⚠️:同一工程内多个Assets文件在打包后,就会生成一个Assets.car文件,所以要保证Assets内资源文件的名字不能相同。

模拟器开关暗黑模式

注意:如果不适配暗黑模式(必须)

如果不适配暗黑模式有以下两种方法:

方法1:Info.plist中添加一栏:User Interface Style : Light,即可在应用内禁用暗黑模式,这种方法简单暴力。

方法1: 修改info.plist,即
<key>UIUserInterfaceStyle</key>
<string>Light</string>

修改info.plist

方法2:

方法2: 修改代码
if (@available(iOS 13.0, *)) {
    [UIApplication sharedApplication].keyWindow.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
} 

iOS 13,为UIViewControllerUIView扩展了一个新的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中部分方法属性不允许使用valueForKeysetValue:forKey:来获取或者设置私有属性,如果使用的话运行时会直接崩溃并提示以下崩溃信息:

  1. 获取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]
         }];
    
  2. 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"];
    
  3. KVC获取状态栏statusBar会导致崩溃

     UIView *statusBar = [[[UIApplication sharedApplication] valueForKey:@"statusBarWindow"] valueForKey:@"statusBar"];
    

UISearchBar黑线处理导致崩溃(必须)

以前我们为了处理搜索框的黑线问题,一般通过遍历searchBarsubViews,找到并删除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'

解决:

  1. 设置UISearchBarBackgroundlayer.contentsnil

     for (UIView *view in _searchBar.subviews.lastObject.subviews) {
         if ([view isKindOfClass:NSClassFromString(@"UISearchBarBackground")]) {
             view.layer.contents = nil;
             break;
         }
     } 
    
  2. 设置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));

这样就会导致某些页面显示内容出现显示不全,如下图

显示不全

如果显示效果不影响,那么就无需修改.

  1. 如果是单纯的一个页面需要修改:

     self.modalPresentationStyle = UIModalPresentationOverFullScreen;
     或者
     - (UIModalPresentationStyle)modalPresentationStyle {
         return UIModalPresentationFullScreen;
     }       
    
  2. 如果项目中有很多地方,那么一个一个改很麻烦,可以使用方法交换,判断一下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];
     }
    

代码(放入项目即可)

注意⚠️:modalPresentationStyleUIModalPresentationAutomatic时,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(根据情况自动选择darkContentlightContent), 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使用了自动布局,左右两边的按钮到屏幕之间会有1620的边距。

为了避免点击到间距的空白处没有响应,通常做法是:定义一个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

通过监听leftViewframe变化,发现是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=<MHCRNavgationController : 0x106039400>) '

因为我们工程里,基本上所有的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里增加相应蓝牙申请的权限字段。

<key>NSBluetoothAlwaysUsageDescription</key> 
<string>我们需要一直使用您的蓝牙</string>`

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里面加入相应尺寸的启动图,这就显得比较呆板。因此引入了LaunchScreenLaunchScreen是支持AutoLayout+SizeClass的,可以直接在Storyboard上设置启动界面样式,这样可以适配各种屏幕。

注意⚠️:

  1. 2020年4月开始,所有使⽤iOS 13 SDKApp将必须提供LaunchScreenLaunchImage将被废弃,所有支持iOS 13App必须提供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 <NSCoding, UIScrollViewDelegate>

目前,如果开发者将包含UIWebView api的应用更新上传到App Store审核后,其将会收到包含ITMS-90809信息的回复邮件,提示你在下一次提交时将应用中UIWebViewapi移除。

ITMS-90809

解决:WKWebView替代UIWebView,确保所有UIWebViewapi都要移除,如果需要适配 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.

SceneDelegate
  1. 基于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];
     }
    

-->

  1. 基于Xcode11新建的工程,在iOS 13以前的版本不展示window,如何解决?

    AppDelegate.h中添加UIWindow

     #import <UIKit/UIKit.h>
     
     @interface AppDelegate : UIResponder <UIApplicationDelegate>
     
     @property (strong, nonatomic) UIWindow * window;
     
     @end
    
  2. 如果不使用场景功能,在info.plist文件中删除Application Scene Manifast

删除Application Scene Manifas
  1. 注释AppDelegateUISceneSession lifecycle
注释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.

如果应用使用了第三方或社交账号登录服务(如FacebookGoogleTwitterLinkedInAmazonWeChat等)来设置或验证用户的主账号,就必须把Sign In With Apple作为同等的选项添加到应用上。如果是下面这些类型的应用则不需要添加:

  1. 仅仅使用公司内部账号来注册和登录的应用;
  2. 要求用户使用现有的教育或企业账号进行登录的教育、企业或商务类型的应用;
  3. 使用政府或业界支持的公民身份识别系统或电子标识对用户进行身份验证的应用;
  4. 特定第三方服务的应用,用户需要直接登录其邮箱、社交媒体或其他第三方帐户才能访问其内容。

注意⚠️:

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像素问题

ContainerViewControllerxib sizeInferred viewy会莫名变成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优化

对于UILabelUITextFieldUITextView,在设置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.scrollHeightiOS 13document.documentElement.scrollHeight两者相差55 应该是浏览器定义高度变了

UITabBarButton不同状态下结构不同

在 iOS 13 中,UITabBarButton的控件结构会随着其选中状态的变化而变化,主要体现为 UITabBarSwappableImageViewUITabBarButtonLabel的位置变化。在选中时和以前一样,是 UITabBarButton的子控件。而在未选中状态下放到了UIVisualEffectView_UIVisualEffectContentView里面。具体可以看下图的对比:

UITabBarButton结构

我们在自定义UITabBar时,通常会遍历UITabBarButton的子控件获取 UITabBarSwappableImageView,比如自定义红点时添加到这个ImageView的右上角,这在iOS 13中可能就会导致异常。

问题1:UITabBar红点偏移

问题2:UITabBar红点不选中显示颜色不对。

原因:如果之前有通过UITabBar上图片位置来设置红点位置,在iOS13上会发现显示位置都在最左边去了。遍历UITabBarButtonsubViews发现只有在UITabBar选中状态下才能取到UITabBarSwappableImageView

问题1解决办法:修改为通过UITabBarButton的位置来设置红点的frame

问题2解决办法:把红点添加到UITabBarButton 上,位置再根据UITabBarSwappableImageView调整即可

CNCopyCurrentNetworkInfo 使用要求更严格

iOS 12开始,CNCopyCurrentNetworkInfo 函数需要开启Access WiFi Information的功能后才会返回正确的值。在iOS 13 中,这个函数的使用要求变得更严格,根据CNCopyCurrentNetworkInfo文档说明,应用还需要符合下列三项条件中的至少一项才能得到正确的值:

  1. 使用Core Location的应用, 并获得定位服务权限。
  2. 使用 NEHotspotConfiguration 来配置 WiFi 网络的应用。
  3. 目前正处于启用状态的 VPN 应用。

参考:

iOS 13 适配要点总结

iOS 13 适配

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容