8

I'm having a problem with the prompt on a UINavigationItem that I just can't resolve...

I have a master and a detail view controller. When I push from the master to the detail a prompt is shown on the detail view controller:

prompt

However, when I pop back to the master view controller, the view isn't resized and the window shows through (the window has been coloured red):

window

This only happens on iOS7, on iOS6 the view resizes as expected.

I've tried a few things such as setting the prompt to nil in viewWillDisappear or viewDidDisappear but nothing seems to fix it.

If I set the navigation bar in the navigation controller to translucent it does fix this - unfortunately that's not an option.

I've created a very small example project here which demonstrates the issue: https://github.com/InsertWittyName/NavigationItemPrompt

Thanks in advance for any help!

InsertWittyName
  • 3,930
  • 1
  • 22
  • 27
  • Quite possible (likely even) that this is an iOS bug. Have you checked 7.1? – Kevin Mar 03 '14 at 18:47
  • Please pause the application after reproducing and print `po [[UIApplication sharedApplication].keyWindow recursiveDescription]` – Léo Natan Mar 03 '14 at 18:49
  • Have you tried using Autolayout? – Audun Kjelstrup Mar 03 '14 at 18:52
  • 1
    @AudunKjelstrup Autolayout won't help, as this is the topmost view of the view controller, and navigation controller does not use constraints to layout its views. – Léo Natan Mar 03 '14 at 18:56
  • Upvoted for putting the example on github. – matt Mar 03 '14 at 19:19
  • Have anyone tried to run the project in new xcode beta? I also get this message in logs: http://stackoverflow.com/questions/19103071/why-am-i-getting-an-uibarbuttonitem-customization-warning – vokilam Mar 03 '14 at 19:25
  • I have sent a pull request demonstrating a workaround for this bug. See https://github.com/mattneub/NavigationItemPrompt – matt Mar 03 '14 at 19:43
  • I updated my pull request to include labels at the top of the interface, proving that the interface works correctly (after the stupid nav bar resizing has taken place) – matt Mar 03 '14 at 21:46

5 Answers5

3

A solution I can think of is to subclass the UIView of the master, and implement viewDidMoveToSuperview to set the frame of the view to be from the navigation bar's height to the end of the superview. Since the navigation bar is not translucent, your job is easier, as you don't have to take into account layout guides and content insets.

A few things to notice. When pushing and popping, the system moves your view controller's view into another superview for the animation and then returns it to the navigation controller's private view hierarchy. Also, when a view goes outside of the view hierarchy, the superview becomes nil.

Here is an example implementation:

@interface LNView : UIView

@end

@implementation LNView

- (void)viewDidMoveToSuperview
{
    [super viewDidMoveToSuperview];

    if(self.superview != nil)
    {
        CGRect rect = self.superview.bounds;

        rect.origin.y += 44;
        rect.size.height -= 44;

        [self setFrame:rect];
    }
}

@end

This is not a perfect implementation because it uses a hardcoded value for the navigation bar's height, does not take into account a possible toolbar, etc. But all these you can add as properties to this view and in viewDidLoad, before it starts going into the view hierarchy, set the parameters according to your needs.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195
  • Thanks for the suggestion. I tried this (overriding `didMoveToSuperview`) however it left the view in an inconsistent size after popping back. How would you suggest I try your alternate 'hide the prompt after the view controller is popped' solution? – InsertWittyName Mar 03 '14 at 20:07
  • @InsertWittyName Can you explain inconsistent size? I did this in our project and it worked, and our navigation bar was translucent. I removed my other suggestion, I checked and the prompt is apparently linked to the navigation item. – Léo Natan Mar 03 '14 at 20:10
  • I assume you mean not translucent. When the view was resized it wasn't the correct size, it was under the nav bar (I tested this by putting a red view on top of the view controller's view, which was a few pixels smaller on all sides). Please feel free to fork the code to demonstrate the fix working. – InsertWittyName Mar 03 '14 at 20:18
  • 1
    Thanks. Initially this still didn't resize the view correctly (using the method in my previous comment) ... however if I change the values from 44 to 64 it works perfectly! Accepting as the answer, thanks again! I can't award the bounty yet, but it will be awarded in 21 hours :) – InsertWittyName Mar 03 '14 at 20:52
  • @InsertWittyName 64 means status bar + navigation controller. So your view controllers are still extended, just not transparent. Just remember, 64 is the correct value for portrait. In landscape, the navigation bar is shorter. So if you pop in landscape, it should be less. – Léo Natan Mar 03 '14 at 20:54
  • Yes I'm well aware of the danger with having a constant. I will of course replace it with the appropriate, calculated, value :) – InsertWittyName Mar 03 '14 at 21:00
  • @LeoNatan the project is now 404. new link? – elcuco Aug 06 '14 at 13:55
  • @elcuco sorry, I no longer have the project. But my answer explains the workaround. – Léo Natan Aug 06 '14 at 16:30
