导语
需求开发中经常要监听某个页面的可见与不可见,例如某个自定义View在可见时要播放动画,当不可见时(例如被其他页面暂时遮挡,或者退出该视图)要暂停动画。常用的监听方法是-[UIView willMoveToWindow:],本文讲介绍该方法的调用时机以及分享作者遇到过的一个隐蔽的坑。
方法简述
- (void)willMoveToWindow:(nullable UIWindow *)newWindow;
newWindow: The window object that will be at the root of the receiver's new view hierarchy. This parameter may be nil
.
如果视图将要从可见变为不可见,即:被从主窗口上移除掉,则newWindow为nil
相反,如果视图将要从不可见变为可见,即:即将添加到主窗口上,则newWindow为当前程序的keyWindow
调用时机
从上面的方法简述来看,当view的视图层级的根窗口改变时,就会触发该方法回调。为了更深刻地理解这个方法,我们还需要知道它在view和viewController的整个生命周期中的位置
为了获取整个生命周期,写如下代码来获取日志
// VC的代码
- (void)viewDidLoad{
NSLog(@"vc[ceshi]viewDidLoad");
MyView *myView = [[MyView alloc] init];
MySubView *mySubView = [[MySubView alloc] init];
[self.view addSubview:myView];
[myView addSubview:mySubView];
}
获取到的日志如下:
如上图,展示了view和viewController生命周期中的主要节点。根据日志的打印顺序,图中的箭头表示时间的先后顺序,不一定能够代表调用/触发关系。
当视图被添加到父视图后,会由上至下递归调用willMoveToSuperView:方法,再由下至上递归调用didMoveToSuperView:方法。
在视图即将出现时,即viewWillAppear时,由上至下递归调用willMoveToWindow:方法,再由下至上递归调用didMoveToWindow:方法
-
此时视图层级已经确定好,然后开始视图布局,在layoutSubViews即将调用前,会触发VC的viewWillLayoutSubviews和viewDidLayoutSubviews方法。
在视图全部布局完成后,触发viewDidAppear。
⚠️注意这里只能根据日志看到时间的先后顺序,看不到「视图布局完成后是如何通过回调触发viewDidAppear的」,官方文档给的注释是当所有过渡动画都完成之后,触发viewDidAppearCalled after the view has fully transitioned to visible, when any transition animations have completed.
遇到的一个坑
需求开发时,发现在当前页面切换到另一个VC时,willMoveToWindow出现了这样的调用情况
willMoveToWindow:nil
willMoveToWindow:newWindow
willMoveToWindow:nil
视图第一次被从窗口上移除,这是预期内的
然而后面视图又被添加回窗口,然后再次移除,这是预期外的表现。
断点查看第二次调用,即willMoveToWindow:newWindow时的堆栈
如上图,可以看到底层调用有许多和UINavigationController、aniamtion相关的方法,推测和我们切换VC时的动画有关。
下面是我们切换VC的代码
UINavigationController *navVC = UIApplication.sharedApplication.keyWindow.rootViewController;
[navVC pushViewController:vc animated:**YES**];
我们尝试将YES改为NO,不要切换VC时的过渡动画,果然额外的两次willMoveToWindow的调用没有了。这印证了我们的推测:当pushViewController设置animated为YES 的时候,
pushViewController先将当前VC pop出堆栈,此时调用了第一次willMoveToWindow:nil
由于设置了animated为YES,要表现出当前VC的页面动态消失的效果,因而当前VC的视图会再次被添加到当前窗口中,此时调用了第二次的willMoveToWindow:newWindow
当过渡动画结束后,当前VC的视图完全从当前窗口上被移除掉,此时调用了第三次的willMoveToWindow:nil
结论:psuhViewController:animated的过渡动画,会导致willMoveToWindow被额外调用两次