Having a very weird behavior on iOS 10. Assume you have an empty app, created with a Master-Detail Application template. Place any UIView as a titleView in navigationBar for detail viewcontroller. Place any UIView to the right bar button items.
Then, write that code to configureView method:
if let item = self.splitViewController?.displayModeButtonItem {
self.navigationItem.leftBarButtonItem = item
self.navigationItem.leftItemsSupplementBackButton = true
}
Then configure splitviewcontroller preferredDisplayMode = .allVisible, so the displayModeButtonItem could appear.
On iOS 9 and lower this results in a standard behavior: the detail viewcontroller displays displayModeButtonItem expand button on a left side.
When user taps it, the icon transforms to an arrow. Tapping an arrow reverses button state.
On iOS 10 displayModeButtonItem displays as expand button, but if user taps it, it disappears.
Meanwhile, the button is still there and a user can tap it one more time. After that, displayModeButtonItem appears again with expand icon and backButtonItem icon as well. Just like it appears when we push another viewcontroller onto detail's navigationcontroller:
But in that case both of icons acts as displayModeButtonItem.
Is this an iOS bug, or a misconfig? What can I do to get normal button behavior?
Edit: I found out, that everything works as expected, if a titleView (of rightBarButtonItem's view) does not contain any constraints on it's child views. Filed a radar on this.
Edit 2: Some controls (like UIImageView) may implicitly add NSContentSizeLayoutConstraint, so, to prevent this behavior (and prevent the bug above), subclass it and override intrinsicContentSize method like this:
private class NoConstraintsUIImageView: UIImageView {
private override func intrinsicContentSize() -> CGSize {
// prevent implicit NSContentSizeLayoutConstraint adding in updateConstraints
return CGSize(width: UIViewNoIntrinsicMetric, height: UIViewNoIntrinsicMetric)
}
}
Related
I'm trying to display a large button at the bottom of the screen, so that it appears above the toolbar.
Button Overlapping Toolbar
My first attempt at this works on the iPad, and on the iPhone in Landscape mode, but the button appears behind the toolbar in Portrait mode. So, this is probably related to the difference in rendering with the Split View Controller:
addButton.frame = CGRect(x:0, y:0, width:48, height:48)
addButton.setBackgroundImage(UIImage(named:"Button - Add"), for: .normal)
let topView = self.navigationController?.view
topView?.addSubview(addButton)
I don't want it to appear above all other view controllers, e.g. popovers and modal segues, so I can't place the button on the application's topmost window, i.e. the following doesn't give me the right result either:
let topView = UIApplication.shared.windows.first
topView?.addSubview(addButton)
The only solution that works for me is to add the button to the toolbar, but this isn't great because the touch zone for the button is clipped by the toolbar:
self.navigationController?.toolbar.addSubview(addButton)
let topView: UIView? = addButton.superview
So, does anyone know of a way to place a UIView or UIControl in a layer or view above the toolbar, but still on the active viewController?
What I want to to: I want to drag down the whole view of a viewController to dismiss to the parent viewController using a pan gesture recognizer.
The Problem: When I drag the view down, the navigationBar decreases its height and does not look good. When the view returns to its original position, the navigationBar returns to the default size. I want the navigationBar to stay at its size. I also tried to use the new large titles and some other properties of the navigationController/-bar, but that did not solve it.
Note: Everything worked fine already before iOS 11.
My code:
override func viewDidLoad() {
super.viewDidLoad()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDown(_:)))
navigationController!.view.addGestureRecognizer(panGesture)
}
#IBAction func dragViewDown(_ gesture: UIPanGestureRecognizer) {
if let dragView = gesture.view {
let translation = gesture.translation(in: dragView)
dragView.center.y = (dragView.center.y + translation.y)
gesture.setTranslation(CGPoint.zero, in: dragView)
}
}
This test project only has one viewController and does not provide the dismissal, but the problem is the same as in my working project.
I also uploaded the project to GitHub: https://github.com/maddinK7/navitationBar-pull-down-problem
Does anyone have an idea how to solve this? Thanks in advance.
I want the navigationBar to stay at its size
It is staying at its size. If you check the navigation bar's bounds size height before, during, and after the drag, you will see that it remains the same (probably 44) at all times. What's changing is the drawing extension that causes the drawing of the nav bar to extend up behind the status bar. It can't do that when you pull the whole thing away from the top of the screen, because it is not at the top next to the status bar any more. iOS 11 is more strict about the way it performs this drawing extension, probably because it has to do it in a special way on the iPhone X.
So, let's make sure you're doing this correctly:
Make sure that the navigation bar has a top constraint pinned to the safe area layout guide's top, with a constant of zero.
Make sure that the navigation bar has a delegate that returns .topAttached from position(forBar:).
If you are doing both those things and it doesn't help, you'll have to implement this in some other way entirely. Making the view directly draggable like this, without a custom parent view controller, was always dubious.
When UINavigationController attached top, system will add safe area top margin in the navigation background.
(NOTICE: Background margin will not changed when offset value is between 1 and 0)
So you have to handle attached/detached top event by handle gesture offset to change the right offset and content insets.
You can try the solution in my lib example. ;)
My example include UITableViewController in the UINavigationController, so it will relatively complex.
https://github.com/showang/OverlayModalViewController
I mean, not a UITabBarItem, an UIButton. For example, in the Deezer app, the middle button shows a view with an animation that covers the entire screen. I don't want the button to be rounded. Just to execute the action.
This is a very simplistic example. You'll still have to modify it to fit your needs, but if you have a UIBarButtonItem called button that you've added to your navigationBar, you should be able to do something like this, in viewDidLoad
button.action = #selector(showView)
Then you just need to create a function to be called.
func showView() {
let myView = UIView()
self.view.addSubview(myView)
}
Of course this has no animations, but again this is just to point you in the right direction.
I'm currently facing stranges behaviours with iOS 10.
Indeed, everything was working fine, but since I updated to xCode 8 and began testing my App on iOS 10, a lot of stuff went wrong.
For example, all my Navigation Bar's titles are not displayed anymore.
More precisely, when I'm using the menu to navigate threw my app, every time I switch to a different view, there are no title, BUT when I press the back button, I can briefly see it before the transition to the other view.
I don't understand this new behaviour, I didn't change anything in the code, and when I launch the App on iOS 9 or lesser, everything is working properly.
I tried to set the title in the ViewWillAppear / ViewDidLoad, in the prepareForSegue, but nothing is working.
if ([segue.identifier isEqualToString:kMy_Segue]) {
MyViewController* destinationVC = segue.destinationViewController;
[destinationVC setTitle:#"MyTitle"];
}
I feel like there are some changes with the pushed Navigation Controller, or the title item is not at the same index than the Navigation Bar itself because when I navigate threw subviews in the menu, the title only appears when I press back.
For example I have a category in my Menu called "World".
When I tap it I want the title of the subview to be "World", but nothing is displayed. Then, I have all the continents listed "Africa", "Europe", "Asia" etc...
When I tap on Africa, I want the next title to be "Africa" right?
And again, nothing is displayed BUT if I press the back button and go back to the list of continents, the title "World" appears.
If anyone has encountered this issue, I would really appreciate some help.
Follow this:
Solution-1
Goto to the viewController, then click on NavigationItem as shown below:
In the right pane, change "Title" as follows:
Solution-2
Add a UIView to your navigationBar as shown below:
Add an UILabel to your navigationItem as shown below:
Change the frame of the newly added UILabel as follows:
Change text of UILabel to "Sample Title" and change UIView backgroundColor to clearColor.
I have made a sample in Xcode8, please check my GitHub link below:
https://github.com/k-sathireddy/NavigationItemSample
Have you try to adding navigation item on to your navigation bar? You'r using Storyboard for this design.
I found a solution to my issue. It's really weird.
I have a fade animation to the navigation bar on the Home Page.
It's a scroll view so that when I scroll down I can still see the title and the navigation bar.
On iOS < 10, the fade animation only change the background color and transparency. But when I launch the app on iOS 10, the fade effect hide everything, the menu icon, the title, everything. I believe Apple changed the structure of the navigation bar items.
Moreover, when I tap on the menu icon to navigate threw my App when the fade animation is on going, I can see that the title and the navigation bar items are under "fade effect" and even for the next ViewControllers's title after my navigation.
So it's working, it's just that the menu icon, titles etc.. are hidden on the Navigation Bar.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIColor * color = [Constants backgroundColor];
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat threshold = 250;
if (offsetY > threshold) {
[self.navigationController.navigationBar lt_setBackgroundColor:[color colorWithAlphaComponent:1]];
} else {
CGFloat alpha = offsetY / threshold;
[self.navigationController.navigationBar lt_setBackgroundColor:[color colorWithAlphaComponent:alpha]];
}
}
I guess the issue is coming from the library LTNavigationBar that I used to change the appearance of the NavigationBar dynamically...
In my detail VC, I am implementing this UISplitViewController delegate method:
- (void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc
It is then easy for me to set the title of the button, but the result is just a button with plain text, and no back arrow. I am looking to get something like the Mail app where the master popover button also has the back chevron.
How can I do this?
Create your custom bar button item, with the chevron image, set up as you want, and set the target and action of your bar button item to be that of the one passed by the delegate callback. This way, your bar button will perform the same action as the one the system passes to you. You must create your own bar button with an image, because there is no possible way with AppStore approved API to create back bar buttons.
In iOS7, the private subclasses are UINavigationItemView + _UINavigationBarBackIndicatorView. One is the button, the other - the chevron. _UINavigationBarBackIndicatorView is a subclass of UIImageView. So it's pretty close to what you will achieve.
In iOS 8, UISplitViewController has a method:
- (UIBarButtonItem *)displayModeButtonItem
If you set the returned bar button item as the left button of a navigation bar (UINavigationBar), it will display the chevron for you.
On the other hand, if you put the returned bar button item into a toolbar (UIToolbar), it will not display the chevron.
For places where I want the chevron back button shown, but also need several of my own bar button items shown (like the Mail app on iPad does), I have to use a UINavigationBar and a UIToolbar. It's an ugly solution, but I have to partially overlay the UINavigationBar on top of the UIToolbar in order to get a back button chevron along with several of my own bar button items.
To access the default back button image used by Apple as what Leo said, the arrow is a class of type _UINavigationBarBackIndicatorView. Set this image to a back bar button and you are good to go.
Here follows the hack,
UIImage *imgViewBack ;
for (UIView *view in self.navigationController.navigationBar.subviews) {
// The arrow is a class of type _UINavigationBarBackIndicatorView. This is not any of the private methods, so I think
// this is fine for the AppStore...
if ([NSStringFromClass([view class]) isEqualToString:#"_UINavigationBarBackIndicatorView"]) {
// Set the image from the Default BackBtn Imageview
UIImageView *imgView = (UIImageView *) view;
if(imgView){
imgViewBack = imgView.image ;
}
}
}
This is based on Ryan Henning's answer.
It's possible to show a back button representing the master view from the detail view controller. The UISplitview controller doesn't provide a native method for this, but it returns a bar button object which we can directly assign as navigation controller bar button. Its obvious that the detail view controller should be inside a navigation controller for this to work.
self.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;