LLDynamicLaunchScreen 设计思路

8,957 阅读11分钟

LLDynamicLaunchScreen 设计思路

在iPhone上,启动图是每个APP向用户展示的第1个页面,足以可见它的重要性。

启动图非常重要,但目前你只能在更新APP的时候才能对它进行修改,如果你想在某个时间点(例如双11当天)显示1张你精心为用户准备的启动图,你只能提前更新你的APP版本并将它设置成指定时间自动发布,还必须要求用户及时更新APP版本,才能看到你为ta精心制作的启动图;如果你只是想当天显示而之后不显示的话,你还需要再次更新APP版本,这么繁琐的操作我想没有人会这样去做,而且如果你只是修改了启动图样式的话,并且过了今天又要改回去的话,Apple审核团队也未必会让你顺利的通过审核;但现在你只需要集成 LLDynamicLaunchScreen 就可以不更新APP而单独更新启动图了。

如果上面提到的点还不足以让你动心的话,那么接下来这种情况可能会让你崩溃,有些用户反馈ta们在打开APP时会出现黑屏或白屏,具体表现为启动图上关于UIImageView区域都显示异常,可能是全白也可能是全黑,如下图所示:

在手机上展示的启动图正常启动图
imageimage

目前所有证据都表明这是系统启动图的BUG而非开发者的问题,而且这个BUG在任何系统、机型都可能出现,无法准确定位具体原因,也无法针对某个类型的设备单独处理,在我测试过程中,发现微信、今日头条这些APP都存在这样的问题,其中微信最严重,如下图所示:

微信启动图
image

LLDynamicLaunchScreen

目前市面上只有 LLDynamicLaunchScreen 这1个框架可以实现修改启动图和自动修复启动图异常的功能;到是有1个框架有类似功能:DynamicLaunchImage,不过它压根没法使用。

自动修复启动图

现在的项目几乎都使用 LaunchScreen 适配启动图,这种方式很棒,它省去了很多的重复图片,但它还不够成熟(这里不得不吐槽一下当初开发启动图的这个团队)。正常情况下,用户在手机上看到的启动图应该和开发者在 LaunchScreen 文件中配置的内容一模一样,但事与愿违,已经有非常多的用户反馈打开APP时出现黑屏或白屏,这其中也包括我自己。

如果你在网络上搜索如何解决iPhone启动图显示异常的话,大部分的教程都会告诉你把图片原文件从Assets中移到项目中或者从项目中移到Assets中,或者修改图片名称,或者让用户重启手机,这些方案我都试过了,没有1个是行之有效的。

如果你不幸遇到了这种问题,那么我建议你使用 LLDynamicLaunchScreen 这个框架来解决,你要做的只是集成它就行(为了方便你集成它,我提供了3种集成方案,详情可见 github 上的框架主页),剩下的框架帮你处理。

以下是我在真机上录制的演示视频(iPhone 14, iOS 16.4.1):

demo2

是的,就像视频里这么简单,你什么都不需要做,框架会自动帮你处理所有情况;我知道,你可能会担心它会不会影响项目稳定性,请容我解释一下它的实现逻辑。

  1. 当用户首次打开APP时(下载或更新后首次打开均算)进入下一步,
  2. 首先它会找到启动图文件最上层的那个UIImageView控件并对它截图(因为只有UIImageView才会出现黑屏或白屏,UILabel和其他控件未发现有该问题;并且所有的UIImageView都会同时出问题而不会出现某1个UIImageView出问题而其它UIImageView正常的情况,故而只需要检查1个UIImageView就行了)。
  3. 使用截图和系统启动图指定区域的内容进行比较,理论上2张图的相同区域内容应该完全一样,如果不一样则认为启动图显示异常。
  4. 如果判定为异常的话就会触发生成启动图并替换的操作逻辑。

所有操作均在子线程中进行,如果启动图显示正常不会触发自动修复逻辑。

另外,我在开发框架的过程中还发现,如果你在启动图文件中有设置关于安全区域约束(safeAreaLayoutGuide)的话,在竖屏下一切正常,但在横屏下,它会显示异常。

image3.png

从上面的图片可以看到我对启动图设置了1个距离顶部安全区域等于0的约束,在横屏下,由于顶部没有不安全区域,所以它应该贴顶显示才对,但实际显示却距离顶部有一段距离,我通过测量发现这段距离刚好等于竖屏状态下顶部的不安全区域的高度,这说明在横屏下关于安全区域的约束可能会失效,我在 iPhone 14 和 iPhone 6等机型上都发现有这个问题,所以我增加了1个判断,如果启动图文件中有设置安全区域约束并且支持横屏的话,框架会自动修复横屏状态下的启动图。

image4.png

系统启动图的格式是ktx而非常见的png或jpg格式,我查了资料后发现这种格式具有更低的压缩和mipmap(mipmap是一种纹理优化技术,可以在渲染时提高性能和图像质量,可以减少纹理映射时的图像失真和锯齿状边缘,同时还可以提高渲染速度)特性;同样的启动图,系统生成的ktx图片是21kb、1.7mb,而使用PNG的话则是75kb、3.7mb,我也尝试了使用JPG格式并将压缩率调至最低,但都没有系统生成的图片小;Apple使用这种格式是想让生成启动图和加载启动图都更快完成,但是ta们却没处理好其中的细节,其中的黑屏和横屏状态下安全区域约束失效就是最大的问题。

