chrismiles / CMPopTipView

Custom UIView for iOS that pops up an animated "bubble" pointing at a button or other view. Useful for popup tips.
https://github.com/chrismiles/CMPopTipView
MIT License
2.74k stars 466 forks source link

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

Open funnel20 opened 6 years ago

funnel20 commented 6 years ago

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:

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

funnel20 commented 6 years ago

@kleinlieu What do you think of my analysis?

NikolayEntin commented 6 years ago

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.

funnel20 commented 6 years ago

@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?

NikolayEntin commented 6 years ago

@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?

NikolayEntin commented 6 years ago

@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!