2

You can remove the prompt when the user taps the back button, like this

override func willMove(toParentViewController parent: UIViewController?) {
    super.willMove(toParentViewController: parent)
    if parent == nil {
        navigationItem.prompt = nil
    }
}
Elijah
  • 8,381
  • 2
  • 55
  • 49
1

The problem exists whether your nav bars are opaque or translucent. It sucks that Apple has allowed this heinous bug to plague us for over three years now.

All of these solutions are hacks. My solution is to either A) NEVER use prompts, or B) use them in every single view even if you have to set them to "".

Scenario
  • 296
  • 3
  • 6
0

You've given the answer yourself - brilliantly. It's a bug, but checking Translucent avoids the bug. Therefore the solution is to check Translucent and then compensate so that the nav bar looks the way you want.

To do so, make a small black rectangle image and include it in your project. Set the background image of the nav bar to this image. Check Translucent. Problem solved! The nav bar is now black opaque in appearance, but it no longer exhibits the bug.

enter image description here

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 2
    I repeat - you've analyzed the heck out of this bug. Please submit a bug report to Apple using your example project. – matt Mar 03 '14 at 19:33
  • 1
    I really appreciate the effort matt, however I can't have the nav bar set as translucent as it affects how each individual view on subsequent view controllers is laid out (i.e. they're underneath the nav bar). – InsertWittyName Mar 03 '14 at 19:53
  • I did try playing with the values in `edgesForExtendedLayout` but it didn't work as expected. Please show me the changes if you can get it to work using this method. – InsertWittyName Mar 03 '14 at 20:13
  • I'm not seeing what the problem is. I put labels at the top of the views and they are correctly placed. Sure, the view controller's view as a whole is under the nav bar, but so what? As long as what is in the view is visible and correctly placed, you're fine. – matt Mar 03 '14 at 20:17
  • You are right about `edgesForExtendedLayout` - it just aggravates the bug! – matt Mar 03 '14 at 20:18
  • Look, you need to accept that this is a bug and is not going away. Anything you do will be a workaround and will have shortcomings. Of course the real solution would be to lose the prompt. – matt Mar 03 '14 at 20:19
  • 1
    Yes I have accepted that, but I also need a workaround that works! – InsertWittyName Mar 03 '14 at 20:20
  • @InsertWittyName Nooo, why did you remove the `goto` comment? – Léo Natan Mar 03 '14 at 20:31
  • @LeoNatan matt posted again so it was out of context, glad you saw it though :) – InsertWittyName Mar 03 '14 at 20:36
  • I still think my way also works! I haven't seen why it doesn't. – matt Mar 03 '14 at 21:39
  • Does this only work w/ black? Trying with another color an always end up with a transparent nav, even when the back image is opaque. – lostintranslation May 23 '16 at 20:02
0

Swift version:

class PromptViewSideEffect: UIView {

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    if let superview: UIView  = self.superview {
        let rect: CGRect = superview.bounds
        rect.origin.y += 44
        rect.size.height -= 44
        self.frame = rect
        }
    }
}
odemolliens
  • 2,581
  • 2
  • 32
  • 34