Auto Layout with UINavigationBar and UIBarButtonItem - uinavigationbar

I like to create my views as standalone Xib files then instantiate them and add as subviews.
So, when working with a UINavigationBar, I expected to be able to do the same thing, first creating my custom view - from the Xib - then adding it as a custom view to the UIBarButtonItem:
UIBarButtonItem *anItem = [[UIBarButtonItem alloc] initWithCustomView:_myCustomView];
Then adding to the nav bar:
self.navigationBar.topItem.rightBarButtonItems = #[ anItem, anotherItem ];
So far so good.
Now, _myCustomView uses Auto Layout (AL) and I thought this would be no problem. Not the case. I've tried just about everything. Nothing has worked. I even tried adding the custom view as a subview of the controller that has the navigation bar. Thinking that as siblings in the view hierarchy, AL would treat it as a regular view outside the UINavigationBar.
That didn't work either. The controller's updateViewConstraints was called but never applied. The view's initial frame stayed at CGRectZero. It's as if AL sees that the view is on top of a UINavigationBar, even as a sibling, and decides it doesn't need laying out.
Of course, I've tried bringSubviewToFront, translatesAutoresizingMaskIntoConstraintstranslatesAutoresizingMaskIntoConstraints, and so on. The latter gave the lovely:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Auto Layout still
required after executing -layoutSubviews. MyNavigationBar's
implementation of -layoutSubviews needs to call super.'
So, the question is, has anyone loaded a custom view with AL from a Xib, and successfully set this as a customView on a UIBarButtonItem? If so, how?

Looks like a duplicate of iOS Autolayout and UIToolbar/UIBarButtonItems
UIBarButtonItems do not inherit from UIView, so using auto-layout based custom views within them might not be fully supported. Sounds like you have to do the pre auto layout thing until the issue is resolved. I would suggest filing a bug.
EDIT: By the pre auto layout thing, I mean manipulating the frame of your views directly.

Related

UICollectionViewLayout shouldInvalidateLayout(forBoundsChange:) Method Not Called on Device Rotation if View Controller Is In a UINavigationController

I am facing an issue where the shouldInvalidateLayout(forBoundsChange:) method of a UICollectionViewLayout subclass is not getting called on device rotation, but only when the view controller with the collection view is embedded inside of a UINavigationController. If I remove the root view controller from the navigation controller and show it on its own, this method gets called and my layout is able to then correctly invalidate and update for the new device orientation.
Here's a sample project I created to show the issue. The first tab has just the view controller displayed and you can see by rotating the simulator that shouldInvalidateLayout(forBoundsChange:) is called. The second tab has the same view controller embedded in a navigation controller and on rotation you can see in the console that shouldInvalidateLayout(forBoundsChange:) does not get called. The actual project that I'm seeing this occur in uses a slightly more complex subclass of UICollectionViewLayout, but poses the same issue as seen here with a simple subclass of UICollectionViewFlowLayout. Since our project's layout requires slightly more heavy computations, we rely on shouldInvalidateLayout(forBoundsChange:) returning true and invalidating the layout before recalculating all of the layout attributes. Since this is never called, it causes our cells to remain the same width after rotation instead of updating for the new width.
This behavior is very strange and seems undocumented to me from what I have seen. Does anyone have any insights as to why the navigation controller causes this change and how to modify it so that shouldInvalidate is called on rotation?
Some things I've tried (non-exhaustive list):
Various combinations of hiding and showing the navigation bar/toolbar to see if they have any effect on the bounds of the view on rotation).
Verifying that the bounds of the collection view change on rotation. They do, and you can see the updated bounds when debugging the view hierarchy in Xcode.
Creating the interface entirely programmatically and removing the storyboard. The issue still persists.
Verifying that this issue occurs on different device types. I've tested this on both iPhones with notch/home indicator safe area insets and those without and see the same issue.
Using a different collection view layout. The issue is present for both UICollectionViewFlowLayout and a custom UICollectionViewLayout subclass.
Some similar issues I've come across/read (again, a non-exhaustive list since I've been at this so long):
https://gist.github.com/NeilsUltimateLab/21d551126f0f03b11a0154a681a48e71
https://www.reddit.com/r/iOSProgramming/comments/bd0yh6/what_are_all_the_conditions_that_trigger/
How to properly rotate viewcontrollers in UINavigationController?
https://github.com/radianttap/Fields/issues/3
Thanks for any insights in advance!

TLYShyNavBar extension view doesn't work with Storyboard

I'm using the TLYShyNavBar library for my Table View Controller in order implement the navigation bar dynamic contraction when scrolling the table elements. I would like to have an extension view containing a Search Bar plus two additional labels underneath it. Following the examples reported in the guide, the extension view is generated by code (which works fine):
[self.shyNavBarManager setExtensionView:self.toolbar];
but when I use the Storyboard for creating this view the contraction doesn't work properly (To be precise the nav bar contraction behaves correctly but the extension view just disappears where it should instead be contracting progressively).
I would prefer not generating this view programmatically as this makes defining the Auto Layout constraints much more complicated (a bit easier using the Storyboard instead).
How are you providing the extension view to self.shyNavBarManager? If you are using an IBOutlet it will already be part of the view controller's view hierarchy, so you'd need to remove it from that first. Also if you are using Auto Layout in your Storyboard then you will need to turn on autoresizing mask translation for the extension view before you add it to the bar manager:
[self.toolbar removeFromSuperview];
[self.toolbar setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.shyNavBarManager setExtensionView:self.toolbar];
This is still an issue in the latest version of TLYShyNavBar. The solution below works:
topTrayView.translatesAutoresizingMaskIntoConstraints = true
shyNavBarManager.extensionView = topTrayView

