1

When presenting a ViewController I expect the view lifecycle to be in the order:

  1. viewDidLoad()
  2. viewWillAppear
  3. viewWillLayoutSubviews()
  4. viewDidLayoutSubviews()
  5. viewDidAppear()

note: between 4 and 5 can be more iterations of the viewWill/viewDidLayoutSubviews(). In other words I'd expect viewDidLayoutSubview to fire before the first viewDidAppear.

To test this, I add a childViewController to a UIViewController subclass and call addSubview to add the childViewController's view.

override func viewDidLoad() {
    super.viewDidLoad()

    let childVC = ChildViewController()
    self.addChildViewController(childVC)
    self.view.addSubview(childVC.view)

}

This produces the expected results when i print out the view methods:

  1. viewDidLoad()
  2. viewWillAppear
  3. viewWillLayoutSubviews()
  4. viewDidLayoutSubviews()
  5. viewWillLayoutSubviews()
  6. viewDidLayoutSubviews()
  7. viewDidAppear

When I use setViewController on a UIPageViewController subclass:

override func viewDidLoad() {
    super.viewDidLoad()

    let childVC = ChildViewController()
    self.setViewControllers([childVC], direction: .forward, animated: true, completion: nil)

}

I always get viewDidAppear performing before the first LayoutSubviews method:

  1. viewDidLoad()
  2. viewWillAppear
  3. viewDidAppear
  4. viewWillLayoutSubviews()
  5. viewDidLayoutSubviews()
  6. viewWillLayoutSubviews()
  7. viewDidLayoutSubviews()

Can someone explain why this behaviour occurs?

---- further observations ---

I've also added in a log for willMove and DidMove methods on the childViewController. The results for the initial test:

willMove(toParentViewController:)

  1. viewDidLoad()
  2. viewWillAppear
  3. viewWillLayoutSubviews()
  4. viewDidLayoutSubviews()
  5. viewWillLayoutSubviews()
  6. viewDidLayoutSubviews()
  7. viewDidAppear

The results for the UIPageViewController:

  1. viewDidLoad()
  2. viewWillAppear
  3. viewDidAppear didMove(toParentViewController:)
  4. viewWillLayoutSubviews()
  5. viewDidLayoutSubviews()
  6. viewWillLayoutSubviews()
  7. viewDidLayoutSubviews()

So it looks as though the setViewController invokes a moveToParentViewController which perhaps may be the reason why it is invoking the viewDidAppear earlier.

Peeks
  • 11
  • 3
  • the order you expect - it's just your assumption, or is there any documentation from which you deduced it? – Milan Nosáľ Feb 07 '18 at 00:14
  • Unfortunately I couldn't find any official documentation on this but it's something I've always observed. Before the view appears it should have laid out all the subviews before hand. Here is a previous discussion with a similar conclusion: [link](https://stackoverflow.com/questions/5562938/looking-to-understand-the-ios-uiviewcontroller-lifecycle) – Peeks Feb 07 '18 at 01:37

1 Answers1

0

setViewControllers calls this method:

/* @class UIPageViewController */ -(void)_setViewControllers:(void *)arg2 withCurlOfType:(long long)arg3 fromLocation:(struct CGPoint)arg4 direction:(long long)arg5 animated:(bool)arg6 notifyDelegate:(bool)arg7 completion:(void *)arg8 {

[r13 _child:r14 beginAppearanceTransition:rcx animated:r8];
r14 = [_objc_msgSend_1426728(r13, @selector(view)) retain];
rax = _objc_msgSend_1426728(var_D0, @selector(view));
rax = [rax retain];
[r14 addSubview:rax];
r13 = var_B8;
[rax release];
rdi = r14;
r14 = var_D0;
[rdi release];
[r13 _childEndAppearanceTransition:r14];

begin and end look like this:

/* @class UIPageViewController */
-(void)_child:(void *)arg2 beginAppearanceTransition:(bool)arg3 animated:(bool)arg4 {
    r14 = arg4;
    r15 = arg3;
    r12 = [arg2 retain];
    if ([self _forwardAppearanceMethods] == 0x0) {
            objc_unsafeClaimAutoreleasedReturnValue([r12 view]);
            [r12 beginAppearanceTransition:r15 & 0xff animated:r14 & 0xff];
    }
    [r12 release];
    return;
}

As we can see above, that call to _forwardAppearanceMethods must be returning 0 which is causing the problem, looking what it does:

/* @class UIViewController */
-(bool)_forwardAppearanceMethods {
    r14 = self;
    rax = objc_opt_class(self, _cmd);
    rbx = @selector(shouldAutomaticallyForwardAppearanceMethods);
    rdx = rbx;
    if ([rax doesOverrideViewControllerMethod:rdx] == 0x0) {
            rbx = @selector(automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers);
    }
    rax = *_objc_msgSend;
    rax = (rax)(r14, rbx, rdx, *_objc_msgSend);
    return rax;
}

As we can see its calling shouldAutomaticallyForwardAppearanceMethods which low and behold UIPageViewController has implemented as follows:

/* @class UIPageViewController */
-(bool)shouldAutomaticallyForwardAppearanceMethods {
    return 0x0;
}

Note it also has:

/* @class UIPageViewController */
-(bool)shouldAutomaticallyForwardRotationMethods {
    return 0x0;
}

Decompilation done using Hopper

malhal
  • 26,330
  • 7
  • 115
  • 133