아이폰 OS - 심지어 지뢰 엔지니어 (13)의 아이폰 OS 적응

아이폰 OS 13 지원 적응 모델

최신 아이폰 (11), 아이폰 프로 (11)와 아이폰 (11) 프로 맥스

아이폰 X, 아이폰 XR, 아이폰 XS, 아이폰 XS 최대
아이폰 8, 아이폰 8 플러스
아이폰 7, 아이폰 7 플러스
아이폰 기가, 아이폰 기가 플러스
아이폰 SE
아이팟 터치 (第七代)

A, UI 레벨

참고 :이 지점에 적응해야 "(필수)"마크, 이것은 어떤 점 몇 가지 개인 항목을 참조 할 수 있어야 있어야합니다, 당신은 무시할 수

1.Dark 모드

디아블로 모드의 아이폰 OS 13 출시, UIKit 다른 API를 색상과 색상 모드에 맞게 새로운 시스템을 제공, 재료의 xcassets 적응이 조정 된 공식 특정 적응 볼 수 : 구현 다크 모드를 ON 아이폰 OS .
적응 방식 :

 
영상

참고 링크 :
https://mp.weixin.qq.com/s/qliFbqRdkkE30vslojfJCA
https://juejin.im/post/5cf6276be51d455a68490b26

애플과 2.Sign에서

애플에서가 사용할 수 있습니다 가입 이 여름에 베타 테스트. 그것은이 올해 상업적으로 사용할 수있는 경우에 로그인 타사 지원 응용 프로그램에서 사용자를위한 옵션으로 요구 될 것이다.
당신은 타사 응용 프로그램 지원을 사용하여 로그인하는 경우 애플에 소개 로그인 : 당신은 애플의 새로운 로그인을 추가해야합니다. 현재는 뉴스 및 업데이트 요구 사항 플러스 공식 자료에서 언급 한 애플은 출시 날짜는 아직 결정한다.

3. 기본 모드는 상호 변경을 팝업 (예정)

이 열거 치의에서 아이폰 OS (13)는 직접 presentViewController 시차 효과는 기본 쇠퇴를 반환하는 것입니다, 그대로 다음보기를 열 수 있도록, 모달 팝업 기본값이됩니다.

 
영상

iOS13에서 여전히 할 수있는 그것을 전체 화면 팝업, 필요성 스타일 UI의 용도를 결정하는

4.UISegmentedControl 기본 스타일 변경 (필수)

기본 스타일은 수정 된 색상을 설정하면, 다음 페이지를 수정해야, 검은 색과 흰색이된다.

 
영상

원래하려면 tintColor가 선택한 색상의 속성을 수정하기 위해 추가 selectedSegmentTintColor 실패한 색상을 선택하도록 설정.
웹 콘텐츠 어댑터

5.h5 적응, 참조 링크 :

https://blog.csdn.net/u012413955/article/details/92198556

둘째, 코드 레벨

1. KVC 개인 방법은 허용되지 않습니다 (필수)

forKey : 아이폰 OS 13에서 더 이상 valueForKey, setValue의 사용이 허용되지 않는 다른 방법을 컴파일 할 수 있지만, 취득 또는 개인 속성을 설정하지만, 런타임에 직접에 대한 충돌 정보와 팁을 충돌합니다 :

// 使用的私有方法
[_textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

 

// 崩溃提示信息
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Access to UITextField's _placeholderLabel ivar is prohibited. This is an application bug' 

解决方案一:使用其他方法:(建议使用此种方法,因为第二种方法不知道能否过审)

// 替换的方案
_textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"输入"attributes:@{NSForegroundColorAttributeName: [UIColor redColor]}]; 

  

 

解决方案二:去掉keypath中的“_”
如果需要修改UISearchBar的placeholder,需要获取其searchTextField,可用category实现:

@implementation UISearchBar (SearchTextField)

- (UITextField *)atu_searchTextField{
    if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) {
        //判断xcode版本
#ifdef __IPHONE_13_0
        return self.searchTextField;
#else
        return [self valueForKey:@"searchTextField"];
#endif
    }

    //尝试过遍历subviews来找到,但是subviews中并不包含searchField!没有找到更好的办法
    return [self valueForKey:@"searchField"];
}

