chrismiles / CMPopTipView

Custom UIView for iOS that pops up an animated "bubble" pointing at a button or other view. Useful for popup tips.

Home Page:https://github.com/chrismiles/CMPopTipView

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

When using 'presentPointingAtBarButtonItem:' from a UINavigationBar on a ViewController in a Tabbed app, the poptip is visible on all tabs

funnel20 opened this issue · comments

Situation
The app is build as a Tabbed App, so it has an UITabBarController as root view controller.
Each Tab has it's own view controller.
On the 4th tab we have an UITableViewController with an UINavigationBar with left and right UIBarButtonItems.
We've created an IBOutlet infoButton and present the pop tip by calling presentPointingAtBarButtonItem:animated::

[_poptipForManual presentPointingAtBarButtonItem:_infoButton
                                        animated:YES];

The pop tip is correctly shown:

screen shot 2018-03-01 at 10 08 47

However, when selecting another tab via the Tab Bar, the pop tip is still visible:

screen shot 2018-03-01 at 10 09 02

Analysis
Method presentPointingAtBarButtonItem:animated: determines the container view of the UIBarButtonItem and calls presentPointingAtView:inView:animated: with it.
The problem is it uses:

UIView *containerView = targetView.window;

Where the app window is returned. This is the reason why the pop tip is visible on all tabs in a Tabbed App.

Solution
The proper container view is the superview of the parent view of the UIBarButtonItem.
The (public) parent view of an UIBarButtonItem is one of:

  • UINavigationBar
  • UIToolbar
  • UITabBar

So the first step is to search upwards in the view hierarchy to determine whether the Bar Button is located in a Navigation Bar, Tool Bar or Tab Bar.
When a Navigation Bar, Tool Bar or Tab Bar is found, return it's superview and abort the search.

When no Navigation Bar, Tool Bar or Tab Bar is found, the default strategy is to use the app window (existing situation).

Here is my updated code for this method:

- (void)presentPointingAtBarButtonItem:(UIBarButtonItem *)barButtonItem animated:(BOOL)animated {
    UIView *targetView = (UIView *)[barButtonItem performSelector:@selector(view)];
    UIView *containerView = targetView;
    
    // Search upwards in the view hierarchy to determine whether the Bar Button is located in a Navigation Bar, Tool Bar or Tab Bar:
    while (containerView != nil) {
        // Get super view:
        containerView = containerView.superview;
        
        // Check Class:
        if ([containerView isKindOfClass:[UINavigationBar class]] ||
            [containerView isKindOfClass:[UIToolbar class]] ||
            [containerView isKindOfClass:[UITabBar class]]) {
            // When a Navigation Bar, Tool Bar or Tab Bar is found, return it's superview and abort while-loop:
            containerView = containerView.superview;
            break;
        }
    }
    
    // When no Navigation Bar, Tool Bar or Tab Bar is found:
    if (nil == containerView) {
        // Get the Bar Button's window:
        containerView = targetView.window;
        
        if (nil == containerView) {
            NSLog(@"Cannot determine container view from UIBarButtonItem: %@", barButtonItem);
            self.targetObject = nil;
            return;
        }
    }

    self.targetObject = barButtonItem;

    [self presentPointingAtView:targetView
                         inView:containerView
                       animated:animated];
}

Validation
This has been tested and now the pop tip is only shown on the view controller of the specific tab, which is the intended behaviour.

An added benefit is that this also works when presenting the pop tip from a Tab Bar button (this can be added to the ReadMe).
In this case the intended behaviour is that the pop tip is always visible, no matter which tab is selected.
Because the new code uses the superview of the UITabBar, this is the case.
In this example the pop tip is always presented from the 4th tab, even if it's not the selected tab:

[_poptipForConnectedTv presentPointingAtBarButtonItem:(UIBarButtonItem *)[self.tabBarController.tabBar.items objectAtIndex:3]
                                             animated:YES]; 

screen shot 2018-03-01 at 10 32 57

screen shot 2018-03-01 at 10 33 02

@kleinlieu What do you think of my analysis?

I've got the same issue, where I have pop-up ViewControllers - tooltips for the NavigationBar in the background view still shown on top of everything.

@NikolayEntin It seems this project isn't under active development anymore, as the last commit is from 7 months ago.
You can manually replace the existing code in presentPointingAtBarButtonItem:animated: by my code from above.
Does that solve your issue?

@funnel20, thank you, I've actually reverted the code to previous state, instead of:

    UIView *targetView = (UIView *)[barButtonItem performSelector:@selector(view)];
    UIView *containerView = targetView.window;

I use old one:

    UIView *targetView = (UIView *)[barButtonItem performSelector:@selector(view)];
    UIView *targetSuperview = [targetView superview];
    UIView *containerView = [targetSuperview superview];

But I'm still wondering for motivation of the change - it did work in the past, why change to the 'window' as container for the pop-up view? What side-effects one can expect with the old code?
Can the target view be child of something else, than referred in your 'while' loop? Did you meet it on practice?

@funnel20 I recognized that my code was not enough (I did it via class inheritance), as for some reason those tooltips were not dismissable on click afterwards. I applied your code to the original file and it seem to work like a charm!