Figure out UIBarButtonItem frame in window?

IphoneObjective CCocoa TouchUikit

Iphone Problem Overview


UIBarButtonItem does not extend UIView, so there is nothing like a frame property.

But is there any way I can get what is it's CGRect frame, relative to the application UIWindow?

Iphone Solutions


Solution 1 - Iphone

Do you like to use private APIs? If yes,

UIView* view = thatItem.view;
return [view convertRect:view.bounds toView:nil];

Of course no one wants this when targeting the AppStore. A more unreliable method, and also uses undocumented features, but will pass Apple's test, is to loop through the subviews to look for the corresponding button item.

NSMutableArray* buttons = [[NSMutableArray alloc] init];
for (UIControl* btn in theToolbarOrNavbar.subviews)
  if ([btn isKindOfClass:[UIControl class]])
    [buttons addObject:btn];
UIView* view = [buttons objectAtIndex:index];
[buttons release];
return [view convertRect:view.bounds toView:nil];

The index is the index to your bar item in the array of .items, after removing all blank items. This assumes the buttons are arranged in increasing order, which may not be. A more reliable method is to sort the buttons array in increasing .origin.x value. Of course this still assumes the bar button item must inherit the UIControl class, and are direct subviews of the toolbar/nav-bar, which again may not be.


As you can see, there are a lot of uncertainty when dealing with undocumented features. However, you just want to pop up something under the finger right? The UIBarButtonItem's .action can be a selector of the form:

-(void)buttonClicked:(UIBarButtonItem*)sender event:(UIEvent*)event;

note the event argument — you can obtain the position of touch with

[[event.allTouches anyObject] locationInView:theWindow]

or the button view with

[[event.allTouches anyObject] view]

Therefore, there's no need to iterate the subviews or use undocumented features for what you want to do.

Solution 2 - Iphone

I didn't see this option posted (which in my opinion is much simpler), so here it is:

UIView *barButtonView = [barButtonItem valueForKey:@"view"];

Solution 3 - Iphone

In iOS 3.2, there's a much easier way to show an Action Sheet popover from a toolbar button. Merely do something like this:

- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
 UIActionSheet *popupSheet;
 // Prepare your action sheet
 [popupSheet showFromBarButtonItem:sender animated:YES];
}

Solution 4 - Iphone

This is the implementation I use for my WEPopover project: (https://github.com/werner77/WEPopover):

@implementation UIBarButtonItem(WEPopover)

- (CGRect)frameInView:(UIView *)v {
	
	UIView *theView = self.customView;
	if (!theView.superview && [self respondsToSelector:@selector(view)]) {
		theView = [self performSelector:@selector(view)];
	}
	
	UIView *parentView = theView.superview;
	NSArray *subviews = parentView.subviews;
	
	NSUInteger indexOfView = [subviews indexOfObject:theView];
	NSUInteger subviewCount = subviews.count;
	
	if (subviewCount > 0 && indexOfView != NSNotFound) {
		UIView *button = [parentView.subviews objectAtIndex:indexOfView];
		return [button convertRect:button.bounds toView:v];
	} else {
		return CGRectZero;
	}
}
@end

Solution 5 - Iphone

As long as UIBarButtonItem (and UITabBarItem) does not inherit from UIView—for historical reasons UIBarItem inherits from NSObject—this craziness continues (as of this writing, iOS 8.2 and counting ... )

The best answer in this thread is obviously @KennyTM's. Don't be silly and use the private API to find the view.

Here's a oneline Swift solution to get an origin.x sorted array (like Kenny's answer suggests):

    let buttonFrames = myToolbar.subviews.filter({
        $0 is UIControl
    }).sorted({
        $0.frame.origin.x < $1.frame.origin.x
    }).map({
        $0.convertRect($0.bounds, toView:nil)
    })
    

The array is now origin.x sorted with the UIBarButtonItem frames.

(If you feel the need to read more about other people's struggles with UIBarButtonItem, I recommend Ash Furrow's blog post from 2012: Exploring UIBarButtonItem)

Solution 6 - Iphone

I was able to get Werner Altewischer's WEpopover to work by passing up the toolbar along with the
UIBarButton: Mod is in WEPopoverController.m

