iOS14和Xcode12踩坑

今年整体适配强度不大。

权限相关

如果设备开启了本地的服务,会弹出允许本地网络的权限提示框。
在Info.plist中增加隐私配置

1
2
3
4
5
Privacy - Local Network Usage Description
- 使用本地网络进行数据请求访问,作为视频的预缓存

Bonjour services
- _http._tcp

如果不加该配置,提审会被拒。

模拟器报错

1
building for iOS Simulator, but linking in object file built for iOS, xxxx for architecture arm64

原因:

  • Xcode12以前,我们通过Valid Architectures 选项来配置支持的架构。
  • Xcode12中, Valid Architectures这一选项被删掉了,改变成了VALID_ARCHS

Xcode11中,x86_64的架构支持是默认支持的,就算不写也支持。但Xcode12的时候,苹果将要推出了ARM架构的Mac,看来全面采用ARM架构已经成为苹果的趋势了,这使得Xcode其实就没必要再对x86_64架构默认支持。故需要手动添加上才能跑模拟器。

解决方案:
VALID_ARCHS选项上增加x86_64的架构配置

如果Cocoapods管理的组件,需要支持模拟器
需要手动设置User-DefinedVALID_ARCHS

微信 Pasted from QQ

打开某款App,频繁提示** Pasted from ***

苹果强化了对系统剪切板内容访问的提示,适度减少访问频次吧。不过现在是大家都玩口令的年代,这很难控制住…

UIPageControl 子视图层级变更 及 新增 API

iOS14中,苹果对UIPageControl控件进行了调整,定制性更强了。可以通过官方的API设置图片了,而且也有几个样式供我们选择。

注意点

1
2
[_pageControl setValue:[UIImage imageNamed:@"xxx"] forKeyPath:@"pageImage"];
[_pageControl setValue:[UIImage imageNamed:@"xxx"] forKeyPath:@"currentPageImage"];

上面代码在iOS14中,会出现崩溃。跟去年UITextFlied设置_placeholderLabel.textColor.一样抓狂的感觉。

如果封装SDK,尽可能的不要给系统UI控件通过KVC赋值和访问私有属性。

UITableViewCell 层级调整

自定义子视图,曾经可以添加在selfself.contentView上均可。 以后需要注意了,必须按照苹果的规范。

1
2
// Custom subviews should be added to the content view.
@property (nonatomic, readonly, strong) UIView *contentView;

影响: 不添加在 contentView 上的子视图,存在点击事件无法响应以及视图被系统默认高亮效果覆盖的问题。

IDFA访问权限和无线局域网地址调整

  • iOS14之前,IDFA的开关是统一管理的,开启关闭和还原都是针对所有App,但是好歹苹果还是默认开启的,如果要关闭,需要用户自己去设置页面关闭。WIFI地址是所有WIFI共用一个设备地址
  • 但是iOS14出来后,Apple将IDFA进一步隐私强化,每个App将需要自己申请用户权限来获取IDFA,Apple不再默认开启,用户可以在设置->隐私->Tracking或者App的权限管理页面来设置。WIFI地址目前是每个网络都配一个虚拟地址

归因真的是越来越难了

如何应对

  1. 在Info.plist中增加隐私配置Privacy - Tracking Usage Description
  2. 引入AppTrackingTransparency框架,调用ATTrackingManager.requestTrackingAuthorization方法来请求权限。
  3. 权限开启后,仍然使用ASIdentifierManager.shared().advertisingIdentifier.uuidString来获取IDFA。

在使用时会进行权限访问的申请,如果拒绝,将是一串无意义的0。

影响

  1. 多渠道推广时很难有效归因,从而分辨是哪个平台买量来的用户。
  2. 精准投放范围更小,转化成本上升。
  3. 三方归因平台话语权更强,因为他们可以自定义统一的标识符来供合作的广告主们使用,平台越大,标识符价值越高。
  4. 一旦用户单独关闭,推广时也无法区分老用户了,用户相当于进了黑盒,不过可以通过技术手段处理。
  5. 进一步提高了用户隐私权,IDFA开启率将会降低。尤其是亚洲国家,默认开启率很高,一旦进行安装提示,开启率以预计将收到较大影响。
  6. 一些无法升级更新的App,几乎无法进行有效推广。