@end

  

如果有哪位大大有更好的办法,请告知

2.推送的 deviceToken 获取到的格式发生变化(必须)

原本可以直接将 NSData 类型的 deviceToken 转换成 NSString 字符串,然后替换掉多余的符号即可:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString *token = [deviceToken description];
    for (NSString *symbol in @[@" ", @"<", @">", @"-"]) {
        token = [token stringByReplacingOccurrencesOfString:symbol withString:@""];
    }
    NSLog(@"deviceToken:%@", token);
}

  


在 iOS 13 中,这种方法已经失效,NSData类型的 deviceToken 转换成的字符串变成了:

{length = 32, bytes = 0xd7f9fe34 69be14d1 fa51be22 329ac80d ... 5ad13017 b8ad0736 } 

需要进行一次数据格式处理,参考友盟的做法,可以适配新旧系统,获取方式如下:

#include <arpa/inet.h>
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    if (![deviceToken isKindOfClass:[NSData class]]) return;
    const unsigned *tokenBytes = [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);
}

  

3.UISearchBar 黑线处理导致崩溃

之前为了处理搜索框的黑线问题,通常会遍历 searchBar 的 subViews,找到并删除 UISearchBarBackground,在 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 removeFromSuperview]; view.layer.contents = nil; break; } } 

4.使用 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.' 

另外说一下,在 iOS 13 中终于可以获取直接获取搜索的文本框:

_searchBar.searchTextField.text = @“search";

 

5.模态弹出默认交互改变(必须)

如果需要做成全屏显示的界面,需要手动设置弹出样式:

- (UIModalPresentationStyle)modalPresentationStyle {
    return UIModalPresentationFullScreen;
} 
//或者在present之前:
ctr.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:ctr animated:animated completion:completion];

  

如果项目里面有大量的位置使用了presentViewController,然后你不想每个位置都显式的手动修改,推荐这篇文章https://juejin.im/post/5d5f96866fb9a06b0517f78c
里面的方法,原理是:

通过runtime修改ctr.modalPresentationStyle的默认值为UIModalPresentationFullScreen(原默认值为UIModalPresentationPageSheet)如果需要禁止自动修改默认值
ctr.LL_automaticallySetModalPresentationStyle = NO;

  

当然,如果有精力,不太多的话还是建议老老实实的一个个手动修改,以免以后官方api再次变动。

有一点注意的是,ctr的生命周期方法调用情况会改变,假设有a,b两个ctr,在a中present出b:

全屏present时(UIModalPresentationFullScreen)的方法调用顺序:

a---viewWillDisappear:
b---viewWillAppear:
b---viewDidAppear:
a---viewDidDisappear:

dissmiss时的方法调用顺序:

b---viewWillDisappear:
a---viewWillAppear:
a---viewDidAppear:
b---viewDidDisappear:

非全屏presnet时(UIModalPresentationPageSheet)的方法调用顺序:

b---viewWillAppear:
b---viewDidAppear:

dissmiss时的方法调用顺序:

b---viewWillDisappear:
b---viewDidDisappear:

可以看出,以UIModalPresentationPageSheet的方式来present/dismiss时,分别少调用了a的两个方法,如果之前在这个位置有相关的逻辑代码,比如网络请求,UI刷新,要注意

6.MPMoviePlayerController 被弃用

在 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。

7.LaunchImage 被弃用(必须)

iOS 8 之前我们是在LaunchImage 来设置启动图,但是随着苹果设备尺寸越来越多,我们需要在对应的 aseets 里面放入所有尺寸的启动图,这是非常繁琐的一个步骤。因此在 iOS 8 苹果引入了 LaunchScreen.storyboard,支持界面布局用的 AutoLayout + SizeClass ,可以很方便适配各种屏幕。
需要注意的是,苹果在 Modernizing Your UI for iOS 13 section 中提到,从2020年4月开始,所有支持 iOS 13 的 App 必须提供 LaunchScreen.storyboard,否则将无法提交到 App Store 进行审批。

