viewDidLayoutSubviews stuck in infinite loop for blank method - ios

I have a view controller that is setup with a UITableView in UIStoryboard. The UITableView is constrained to the SafeArea of the controller on all four sides.
It may seem that this question has been asked before, but each time I have found the question, it is because there are changes taking place in viewDidLayoutSubviews that cause the loop to occur. I have a blank implementation of viewDidLayoutSubviews and basic UITableViewCells (no nib). Yet in iOS 11, Xcode 9.2, flicking the tableview up causes viewDidLayoutSubviews to be called on an endless loop. This is my implementation:
- (void)viewDidLayoutSubviews {
NSLog(#"viewDidLayoutSubviews");
[super viewDidLayoutSubviews];
}
I cannot figure out for the life of me how this is happening.
Edit: The controller has a NavigationController and is the Master controller of a UISplitViewController, which is embedded in a BannerViewController (from iAdSuite example).
Edit: This happens on iOS 11 (up through 11.2) on ALL devices on the simulator. Does NOT happen iOS 10.3. viewDidLayoutSubviews isn't even called in iOS 10.3. I get the same results when I simply use the Nav Controller with the UIViewController/TableView in place of the UISplitViewController/BannerViewController. So seems that the issue is not introduced there, but from something that is happening in iOS 11.
EDIT: So this is odd - seems it is happening only when large titles are selected for the navigation controller!
if (#available(iOS 11.0, *)) {
self.navigationController.navigationBar.prefersLargeTitles = YES;
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeAlways;
}
EDIT: This problem can be easily reproduced. I downloaded Apple's iAd Suite sample code, and used the SplitNavigationController project. I updated the project for iOS 11, including updating the storyboard to use Safe Area Layout Guides. I changed the MasterViewController from a UITableViewController to a UIViewController with a UITableView, which I constrained to the Safe Area layout guides in the storyboard. I added in prefersLargeTitles and largeTitleDisplayMode to the MasterViewController. I added in the viewDidLayoutSubviews method, with a NSLog statement, so this behavior can be easily observed. I think this is a real problem with iOS 11, and I will try to get to the bottom of it, and I would also appreciate if anyone else can offer any thoughts or suggestions.
EDIT: matt was able to reproduce this issue and post it in a GitHub project, to be found at the following link (thanks matt!): InfiniteLayoutSubviewsBug

EDIT This was definitely a bug in iOS 11 thru at least iOS 11.2, but in iOS 12 the bug no longer exists.
It looks like you've found a bug. I was able to reproduce the problem in a minimal example project, which I've posted at GitHub here:
https://github.com/mattneub/InfiniteLayoutSubviewsBug
Download the project and run it. You'll see a simple table with three rows. Scroll the table view up and let go. Watch the console. We are getting repeated calls to viewDidLayoutSubviews, once every 1/60 of a second, forever.
Note that this is not caused by a recursive layout loop in our code. My example doesn't even call super. All it does is log. This is the runtime itself calling viewDidLayoutSubviews repeatedly forever. No code of ours (except print) runs while this is happening.
Other observations:
As you rightly observed, if you change the example so that the navigation bar doesn't use large titles, the problem goes away.
If you change the example so that the table view is not positioned using autolayout, the problem goes away.
If you change the example (rather more elaborately) so that the view controller is a UITableViewController subclass, the problem goes away; we get some repeated calls to viewDidLayoutSubviews but not forever, just while scrolling.

Related

Who should manage the UI Elements, the view or the view controller?

I am currently developing an iOS app and I was wondering how to manage the UI elements. I am using a Storyboard to place my views and Autolayout to make everything resolution independent. If it helps, here's a bit of background on how I came up with this question.
Some Backgroud
I have buttons the represent piano keys and I later in code add a subview to these that draws the actual keys on them. I did this by creating #IBOutlets on the ViewController and susbscribed to the UIDeviceOrientationDidChangeNotification. This called a method that adds the subviews based on the current button frames to get the appropriate sized keys.
This worked fine when running in iOS 8.1, but if I ran the app on iOS 7, the frames of the buttons weren't updated to the new orientation by the time I got the UIDeviceOrientationDidChangeNotification, so everything was messed up in landscape. I did a little research (UIInterfaceOrientation not yet updated when UIDeviceOrientationDidChangeNotification caught in UIView) and it seems that a good solution would be to override the layoutSubviews method. Problem is, layoutSubviewsis a UIView method, and I am managing my buttons and UI in my UIViewController.
Impulsively I wanted to subclass my UIView, override layoutSubviews and from there call a method on my Controller to add the keys to the buttons correctly, but that doesn't sound right.
The Questions
My UI is managed by the UIViewController. But I need to update my views based on a method proper of a UIView. I assume, to respect the MVC principles, that my view should't now a thing about my controller, but then this brings up a few questions:
Who should be managing the UI?
Is my strategy wrong and should the
UIView hold the #IBOutlets to the buttons so that it can later
apply the subviews I need them to have?
If you read the background, do you have a suggestion for this particular situation?
I assume the complexity of this problem is relatively small and I don't need to setup notifications in NSNotificationCenter, but I may be wrong. I would really like to hear this is not the only solution.
Thanks in advance. I hope I was clear enough, but if you want/need any additional details on my particular situation, let me know and I will gladly elaborate.
if I understood the question correctly, your ViewController should be laying out the keys within it's view. A good, but not the only, place to do this is in viewDidLayoutSubviews which a method you can override on UIViewController.
Now if your views should maintain their internal layout. i.e if you buttons/keys have any subviews, you should update those in layoutSubviews on UIView as you mentioned.
Both methods will be called in response to changes in the bounds or center of your parent view.
As far as rotation changes are concerned, in iOS 8, you should use viewWillTransitionToSize:withTransitionCoordinator: or willTransitionToTraitCollection:withTransitionCoordinator:
depending on your needs.
I highly recommend that you write your layout independent of orientation. You should just use the containing view's bounds for reference and never hard code in any frames. For example perhaps one of your keys should be 1/56 the width of it's superview instead of a magical number for portrait and landscape.

View is pushed down by about 20px, but is fixed when I rotate screen

I have a viewcontroller(Nib) that is invoked from two different places. When I invoke it from one place, all looks fine but from the other it always seems to have a 20px(looks like enough for the status bar) offset up top..
If I rotate the device this goes away and it looks OK..I've been troubleshooting this since yesterday with no luck - As far as I can tell I'm not doing anything different when I load the screen..
I'm not really sure what code to paste, as from both places they are being called in the same way, I'm looking for any ideas - even if it's not an answer as I seem to be out of ideas.
Screenshot,
Did you try setting edgesForExtendedLayout in viewDidLoad?
if ( IS_OS_7_OR_LATER && [self respondsToSelector:#selector(edgesForExtendedLayout)] ) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
EDIT: here is the iOS 7 Transition Guide from Apple if you want more info on how this is fixed for iOS 7
I hope that these questions will help us and you to pinpoint the problem:
Are you invoking the VC using Interface builder or programmatically?
Have you added any or none constraints?
My best guess would be that you try to load the VC from:
- (void)viewWillLayoutSubviews
rather then
- (void)viewDidLoad
The reason for this suggestion is that the screen bounds are not properly loaded in viewDidLoad which you can check by printing self.view.bounds.width & self.view.bounds.height. In viewDidLoad they are not set correctly. This can lead to many later problems. This is just a wild guess.
Hope it helps.

UISearchDisplayController's full-screen background intercepts touch events in iOS 7

I have a UITableview that doesn't take up the whole screen (screenshot). Everything worked fine in iOS 6. But in iOS 7, when the user searches, the search result table takes up the whole view (screenshot).
To fix this, I tried setting the frame manually as described in this answer. The appearance is now correct (screenshot), but now the "<" button in the top left doesn't receive tap events when the search results table is displayed.
It seems the searchResultsTableView is adding a full-screen background view that is intercepting touch events. To prove this, I added this code to didShowSearchResultsTableView:
controller.searchResultsTableView.superview.backgroundColor = [UIColor blueColor];`
This screenshot confirms my hypothesis.
How can I fix this to allow the "<" button to receive tap events?
I want to avoid modifying controller.searchResultsTableView.superview so that my change doesn't break in future versions of iOS.
And what change in iOS 7 caused this behavior to start happening?
I am still searching for a better solution, but currently my solution is in the viewControllers viewDidLayoutSubviews tell your view to move to front. The code would look something like this.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self.view bringSubviewToFront:self.navigationBar];
}

wrong frame size in viewDidLoad [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why am I having to manually set my view’s frame in viewDidLoad?
I have universal app for iphone and ipad, two storyboards, if I run my app on the iphone simulator in viewDidLoad every frame of each element in nib is correct. But if I do it for ipad (simulator) in frame is nil, but the screen looks correct. Could you please advise me smth? Thanks.
It's a very similar question to ios setContentOffset not working on iPad ... And my answer is the same as there:
"You should not initialise UI geometry-related things in viewDidLoad, because the geometry of your view is not set at this point and the results will be unpredictable.
... viewWillAppear: is the correct place to do these things, at this point the geometry is set so getting and setting geometry-related properties makes sense."
This also applies to getting geometry-related properties. Don't get distracted by it's behaving correctly in one circumstance and not in another... The main point is that setting/getting geometry properties too early will yield unpredictable results.
Your screen looks correct because you are seeing the results after viewWillAppear: / viewDidAppear:.
update (ios6)
When using autolayout in ios6, frames are not set until subviews have been laid out. The right place to get frame data under these conditions is in the viewController method viewDidLayoutSubviews.
This is called after viewWillAppear:. But take care - they are not always called together. For example, viewDidLayoutSubviews is called after a rotation, viewWillAppear: is not. viewWillAppear: is called every time a view becomes the visible view*, viewDidLayoutSubviews not necessarily (only if the views needed relaying out).
(* unless the app was backgrounded and is becoming the foreground app, then viewWillAppear: is not called)
I figured it out why it happened. I was using AutoLayout. When I uncheck it in the storyboard then the frames are correct.

UIView with UIButtons not showing up prior to click or rotate

I've been banging my head with this issue for the last two days. Googled a lot but wasn't able to find the answer yet, so I decided to request some help here. Let's see if I get any luck.
I'm coding a reusable control that consists of an UIView with a variable number of customized UIButtons. I implemented initWithFrame:, initWithCoder: and drawRect: where the buttons (which are built prior to drawing) are actually added to the view. Everything is done programmatically since the UIButton content should be supplied when using the control, so there's no XIB for this UIView.
This UIView, let's call it CustomizableBarButton is then used in an UIViewController, let's call it MyTestViewController with a view on it, let's call it customizableBarButtonView.
MyTestViewController's GUI was set on IB where an UIView was tied to customizableBarButtonView (the class was matching accordingly).
MyTestViewController's is a pretty standard class except for the viewWillAppear: that initializes the buttons and passes them to the CustomizableBarButton along with some other options.
The issue is that everything works perfectly...except for the first time!
I mean, when I run the app on the simulator (I haven't tried it on the iPhone yet but I strongly believe that it's not an hardware issue) the MyTestViewController shows the customizableBarButtonView background but not the buttons. Now when you click on the place where a button should be all the buttons suddenly appear!
I'm puzzled since the CustomizablebarButton drawRect: runs before the strange "click n'appear" effect and the buttons are actually added to the subview.
Another hint that my help: if you don't click on the buttons (so you still got no buttons yet) but rotate the device they will also appear as if by magic!
It is probably something very simple but I'm missing it and I'm going nuts...
Can someone lend a hand on this, please?
Thanks in advance!
You said you're adding the buttons in drawRect:. Don't do that. You need to add the buttons in your init methods.

Resources