可以尝试SKAdNetwork解决,统计更准确。但是应对场景过于单一。

对象存储结构发生改变

  • iOS14以前,类是有MyClass+class_rw_t(dirty memory) + class_ro_t(clean memery)三个数据结构存储信息
  • iOS14,类里面的脏内存得到了优化,class_rw_t拆解成class_rw_t+class_rw_ext_t

官方解释:通过统计发现,开发中大多数的类是没有通过Runtime机制为其添加属性,方法等信息的。所以就没必要为每个类都开辟这个一段空间,造成不必要的浪费。。

dirty memory
脏内存。指可读可写内存
clean memory
干净内存。指只读内存

影响
如果App内部没有通过系统API去访问脏内存的数据(属性或方法),那么就会产生错误,甚至崩溃。

解决
官方建议:访问内存信息,请一定要使用官方提供的API,这样在以后不断的迭代优化过程中,才不会出现兼容问题。

新特性

画中画

PHPicker

在iOS14中,Apple优化了照片的隐私权限,可以让用户选择为该App共享哪些图片,而不是以前的允许访问(所有)图片。

影响

  • 废弃AssetLibrary, 请使用PhotoKit
  • 废弃UIImagePickerController,请使用PHPickerViewController

特性

  • 支持选择视频或图片
  • 支持单选或多选
  • UI设计风格和操作手势与照片App完全相似
  • 不需要授权隐私权限

官方说明

Also please don’t prompt for photo library access before showing the picker and don’t require the user to grant you access before showing the picker. There’s no need to do any of this. And it doesn’t help with the users trust into your app.If your app leverages PhotoKit to access the photos library please reconsider if it really needs to have access to the library or if you can use PHPicker instead.We really hope you like the new picker and API. We’re looking forward to you adopting it in your apps. Thank you.
另外,在显示选择器之前,请不要提示对图库的访问权限,也不要要求用户在显示选择器之前授予您访问权限。 无需执行任何操作。 如果您的应用程序利用PhotoKit访问照片库,请重新考虑它是否真的需要访问该库或是否可以使用PHPicker,我们真的希望您喜欢该应用程序。 新的选择器和API。 我们期待您在应用中采用它。 谢谢。

Widget Extension

小组件功能
具体开发实现可以参考苹果的官方教程
为您介绍WidgetKit
SwiftUI新功能
为小组件添加配置和智能
为小组件构建SwiftUI视图

小组件仅支持SwiftUI开发

App Clips

如何唤醒?

苹果对App Clip的使用场景非常明确,系统对调起方式做了严格的过滤,支持的发起入口有如下几种:

  • NFC
  • QR Codes(也就是二维码,专门的生成工具会在年底开放)
  • Maps
  • Siri 建议
  • Safari链接
  • Messages

注意点

  • 10M的限制
  • 30天不使用,系统自动删除App Clip数据
  • 支持地理位置,相机,麦克风和蓝牙等权限,限制访问 Health、Fitness、通讯录、信息、照片、文件等个人数据。
  • 为了避免弹窗授权的糟糕体验,设计了免申请的通知、定位权限。当然还是有限制,免申请的通知只在 8 个小时内有效,地理位置只能获取一次。
  • 支持Sign in with Apple,也支持ASWebAuthenticationSession来第三方登录,以及使用Apple Pay。
  • 在使用App Clip的过程中,Apple也会明显提示主App的存在,方便用户直接去下载。
  • 不会与App一起出现在用户的Setting App里,有单独的App Clips的分组。
  • 一旦安装了主App,对应的App Clip将会被删除,再点击链接将会直接进入主App。

实现方式

依赖主APP
虽然对于用户来说不需要下载主App,但开发者必须使App Clip跟随主App一同提交审核,App Clip并不能够独立开发并提审。(这与Apple设计之初的理念是一致的,目的是为了快速体验功能,而不是替代App)

