NOTE This note is written based on Xcode version 9.0 beta 6 (9M214v) and its simulator binary.
iOS 11 changes UINavigationBar
a lot, not just only for its large title, but also the internal view hierarchy and lay outing views are changed. This is a small note about UINavigationBar
behavior on iOS 11, mainly focusing on migrating the application to iOS 11.
UINavigationBar
has been using manual lay outing until iOS 10, so all its content views like titleView
has been directly child view of the UINavigationBar
. However, since iOS 11, it is using auto layout with bunch of layout guides to lay out its content views in its own internal container view, _UINavigationBarContentView
.
The view hierarchy loosk like this.
UINavigationBar
| _UIBarBackground
| | UIImageView
| | UIVisualEffectView
| | | _UIVisualEffectBackdropView
| | | _UIVisualEffectSubview
| _UINavigationBarLargeTitleView
| | UILabel
| _UINavigationBarContentView // ← All content views goes in here.
| | // There are many layout guieds for title view, buttons etc.
| | _UIButtonBarStackView
| | | _UIButtonBarButton
| | | | _UIModernBarButton
| | | | | UIButtonLabel
| | _UITAMICAdaptorView // ← Only when `titleView` doesn't have `intricinsicSize`.
| | | MyTitleView // ← This is where your `titleView` goes.
| _UINavigationBarModernPromptView
| | UILabel
Because of this, the titleView
which deson't support auto layout, in another words, doesn't have intrinsicContentSize
, will be encupsulated by the internal adapter view (_UITAMICAdaptorView
) to make it works in auto layout environment.
So, the traditional lay outing — like assigning a frame same as UINavigationBar
bounds and set autolayoutMask
is no longer working, instead, the titleView
needs to have an explicit intrinsicContentSize
or sizeThatFits:
to tell that adapter view to give a right frame
to it.
A simple implementation that enforces the titleView
to fit entier title view area can be done by adding next to it.
- (CGSize)intrinsigContentSize
{
return CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
}
By disassembling current UIKit
binary, looks like setLeftBarButtonItem:animated:
or setRightBarButtonItem:animated:
call an internal lay outing code somehow, twice, and it may cause a trouble with buttons.
To workaround this behavior, always use setLeftBarButtonItems:animated:
or setRightBarButtonItem:animated:
with @[barButtonItem]
instead. I think it's an iOS 11 implementation bug.
These setLeftBarButtonItem:animated:
, setRightBarButtonItem:animated:
, setLeftBarButtonItems:animated:
, and setRightBarButtonItems:animated:
are not really animating items at all.
I think it's also an iOS 11 implementation bug, because in the chain of call from these mtehods are evenatually calling updateTopNavigationItemAnimated:
, however, the current implementation is like this.
-[_UINavigationBarVisualProviderModernIOS updateTopNavigationItemAnimated:]:
0000000000be5708 push rbp
; Objective C Implementation defined at 0x13ff050 (instance method), DATA XREF=0x13ff050
0000000000be5709 mov rbp, rsp
0000000000be570c mov rsi, qword [0x147f030]
; @selector(setupTopNavigationItem), argument "selector" for method _objc_msgSend
0000000000be5713 pop rbp
0000000000be5714 jmp qword [_objc_msgSend_11195c8]
; _objc_msgSend
Obvisouly, this method ignores given animated
argument and simpley calls setupTopNavigationItem
which doesn't take any arugments. Thus, animated
flag is ignored at this point.
Because of this implementation, seems like, in some cases, UIBarButton
sometimes remains pressed state.
I cheated with a workaround to fade OUT the navigation bar items I had, but this code may not be App Store safe. The context of this app is that a user can tap the space below the navigation bar to fade in/out the navigation bar and toolbar.