iOS-谁引起webview.frame自动变化?

引言

一般我们创建的项目架子是以下两种方式:

  1. UITabBarController + UINavigationController + UIViewController
  2. UINavigationController + UITabBarController + UIViewController
    我的项目采用的是第2种形式之前项目都是使用第一种形式,现在选择第二种是想嵌入阿里云的mpass,然后由UIViewController去加载具体的业务场景。

现在遇见这样一个问题:

UIViewController.view 添加了一个webview,当VC viewwillAppear(1.从后台会到前台 2.pop或dismiss到该VC 都会触发)时,webview的frame会发生变化,viewwillAppear 有做一些处理,可是看上去和webview.frame也没关系(或者说即使有关系,那么多代码怎么定位问题在哪里?),是什么导致webview.frame的自动变化?,

探索

  1. 首先我们监听webview.frame的变化,并打下断点。bt 一下

    image.png

  2. 发现系统底层调用了UIView(Geometry) _resizeWithOldSuperviewSize方法,去官网看看做什么的

The default implementation resizes the view according to the autoresizing options specified by the autoresizingMask property.
You shouldn’t invoke this method directly,
but you can override it to define a specific resizing behavior.
If you override this method and call super as part of your implementation,
you should be sure to call super before making changes to the receiving view’s frame yourself.

谷歌翻译:
默认实现根据autoresizingMask属性指定的自动调整大小选项来调整视图的大小。您不应该直接调用此方法,但可以覆盖它以定义特定的调整大小行为。
如果你重写这个方法并调用super作为实现的一部分,你应该确保在自己更改接收视图的框架之前调用super。

分析:
之所以会调用UIView(Geometry) _resizeWithOldSuperviewSize,是因为设置autoresizingMask属性,确实在webview的初始化时设置该属性

webview.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);

  1. autoresizingMask 又是什么?
    UIView以及子类的一个UIViewAutoresizing类型,该类型的取值决定视图随父视图的变化而产生怎么样的变化

UIViewAutoresizingNone(默认) 不会随着父视图的改变而变化
UIViewAutoresizingFlexibleLeftMargin 自动调整view与父视图左边距,以保证右边距不变
UIViewAutoresizingFlexibleRightMargin 自动调整view的父视图右边距,以保证左边距不变
UIViewAutoresizingFlexibleTopMargin 自动调整view与父视图的上边距,以保证下边距保持不变
UIViewAutoresizingFlexibleBottomMargin 自动调整view与父视图的下边距,以保证上边距保持不变
UIViewAtuoresizingFlexibleWidth 自动调整view的宽度,保证左边距和右边距不变
UIViewAtuoresizingFlexibleHeight 自动调整view的高度,保证上边距和下边距不变

小结:
到这里我们已经知道webview.frame就是因为我们给webview设置了autoresizingMask属性,webview会随着父视图变化而自动调整宽度和高度(UIViewAtuoresizingFlexibleWidth, UIViewAtuoresizingFlexibleHeight),以保证上下左右边距保持不变,那么问题又来了,webview的父视图是VC.view谁触发的父视图发生变化了?

再探

使用相同的方式,监听VC.view.frame的变化,bt一下,发现是由[UITabBarController __viewWillLayoutSubviews] 间接触发的,这也是个系统方法,谁触发的?

image.png

  1. 项目过于庞大,不便于定位问题,我们把项目恢复到最简单的样子,UINavigationController + UITabBarController + UIViewController ,观察一下VC.view,view把tabbar+navBar穿透了
    image1.png

打印一下VC.view,此时的view是铺满全屏的

image3.png

这可是VC.view,如果view的顶端和低端有内容,这样会被navBar和tabBar遮盖住了,苹果为什么要这样做?难道是要我们改VC.view.frame
于是我们针对滚动和非滚动视图进行分析

  1. 滚动视图:UIScrollView以及UIScrollView子类

此时我们在VC.view上添加一个滚动视图UITableView,和VC.view保持frame一致,那么UIScrollview是不是要被遮盖住呢?

// tabview和父视图view frame保持一致
UITableView *tabview = [[UITableView alloc] initWithFrame:self.view.frame];
[self.view addSubView:tabview];

事实是没有被遮盖住(见下图)


tableview.gif

此时再看下view和tabview的frame,此时tabview.contentOffset.Y值向下偏移了64,整好是导航栏+状态栏的高度

image4.png

分析:
scrollView的内容原本没有内边距,但是考虑到导航栏、状态栏、TabBar 会挡住后面scrollView所展示的内容,系统自动为scrollView增加上下的内边距

iOS11之前:

self.tabview.automaticallyAdjustsScrollViewInsets = YES;

iOS11之后使用contentInsetAdjustmentBehavior代替automaticallyAdjustsScrollViewInsets