UITextView and UILabel Subview Properties are nil

In one of my view controllers, I've got the following structure:
+-View
+-Scroll View 1
+-UIView
+-Scroll View 2
...and I'm adding three subviews into Scroll View 2, one of which contains a series of UITextViews and UILabels as a form-like structure.
The problem I'm seeing is that when I try to access the properties that refer to those controls from the view controller level, they are coming up nil. However, if I set a breakpoint in awakeFromNib inside the UIView that contains the UITextFields and UILabels, the properties are valid... it's almost like the reference disappears.
I can clearly see that they exist, and if I do the following in the debug console after setting a breakpoint, I definitely get the view graph output to the console:
po [self.profileEditView recursiveDescription]
Does this sound familiar to anyone, and if so, how can I remedy it?
One thing I'm thinking of doing is just not trying to access those properties directly, since the goal is to get the view controller to be the delegate of those UITextView controls so it can respond to becoming the first responder by adjusting Scroll View 1's position when each text view gets focus. It's just more of a hassle, but if that's what I have to do I'll go that route.
I've just never seen this happen before so I thought it was pretty strange. There's definitely a lot going on in this view controller.
Additional Details:
The View Controller is defined in a Storyboard.
The UIView subviews added at runtime have XIBs that I load programatically.
I discovered what the problem is... I was inadvertently instantiating another subview as a subview inside initWithFrame (don't ask me why I was doing that... I think I was under the impression that I needed to do that to instantiate the XIB at that point), so the properties on the nested subview were valid but the properties of the top-level view (the one contained in Scroll View 2) were all nil.
I had a five-minute conversation with a developer in NZ and he asked about what I was doing there and it became readily apparent that what I needed to do was instantiate the view and its NIB in the view controller with:
ProfileEditView *view = [[[NSBundle mainBundle] loadNibNamed:#"ProfileEditView" owner:nil options:nil] objectAtIndex:0];
...instead of instantiating it with initWithFrame.
Thanks for the help, however!

Runtime error setting backroundView property of UITableViewCell in storyboard

In storyboards using a static content tableview. I've added a tableview cell. In it I've added a view and a label. When I assign the view as the cell's backgroundView (ctrl+click & drag from the cell to the view) I get the following error at run after loading the tableview.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableViewCell's implementation of -layoutSubviews needs to call super.'
If I remove the association, it runs without issue.
Has anyone had any experience with this error?
I'm going to make a guess that is the one rare time where a view is treated as being included twice in a view hierarchy. When layout is performed, it goes through the normal view heir achy and lays out the view, but then (or before that) it lays out the background view hierarchy as well. This would HAVE to result in a layout error.
The solution? Growl at apple and ignore it. Disconnect the view from the backgroundView property and just deal with it as a view. This works for me. I don't see any great advantage in connecting it.

iOS: setFrame no longer working from viewDidLoad or viewWillAppear

I prefer to disable autoresizesSubviews and to use setFrame to place all of my subviews.
As of iOS 6, things seem to have changed a lot.
When I call setFrame on a view in viewDidLoad, there is no effect. I tried it from viewWillAppear; same thing.
A setFrame call will work in willAnimateRotationToInterfaceOrientation, but that is not called initially like it was in iOS 5.
Can someone clarify please where I am expected to layout my views from?
I guess you want to layout your views from UIViewController. Have you tried performing your layout tasks in viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews
According to Apple's documentation:
Your view controller can override this method to make changes after the view lays out its subviews.
Since your view itself does not do layout its subviews (you do not use autosize, autolayout or layoutSubViews:) you can do the layout tasks in this method.
Nevertheless, the elegant way would be to use a custom parent UIView and perform all the layout there, overriding UIView's layoutSubViews: (unless you add/removes views dinamically). Quote from Apple's documentation on "How View Controllers Participate in the View Layout Process":
Ideally, the views themselves perform all of the necessary work to reposition themselves, without requiring the view controller to participate in the process at all. However, if the view controller adds and removes views dynamically, a static layout in Interface Builder may not be possible. In this case, the view controller is a good place to control the process, because often the views themselves only have a limited picture of the other views in the scene.
You can use setFrame in viewDidLoad if you uncheck the Autolayout option in Interface Builder. If you need to use auto layout, You need to perform your layout tasks in viewDidLayoutSubviews:.
Regarding "Asking the views to lay themselves out doesn't make sense to me. Views don't have authority to say where they should go. That's the role of the controller, isn't it?". I think the answer is often negative. It's the parent view (not view controller) which usually plays the role of laying out it's own subviews. It does so by utilizing autoresizingMask or autolayout (iOS 6 only). Or You can make layout programmatically by overriding -layoutSubviews method of the parent view.
Sure, like Imre mentioned, controller can participate in the layout process as well by overriding - (void)viewDidLayoutSubviews. I often use this approach when I don't want to subclass the top level view of the view controller for keep things simple.
Finally, I found this post super useful. Even with some minor errors, the post provides a big picture of view layout process.

Resources