3

iOS 10, the gift that keeps on breaking, seems to have changed another behavior.

Assume two UIViewControllers are pushed onto a UINavigationController.

On iOS 8/9, calling navigationController?.popViewController(animated: true) to pop the top UIViewController (say VC2) caused viewDidLayoutSubviews on the bottom view controller (say VC1) to get called.

We relied on this to refresh VC1. Sometimes VC2 adds subviews to VC1 (via the data model), and this needs to get reflected when popping back to VC1.

Accurate frame information is required. We can't use viewWillAppear because frame data is wrong on iOS 9. The problem with viewDidAppear is that there is a momentary glitch between seeing the view initially and the adjustment.

Now VC1's viewDidLayoutSubviews does not get invoked when popping VC2.

1) Is this a bug for viewDidLayoutSubviews to not get invoked?

2) What's the right way to refresh view controllers when popping with UINavigationController?

Community
  • 1
  • 1
Crashalot
  • 31,452
  • 56
  • 235
  • 393
  • I assume `viewDidLayoutSubviews` is called at some point. Is it called together with the other controller (VC2)? – Leo Natan Sep 29 '16 at 21:51
  • @LeoNatan yes it is is called when VC1 appears the first time – Crashalot Sep 29 '16 at 21:52
  • Actually sounds like an acceptable behavior. If you update data source in `viewDidLayoutSubviews`, that's not good. – Leo Natan Sep 29 '16 at 21:53
  • @LeoNatan no we don't update the data source in viewDidLayoutSubviews. the data source is modified in VC2, but VC1 should reflect the new data (which causes new subviews to appear). – Crashalot Sep 29 '16 at 21:55
  • 1
    @Crashalot Then what I put in my answer is correct. You should update the data in `viewWillAppear` and add/update any new views as well. The new views should be setup based on the view controller's view size at the time. `viewDidLayoutSubviews` can be used as normal to adjust the subview's frames. – rmaddy Sep 29 '16 at 21:58
  • @rmaddy but if `viewDidLayoutSubviews` isn't getting called now, how will the new subview's frames get adjusted? – Crashalot Sep 29 '16 at 22:03
  • They won't need to be adjusted. As I said, you create them with the proper frame to begin with. This should always be done anyway. But you also have code in `viewDidLayoutSubviews` to adjust any and all subviews as needed. Keep in mind that simply dismissing VC2 does not and should not result in the view of VC1 changing size. That's why `viewDidLayoutSubview` isn't called. – rmaddy Sep 29 '16 at 22:05

2 Answers2

3

Relying on viewDidLayoutSubviews was never the proper solution. UIViewController provides viewWillAppear: or viewDidAppear: for such a use. When VC2 is popped from the navigation controller, those two methods will be called on VC1 to let you know that is will be or now is visible again.

viewDidLayoutSubviews should only be used to adjust view frames and layout.

viewWill|DidAppear: should be used to handle the view controller becoming visible originally or again. In your case you should use this to update data and add/update views as needed. Those new views should be setup based on the view controller's current frame. They will be adjusted in your implementation of viewDidLayoutSubviews as needed.

Crashalot
  • 31,452
  • 56
  • 235
  • 393
rmaddy
  • 298,130
  • 40
  • 468
  • 517
  • Sorry should have clarified that frame information is required, and it appears `viewWillAppear` does not contain proper frame data: http://stackoverflow.com/questions/32662851/ios-9-frame-no-longer-set-in-viewwillappear-after-uinavigationcontroller-pushvi. The problem with `viewDidAppear` is that there is a momentary glitch between seeing the view initially and the adjustment. – Crashalot Sep 29 '16 at 21:48
  • `viewDidLayoutSubviews` is sometimes much more appropriate place to put code which specifically needs to layout view. For instance, in `viewWillAppear` the hierarchy is often incomplete and not ready yet. `viewDidAppear` is too late, as it happens after the animation. In this case, refreshing a data source is indeed not the place. – Leo Natan Sep 29 '16 at 21:48
  • @LeoNatan precisely our assessment. So any suggestions? Also (1) is this a bug on iOS 10; (2) or is this intended behavior from now on? – Crashalot Sep 29 '16 at 21:50
  • @LeoNatan We don't disagree. I never said anything about laying out views in `viewWillAppear`. I stated that adjusting frames belongs in `viewDidLayoutSubviews`. But that's not the question. – rmaddy Sep 29 '16 at 21:51
  • @Crashalot One cannot know. Open a bug report if you believe it is an issue. `viewDidLayoutSubviews` is meant to *layout* the view, not update data. – Leo Natan Sep 29 '16 at 21:52
  • @Crashalot It will help if you clarify exactly what changes you wish to make when VC1 appears again after VC2 is dismissed. – rmaddy Sep 29 '16 at 21:53
  • @LeoNatan sorry imprecise wording. yes, the view layout changes as more subviews are added. will update q. – Crashalot Sep 29 '16 at 21:53
  • Thanks for the help! But if `viewDidLayoutSubviews` isn't getting called now, how will the new subview's frames get adjusted? – Crashalot Sep 29 '16 at 22:04
  • Thanks to you both rmaddy and @LeoNatan. – Crashalot Sep 29 '16 at 22:17
  • @LeoNatan hi again. stumbled across this SO answer which suggests `viewDidLayoutSubviews` does not guarantee frame information is ready for certain cases of AutoLayout: http://stackoverflow.com/a/28622285/144088. `viewWillAppear` does not provide accurate information on iOS 9, so which function do we use for accurate frame information? – Crashalot Oct 15 '16 at 01:33
  • @Crashalot Why do you need accurate frame information with AL? The whole point is not to need that. If you have specific designs for iPhone/iPad, the correct way is to consult the size class of the controller. This will always be accurate at `viewDidLayoutSubviews`. – Leo Natan Oct 15 '16 at 01:41
  • @LeoNatan good question. We need a camera preview layer to assume the frame of a view that is connected via AL to the edges of a UIViewController's main view. Thanks for the fast response! – Crashalot Oct 15 '16 at 01:51
  • @Crashalot Yeah, that's going to be a mess. You can observe the `bounds` and `center` properties and set it there. Do not observe the `frame`. Or better, wrap that layer in a view and then the system will do it for you. – Leo Natan Oct 15 '16 at 02:06
2

I will complement rmaddy's answer. You need to decouple performing layout and updating your data. If your flow is such that data should updated as the view is about to appear, you should update your controller's view-backing data in viewWillAppear:, reload your views and then mark the view as needing layout using setNeedsLayout. This will cause the system to perform a layout on the controller's view, and will trigger the layout. This way, you can ensure the layout is performed once the view is ready, and not before (as often the case is in viewWillAppear:.

Leo Natan
  • 55,734
  • 8
  • 140
  • 186
  • This is exactly what I meant in my answer before it became clear that the goal was to add new views. I generalized that in my answer by simply stating that `viewWillAppear` should be used to handle the view becoming visible. In this case, that handling is to add new views and refresh data. – rmaddy Sep 29 '16 at 22:00
  • @rmaddy It is often confusing. I am used to people recommeding the `viewWillAppear` route of performing layout on the view, which is often incorrect. Af first, I read your answer like that. – Leo Natan Sep 29 '16 at 22:01
  • Do you need to call `layoutIfNeeded` as well as `setNeedsLayout`? – Crashalot Oct 04 '16 at 04:55
  • No. the system will know it needs to layout before display. – Leo Natan Oct 04 '16 at 10:01