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

随着iOS 13的发布,公司的项目也势必要着手适配了。现汇总一下iOS 13的各种坑

1. KVC访问私有属性

这次iOS 13系统升级,影响范围最广的应属KVC访问修改私有属性了,直接禁止开发者获取或直接设置私有属性。而KVC的初衷是允许开发者通过Key名直接访问修改对象的属性值,为其中最典型的 UITextField 的 _placeholderLabel、UISearchBar 的 _searchField。

造成影响:在iOS 13下App闪退

错误代码:

// 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

textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"placeholder" attributes:@{NSForegroundColorAttributeName: [UIColor darkGrayColor],NSFontAttributeName: [UIFont systemFontOfSize:13]}];

因此,可以为UITextFeild创建Category,专门用于处理修改placeHolder属性提供方法

#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 的通过富文本方法修改属性。

#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 方法交换来实现。

#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 状态。

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

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

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

iOS 13之后获取结果:

微信公众号搜索 “ 程序精选 ” ,选择关注!
精选程序员所需精品干货内容!