8.Xcode 11 创建的工程在低版本设备上运行黑屏

使用 Xcode 11 创建的工程,运行设备选择 iOS 13.0 以下的设备,运行应用时会出现黑屏。这是因为 Xcode 11 默认是会创建通过 UIScene 管理多个 UIWindow 的应用,工程中除了 AppDelegate 外会多一个 SceneDelegate.

 
image

这是为了 iPadOS 的多进程准备的,也就是说 UIWindow 不再是 UIApplication 中管理。但是旧版本根本没有 UIScene,因此解决方案就是在 AppDelegate 的头文件加上:

@property (strong, nonatomic) UIWindow *window; 

9.使用 @available 导致旧版本 Xcode 编译出错。(必须)

在 Xcode 11 的 SDK 工程的代码里面使用了 @available 判断当前系统版本,打出来的包放在 Xcode 10 中编译,会出现一下错误:

Undefine symbols for architecture i386:
    "__isPlatformVersionAtLeast", referenced from: ... ld: symbol(s) not found for architecture i386 

从错误信息来看,是 __isPlatformVersionAtLeast 方法没有具体的实现,但是工程里根本没有这个方法。实际测试无论在哪里使用@available ,并使用 Xcode 11 打包成动态库或静态库,把打包的库添加到 Xcode 10 中编译都会出现这个错误,因此可以判断是 iOS 13 的 @available 的实现中使用了新的 api。如果你的 SDK 需要适配旧版本的 Xcode,那么需要避开此方法,通过获取系统版本来进行判断:

if ([UIDevice currentDevice].systemVersion.floatValue >= 13.0) { ... } 

另外,在 Xcode 10 上打开 SDK 工程也应该可以正常编译,这就需要加上编译宏进行处理:

#ifndef __IPHONE_13_0
#define __IPHONE_13_0 130000
#endif #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 ... #endif 

10.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:

 
image

解决方法:嵌套一个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;

  

11.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];

  

12.TabBar红点偏移

如果之前有通过TabBar上图片位置来设置红点位置,在iOS13上会发现显示位置都在最左边去了。遍历UITabBarButton的subViews发现只有在TabBar选中状态下才能取到UITabBarSwappableImageView,解决办法是修改为通过UITabBarButton的位置来设置红点的frame

13.废弃UIWebView(必须)

UIWebView在12.0就已经被废弃,部分APP使用webview时, 审核被拒

14.WKWebView 中测量页面内容高度的方式变更

iOS 13以前 document.body.scrollHeight iOS 13中 document.documentElement.scrollHeight 两者相差55 应该是浏览器定义高度变了

15.使用MJExtension 中处理NSNull的不同(必须)

这个直接会导致Crash的在将服务端数据字典转换为模型时,如果遇到服务端给的数据为NSNull时, mj_JSONObject,其中 class_copyPropertyList方法得到的属性里,多了一种EFSQLBinding类型的东西,而且属性数量也不准确, 那就没办法了, 我只能改写这个方法了,这个组件没有更新的情况下,写了一个方法swizzling掉把当遇到 NSNull时,直接转为nil了。

有人问这个方法怎么实现,其实目前我们项目在ios13下面还没遇到这种情况,发一个之前项目处理null的方法,在项目里面写一个NSObject的分类,添加下面的方法就可以了。大家请根据项目情况、数据格式修改下这个方法,MJ库里面自己会进行替换的:

