最近遇到一个奇怪的问题,在 iPhone 7P 上,用户在打开某些活动页面时,截图会让 h5 向上窜,导致顶部被遮挡。如果是截图导致的,后续滑动 h5 还可以恢复。如图
分析
首先 iOS 没有监听截图事件,不会主动执行自己逻辑; WebView 位置是用约束实现的。顶部约束等于 vc.view 的安全区顶部对齐,布局是没有问题的。
拿到真机后,查看了 View 视图发现是安全区的 y 坐标变化了。正确的时候是
<UIView: 0x102708ef0; frame = (0 64; 375 812); autoresize = W+H; layer = <CALayer: 0x281087920>>
而截图后的表现是:
<UIView: 0x104f0c0d0; frame = (0 20; 390 844); autoresize = W+H; layer = <CALayer: 0x281d20b00>>
这是为何?截图是怎么影响安全区布局的。一头雾水,然后通过伟大的二分法,最后确认是我们 WebView 页面里有个自定义导航栏的操作导致的。
[self.navigationController.navigationBar setBackgroundImage:newImage forBarMetrics:UIBarMetricsDefault];
注释掉就不会有问题。而且在刘海屏上也没有问题,我只能把此问题定义为 iOS 的 bug(iOS 14 也存在的)。
回到顶部
过了几天有开发反馈说,点击 statusbar (触发返回顶部),后续滚动操作都无法恢复。最后的 bug 表现是一样的。但相对于截图客户端没有做任何监听逻辑,scrollToTop
似乎还有些发掘的空间。
我大概找了下影响 view 安全边界的一些 VC 参数,edgesForExtendedLayout
, extendedLayoutIncludesOpaqueBars
,进而了解的他们对 self.navigationController.navigationBar.translucent
参数的影响,其中有一条:
When the navigation bar is translucent, configure the edgesForExtendedLayout and extendedLayoutIncludesOpaqueBarsproperties of your view controller to display your content underneath the navigation bar.
If the navigation bar doesn't have a custom background image, or if any pixel of the background image has an alpha value of less than1.0
, the default value of this property is YES. If the background image is completely opaque, the default value of this property is NO. If you set this property to YES and the custom background image is completely opaque, UIKit applies a system-defined opacity of less than1.0
to the image. If you set this property to NO and the background image is not opaque, UIKit adds an opaque backdrop.
所以,是否translucent
会导致导航栏行为变化,进而影响安全距离是吗?我在 setBackgroundImage
前后追加了 log:
NSLog(@"1. translecent = %d", self.navigationController.navigationBar.translucent);
[self.navigationController.navigationBar setBackgroundImage:newImage forBarMetrics:UIBarMetricsDefault];
NSLog(@"2. translecent = %d", self.navigationController.navigationBar.translucent);
果然,执行 setBackgroundImage
之后,translucent
从 1,变为 0 。也就是说,初始时 SafeArea 的 y 边界 = 64 ,当设置了背景后, UIKit 发现此时不需要额外的 44 高度的导航栏偏差,translucentChanged=1
,并且设置 SafeArea 的 y 边界 = 20 但是没有触发渲染,记住这个标记位。当用户截图、回到顶部时,UIKIt 会更新渲染,所以会导致 bug。(以上是我对 UIKit 的黑盒运行机制猜测)
修改方法
在设置 setBackgroundImage
之前,主动设置 translucent = 0
,避免触发setBackgroundImage
内部的检测是否透明的逻辑。
self.navigationController.navigationBar.translucent = NO;
[self.navigationController.navigationBar setBackgroundImage:newImage forBarMetrics:UIBarMetricsDefault];
总结
属于 iOS 自身对非刘海屏的 bug,现在还存在。[self.navigationController.navigationBar setBackgroundImage:newImage forBarMetrics:UIBarMetricsDefault];
不一定会导致 translucent = 0
,即使你使用了 JPG 或者下面示例中的代码。在严选 WebView 有问题,自己全新创建的工程里不会有问题,内部机制待查。
NSLog(@"1. translecent = %d", self.navigationController.navigationBar.translucent);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://yanxuan.nosdn.127.net/0ae2f591d48e4b29b064fa3f4f71e190.jpg?imageView&thumbnail=750y128&quality=75&axis=5_10"]];
UIImage *img = [UIImage imageWithData:imageData];
CGFloat scale = [UIScreen mainScreen].scale;
UIImage *newImage = [UIImage imageWithData:UIImagePNGRepresentation(img) scale:scale];
[self.navigationController.navigationBar setBackgroundImage:newImage forBarMetrics:UIBarMetricsDefault];
NSLog(@"2. translecent = %d", self.navigationController.navigationBar.translucent);
});