一文弄懂ios 视图进入/离开窗口的监听方法:willMoveToWindow:

导语

需求开发中经常要监听某个页面的可见与不可见,例如某个自定义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];
}

获取到的日志如下:


image.png
view和VC的生命周期.png

如上图,展示了view和viewController生命周期中的主要节点。根据日志的打印顺序,图中的箭头表示时间的先后顺序,不一定能够代表调用/触发关系。

  • 当视图被添加到父视图后,会由上至下递归调用willMoveToSuperView:方法,再由下至上递归调用didMoveToSuperView:方法。

  • 在视图即将出现时,即viewWillAppear时,由上至下递归调用willMoveToWindow:方法,再由下至上递归调用didMoveToWindow:方法

  • 此时视图层级已经确定好,然后开始视图布局,在layoutSubViews即将调用前,会触发VC的viewWillLayoutSubviews和viewDidLayoutSubviews方法。
    在视图全部布局完成后,触发viewDidAppear。
    ⚠️注意这里只能根据日志看到时间的先后顺序,看不到「视图布局完成后是如何通过回调触发viewDidAppear的」,官方文档给的注释是当所有过渡动画都完成之后,触发viewDidAppear

    Called after the view has fully transitioned to visible, when any transition animations have completed.

遇到的一个坑

需求开发时,发现在当前页面切换到另一个VC时,willMoveToWindow出现了这样的调用情况
willMoveToWindow:nil
willMoveToWindow:newWindow
willMoveToWindow:nil
视图第一次被从窗口上移除,这是预期内的
然而后面视图又被添加回窗口,然后再次移除,这是预期外的表现。
断点查看第二次调用,即willMoveToWindow:newWindow时的堆栈


image.png

如上图,可以看到底层调用有许多和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被额外调用两次

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容