- (id)mj_newValueFromOldValue:(id)oldValue property:(MJProperty *)property {
    //为了解决json字符串先赋值给oc字典后,类型转换crash问题,如:
    //json->oldValue:0
    //model中值为NSString类型
    //如果先将json转为dic,dic中对应value值为NSNumber类型,则向oldValue发送isEqualToString消息会crash
    id tempValue = oldValue;

    if ([property.type.code isEqualToString:@"NSString"]) {
        tempValue = [NSString stringWithFormat:@"%@", tempValue];

        if ([tempValue isKindOfClass:[NSNull class]] || tempValue == nil || [tempValue isEqual:[NSNull null]] ||  [tempValue isEqualToString:@"(null)"] ||  [tempValue isEqualToString:@"(\n)"] ) {
            return @"";
        }
    }
    if ([property.type.code isEqualToString:@"NSNumber"]) {
//        tempValue = [NSNumber numberWithFloat:[tempValue floatValue]];

        if ([tempValue isKindOfClass:[NSNull class]] || tempValue == nil || [tempValue isEqual:[NSNull null]] ||  [tempValue isEqualToString:@"(null)"] ||  [tempValue isEqualToString:@"(\n)"] ) {
            return @0;
        }
    }

    return tempValue;
}

  

16.StatusBar 与之前版本不同(必须)

之前 Status Bar 有两种状态,default 和 lightContent
现在 Status Bar 有三种状态,default, darkContent 和 lightContent
现在的 darkContent 对应之前的 default,现在的 default 会根据情况自动选择 darkContent 和 lightContent

17.UIActivityIndicatorView(必须)

之前的 UIActivityIndicatorView 有三种 style 分别为 whiteLarge, white 和 gray,现在全部废弃。
增加两种 style 分别为 medium 和 large,指示器颜色用 color 属性修改。

18.蓝牙权限需要申请

CBCentralManager,iOS13以前,使用蓝牙时可以直接用,不会出现权限提示,iOS13后,再使用就会提示了。 在info.plist里增加

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

  

在iOS13中,蓝牙变成了和位置,通知服务等同样的可以针对单个app授权的服务。

- (NSString*) getWifiSsid {
  if (@available(iOS 13.0, *)) {
    //用户明确拒绝,可以弹窗提示用户到设置中手动打开权限
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
      NSLog(@"User has explicitly denied authorization for this application, or location services are disabled in Settings.");
      //使用下面接口可以打开当前应用的设置页面
      //[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
      return nil;
    }
    CLLocationManager* cllocation = [[CLLocationManager alloc] init];
    if(![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined){
      //弹框提示用户是否开启位置权限
      [cllocation requestWhenInUseAuthorization];
      usleep(50);
      //递归等待用户选选择
      return [self getWifiSsidWithCallback:callback];
    }
  }
  NSString *wifiName = nil;
  CFArrayRef wifiInterfaces = CNCopySupportedInterfaces();
  if (!wifiInterfaces) {
    return nil;
  }
  NSArray *interfaces = (__bridge NSArray *)wifiInterfaces;
  for (NSString *interfaceName in interfaces) {
    CFDictionaryRef dictRef = CNCopyCurrentNetworkInfo((__bridge CFStringRef)(interfaceName));

    if (dictRef) {
      NSDictionary *networkInfo = (__bridge NSDictionary *)dictRef;
      NSLog(@"network info -> %@", networkInfo);
      wifiName = [networkInfo objectForKey:(__bridge NSString *)kCNNetworkInfoKeySSID];
      CFRelease(dictRef);
    }
  }
  CFRelease(wifiInterfaces);
  return wifiName;
}

  

19.CNCopyCurrentNetworkInfo

iOS13 以后只有开启了 Access WiFi Information capability,才能获取到 SSID 和 BSSID wi-fi or wlan 相关使用变更
最近收到了苹果的邮件,说获取WiFi SSID的接口CNCopyCurrentNetworkInfo 不再返回SSID的值。不仔细看还真会被吓一跳,对物联网的相关APP简直是炸弹。仔细看邮件还好说明了可以先获取用户位置权限才能返回SSID。
注意:目本身已经打开位置权限,则可以直接获取

参考链接,如有侵权,请告知,

https://github.com/ChenYilong/iOS13AdaptationTips/issues
iOS 13 适配
iOS13适配
适配 iOS13
Xcode11 和 iOS13 适配
iOS13 UI & 功能适配
解决Xcode11-beta版本新创建iOS工程低版本黑屏的问题
Modernizing Your UI for iOS13

转载:https://www.jianshu.com/p/8183d086b931



추천

출처www.cnblogs.com/baitongtong/p/11940639.html