独立的Target
在开发上,也是完全Native的实现,类似于一个新增的Extension target,例如Keyboard ExtensioniMessage Extension等。

App Clip不仅支持SwiftUI,也支持UIKit,包括很多人还在坚守的Objective-C,完全都没有问题,并不存在上手难度。

数据共享
由于Target依赖于主App,所以Target间的资源共享都是完全OK的,只需要在资源归属上勾选上App Clip就可以了;同时,与Extension一致,App Clip可以通过App Groups来与主App共享数据。

引导转化
Apple建议开发者可以在App Clip的视图中嵌入SKOverlay,当用户在App Clip中完成相关任务后展示SKOverlay,这样可以较好的引导用户

比如可以将其放置在用户的付款确认界面之后。
新特性SKOverlay

支持一对多
同时,主工程支持多个App Clip Target,目前并不清楚Apple对于数量的限制是多少,但是估计能够满足大部分App主要功能的拆分,以某团为例,可以存在多个App Clip:单车、外卖、酒店住宿、打车等等。

支持不同参数
只需要提供不同参数,就可以针对不同场景不同需求来提供不同的 App Clip 体验,例如官方提供的统一连锁下不同咖啡馆举例

如何处理Universal Link和App Clip URL?

官方Demo提供的解决方案是通过编译宏APPCLIP来做分支处理,这样能够最大程度共用代码:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import SwiftUI
#if APPCLIP
import AppClip
import CoreLocation
#endif

@main
struct FrutaApp: App {
@StateObject private var model = FrutaModel()

#if !APPCLIP
@StateObject private var store = Store()
#endif

@SceneBuilder var body: some Scene {
WindowGroup {
#if APPCLIP
NavigationView {
SmoothieMenu()
}
.environmentObject(model)
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: handleUserActivity)
#else
ContentView()
.environmentObject(model)
.environmentObject(store)
#endif
}
}

#if APPCLIP
func handleUserActivity(_ userActivity: NSUserActivity) {
guard let incomingURL = userActivity.webpageURL,
let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems else {
return
}
if let smoothieID = queryItems.first(where: { $0.name == "smoothie" })?.value {
model.selectSmoothie(id: smoothieID)
}
guard let payload = userActivity.appClipActivationPayload,
let latitudeValue = queryItems.first(where: { $0.name == "latitude" })?.value,
let longitudeValue = queryItems.first(where: { $0.name == "longitude" })?.value,
let latitude = Double(latitudeValue), let longitude = Double(longitudeValue) else {
return
}
let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: latitude,
longitude: longitude), radius: 100, identifier: "smoothie_location")
payload.confirmAcquired(in: region) { inRegion, error in
if let error = error {
print(error.localizedDescription)
return
}
DispatchQueue.main.async {
model.applePayAllowed = inRegion
}
}
}
#endif
}

其中handleUserActivity就是在处理App Clip链接。
当然,直接将主App和App Clip的代码分开也是可以的,但是这可能对于两者区别较大的产品更适合,但同时也违反了Apple的初衷,希望App Clip仅仅是主App的一部分,这可能会导致审核遇阻。

与小程序的异同

有些人认为App Clip是苹果小程序,像微信一样。如果仅从两者设计之初的理念来比较,确实比较相似,但是从其他方面来讲,两者差异较大:

  • 入口不同:App Clip支持多种打开方式,微信小程序只支持从微信进入,但是后者支持主动搜索。
  • 性能不同:App Clip依赖于系统,微信小程序依赖于微信,原生性能会明显优于小程序。
  • 体验路径不同:微信小程序需要打开微信,下拉小程序列表,找到并打开目标小程序,App Clip一步到位,实施降维打击。
  • 定位不同:App Clip明确是主App功能的一部分,不能做主App无关的内容,但是微信小程序没有该限制,并且支持独立发布。

也正是由于定位不同,微信小程序完全可以与App Clip共分天下,微信小程序已经形成生态圈,国内很多开发商专注于小程序的开发,都没有App,自然也就用不上App Clip,虽然App Clip必然会抢占小程序的市场,但是目前看,还是无法从根本上动摇小程序的地位。