- (void)presentPopoverFromBarButtonItem:(UIBarButtonItem *)item toolBar:(UIToolbar *)toolBar
			   permittedArrowDirections:(UIPopoverArrowDirection)arrowDirections 
							   animated:(BOOL)animated 
{
    self.currentUIControl = nil;
    self.currentView = nil;
    self.currentBarButtonItem = item;
    self.currentArrowDirections = arrowDirections;
    self.currentToolBar = toolBar;

    UIView *v = [self keyView];
    UIButton *button = nil;

    for (UIView *subview in toolBar.subviews) 
    {
        if ([[subview class].description isEqualToString:@"UIToolbarButton"])
        {
            for (id target in [(UIButton *)subview allTargets]) 
            {
                if (target == item) 
                {
                    button = (UIButton *)subview;
                    break;
                }
            }
            if (button != nil) break;
        }
    }

    CGRect rect = [button.superview convertRect:button.frame toView:v];

    [self presentPopoverFromRect:rect inView:v permittedArrowDirections:arrowDirections animated:animated];
}

Solution 7 - Iphone

-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
    UIView *view = [item valueForKey:@"view"];
    return [view frame];
}

Solution 8 - Iphone

You can get it from the UINavigationBar view. The navigationBar is a UIView which has 2 or 3 custom subviews for the parts on the bar.

If you know that the UIBarButtonItem is currently shown in the navbar on the right, you can get its frame from navbar's subviews array.

First you need the navigationBar which you can get from the navigationController which you can get from the UIViewController. Then find the right most subview:

UINavigationBar* navbar = curViewController.navigationController.navigationBar;
UIView* rightView = nil;

for (UIView* v in navbar.subviews) {
   if (rightView==nil) {
      rightView = v;
   } else if (v.frame.origin.x > rightView.frame.origin.x) {
      rightView = v;  // this view is further right
   }
}
// at this point rightView contains the right most subview of the navbar

I haven't compiled this code so YMMV.

Solution 9 - Iphone

This is not the best solution and from some point of view it's not right solution and we can't do like follow because we access to object inside UIBarBattonItem implicitly, but you can try to do something like:

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 30, 30)];
[button setImage:[UIImage imageNamed:@"Menu_Icon"] forState:UIControlStateNormal];
[button addTarget:self action:@selector(didPressitem) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:button];
self.navigationItem.rightBarButtonItem = item;

CGPoint point = [self.view convertPoint:button.center fromView:(UIView *)self.navigationItem.rightBarButtonItem];
//this is like view because we use UIButton like "base" obj for 
//UIBarButtonItem, but u should note that UIBarButtonItem base class
//is NSObject class not UIView class, for hiding warning we implicity
//cast UIBarButtonItem created with UIButton to UIView
NSLog(@"point %@", NSStringFromCGPoint(point));

as result i got next:

> point {289, 22}

enter image description here

Solution 10 - Iphone

Before implement this code, be sure to call [window makeKeyAndVisible] in your Applition delegate application:didFinishLaunchingWithOptions: method!

- (void) someMethod
{
    CGRect rect = [barButtonItem convertRect:barButtonItem.customview.bounds toView:[self keyView]];
}

- (UIView *)keyView {
UIWindow *w = [[UIApplication sharedApplication] keyWindow];
if (w.subviews.count > 0) {
	return [w.subviews objectAtIndex:0];
} else {
	return w;
}
}

Solution 11 - Iphone

I handled it as follows:

- (IBAction)buttonClicked:(UIBarButtonItem *)sender event:(UIEvent *)event
{
    UIView* view = [sender valueForKey:@"view"]; //use KVO to return the view
    CGRect rect = [view convertRect:view.bounds toView:self.view];
    //do stuff with the rect
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionleolobatoView Question on Stackoverflow
Solution 1 - IphonekennytmView Answer on Stackoverflow
Solution 2 - IphoneCarlos GuzmanView Answer on Stackoverflow
Solution 3 - IphoneJeremy FullerView Answer on Stackoverflow
Solution 4 - IphoneWerner AltewischerView Answer on Stackoverflow
Solution 5 - IphoneErik TjernlundView Answer on Stackoverflow
Solution 6 - IphoneJim MalakView Answer on Stackoverflow
Solution 7 - IphoneGankView Answer on Stackoverflow
Solution 8 - IphoneprogrmrView Answer on Stackoverflow
Solution 9 - IphonehbkView Answer on Stackoverflow
Solution 10 - IphoneValerii PavlovView Answer on Stackoverflow
Solution 11 - IphoneMatthew CawleyView Answer on Stackoverflow