关于安全区域约束的判断在 LLDynamicLaunchScreen+LLPrivate.m 文件中的第114~117行,如下图所示:

image5-1.png

修改启动图

由 LaunchScreen 适配的启动图,系统会将生成的图片缓存到沙盒指定路径下,如果你的应用只支持深色或浅色其中1种模式的话,修改启动图其实非常简单,你只需要将目标图片写入到指定位置即可;

这里有2个注意事项:

  1. 目标图片的尺寸必须和原启动图保持一致,否则系统在下次启动时会重新生成启动图;
  2. 不能修改启动图名称,如果修改了系统在下次启动时找不到原来缓存的图片也会重新生成。

但如果你的项目同时支持深色和浅色模式的话,这将变得复杂起来,以下是系统生成的启动图:

image5.png

一共4张启动图,分别对应项目中的深色竖屏启动图、浅色横屏启动图、浅色竖屏启动图、深色横屏启动图。

最难的点在于如何区分哪个是深色启动图,哪个是浅色启动图;

目前的逻辑是这样的:框架会通过 LaunchScreen 文件生成浅色和深色各1张目标图,然后与本地的启动图逐一比较,正常情况下这样就可以区分出来了;

但是,如果遇到系统启动图显示黑屏或白屏的话,可能会导致失败,此时框架的处理逻辑是这样的:重新对图片进行比较,但在比较的过程中忽略掉那些UIImageView区域,对于一般情况,这种处理方式就能应付了,但如果你在启动图中UIImageView占用的区域比较大的话,可能还是会失败;

此时,就需要第3步了,这也是最后1步,框架会通过截取图片最右下角的1×1像素点的图片进行比较判定,如果启动图这个区域的图片内容一模一样的话那结果可能正确也可能错误,这需要开发者配合,开发者需要在启动图文件的最右下角添加1个至少1×1像素点的视图,并将视图的背景色设置为 system color,因为只有 system color 才能在不同模式下切换颜色;

大部分情况,你都不需要添加这个辅助视图,但如果你的启动图是使用1整张图片适配的话,就像下图这样:

image3-1.png

这种情况下你只能通过添加辅助视图来帮助框架识别正确的启动图类型;你可能会担心添加的这个视图会不会影响在手机上的显示效果,你的这种担心是多虑的,如果目标手机是异形屏的话,这个区域的图片压根不会显示在手机屏幕上而是会被裁剪掉,就像下图这样:

原图展示效果
image3image4-1.png

在异形屏手机上压根看不到右下角的那块区域,但原图是存在那块区域的;如果目标手机不是异形屏的话也不用担心,由于只有1个像素点那么大,肉眼压根看不出来,你能看出来上面的原图右下角有1个白点点吗?

添加辅助视图有3个注意事项:

  1. 由于目前的iPhone屏幕都是retina屏幕,所以你可以在启动图文件中添加1个0.5×0.5尺寸的UIView,这样它在图片中实际展示就会是1×1或1.5×1.5,这足够小,小到肉眼看不出来,框架中的Demo就是这样处理的;
  2. 辅助视图的背景色只能是 system color,因为只有这种颜色在不同模式下才会显示不同色值,如果你怕黑色与白色这种system color会影响图片显示,你可以选择1个和图片背景相似的system color,例如在上面的图片中你就可以选择 system orange color。
  3. 给辅助视图添加布局时系统默认会距离安全区域为0,你需要将它修改为距离父视图为0。

以下是我在真机上录制的演示视频(iPhone 14, iOS 16.4.1):

修改启动图
demo1.gif

其实它也支持从网络上下载1张图片来设置成启动图,感兴趣的话就下载Demo试一下吧。

可以优化的地方

如何比较2张图片是否一样是整个框架最核心的部分之一,目前是通过遍历2张图片像素点判断UIColor的相似度来判定图片相似度,但这种方式的缺陷在于如果2张图片虽然显示不一样,但如果它们的颜色值非常接近的话,也会认为是同1张图片,例如使用 system blue 做为背景色就会出现这种问题,如果你有更好的建议请告诉我:internetwei@foxmail.com

FAQ

为什么不支持LaunchImage?

因为使用 LaunchImage 适配的启动图被打包放到了 .app 中,我们没有权限读写自然也就无法修改。

为什么我在测试的时候修改失败?

这大概率是因为你修改完启动图之后又重新运行项目了,重新运行后系统会重新生成一份启动图,所以测试的时候你应该将APP手动杀死再打开;不用担心这个问题,因为真实用户是无法重新运行你的APP的。

为什么更新版本后首次打开会显示默认启动图?

因为当APP更新版本后,系统再首次启动时会重新生成一份启动图(不管你有没有修改过启动图文件),由于系统生成启动图是在APP运行之前进行的,故而我无法干预,目前的解决方案是在启动后检测到是新版本首次打开的话就把上个版本修改的数据自动应用到当前版本,这样当用户下次进入APP时就会正常显示。

PS: 更新系统版本后不会重新生成启动图。

致谢

  1. DynamicLaunchImage
  2. iOS启动图异常修复方案