/**
与UIScrollViewContentInsetAdjustmentScrollableAxes相似,但为了向后兼容(向低版本兼容),
当scroll view被view controller管理,且该view controller的automaticallyAdjustsScrollViewInsets = YES并在导航条控制器栈内,
该枚举也会调整顶部(导航条)和底部(Tabbar)的内边距,无论该scroll view是否可滚动。
/
UIScrollViewContentInsetAdjustmentAutomatic,
/
*
滚动轴的边缘会被调整(例如contentSize.width/height > frame.size.width/height 或 alwaysBounceHorizontal/Vertical = YES)
/
UIScrollViewContentInsetAdjustmentScrollableAxes, // Edges for scrollable axes are adjusted (i.e., contentSize.width/height > frame.size.width/height or alwaysBounceHorizontal/Vertical = YES)
/
*
内边距不会被调整
/
UIScrollViewContentInsetAdjustmentNever, // contentInset is not adjusted
/
*
内边距总是被scroll view的safeAreaInsets所调整,safeAreaInsets顾名思义就是safeArea的内边距
*/
UIScrollViewContentInsetAdjustmentAlways, // contentInset is always adjusted by the scroll view's safeAreaInsets

  1. 非滚动视图
    在开发过程中更多的是非滚动试图,那如果能想滚动视图那样有内边距,那么我们自定义的视图就无视当前controller有无导航栏和tabbar了,如何做到让非滚动试图有内边距呢?
    方案一: 保留导航栏和tabbar的半透明度
    UIViewControllerself.edgesForExtendedLayout 置为 UIRectEdgeNone

self.edgesForExtendedLayout = UIRectEdgeNone;

// edgesForExtendedLayoutview:决定了view边缘扩展布局的情况
typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
// 会自动去掉navigationBar和tabbar的高度
UIRectEdgeNone = 0,
UIRectEdgeTop = 1 << 0,
UIRectEdgeLeft = 1 << 1,
UIRectEdgeBottom = 1 << 2,
UIRectEdgeRight = 1 << 3,
// 全屏布局,顶部和底部都会被navigationBar和tabbar覆盖,默认值
UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
}

方案二:导航栏和tabbar改为不透明 (我项目中的配置)
translucent置为NO + extendedLayoutIncludesOpaqueBars = NO(默认)

tabbar配置(基类):

// viewDidLoad 执行结束后,自动将VC的view的Y向下偏移64
// 1. 将navigationBar 、tabbar 的半透明设置为NO
self.tabbar.translucent = NO;
// 2. 额外布局是否包括不透明的Bar 默认NO
self.extendedLayoutIncludesOpaqueBars = NO;// 也就是额外布局不包含不透明的Bar

导航栏配置(基类):

self.navigationBar.translucent = NO;

  1. translucent
    会到我们最初定位问题时,viewwillAppear做了什么?具体是哪行代码导致了weview.frame的变化?
    其中在UITabbarControllerUINavgationController viewwillAppear时,将translucent设置为了NO,那我们分析一下改属性

官方解释:

1.The default value is YES. If the search bar has a custom background image, the default is YES if any pixel of the image has an alpha value of less than 1.0, and NO otherwise.
2.If you set this property to YES on a search bar with an opaque custom background image, the search bar will apply a system opacity less than 1.0 to the image.

  1. If you set this property to NO on a search bar with a translucent custombackground image, the search bar provides an opaque background for the image using black if the search bar has UIBarStyleBlack style, white if the search bar has UIBarStyleDefault, or the search bar’s barTintColor if a custom value is defined.

以上3条上代码解释

第1条:translucent不设置,由图片的透明度决定


image5.png
image6.png

第2条: translucent 手动设置为YES


image7.png

第3条: translucent 手动设置为NO,图片带有透明度


image8.png

最后总结一下webview.frame自动更新的原因(倒着推):

首先我们监听一下webview.frame的变化,通过打断点查看堆栈信息,发现系统自动调用了一个方法_resizeWithOldSuperviewSize,根据官方给到的解释,是因为设置了autoresizingMask属性,确实在相中设置了
webview.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
这样webview会随着父视图的变化而调整自己的宽高,以保证上下左右边距不变。
父视图变化时机:
在基类UINavigationController和UITabBarController将translucent设置为NO,也就是导航栏和tabbar都是不透明的,VC.view.frame自动调整{64,0,49,0} , 因为父视图VC.view.frame的变化,所以影响到了子类webview.frame的变化,到这里这个bug就清晰明朗了。

写到这里的时候觉得这个bug其实也挺简单的,可是面对成千上十万行代码能分析并解决了这个问题了,还是不错的,加油~

你可能感兴趣的:(iOS-谁引起webview.frame自动变化?)