ios:如何获取导航栏中rightBarButtonItem对应self.view的坐标?

https://segmentfault.com/q/1010000005592396

UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[btn setTitle:@"right" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:btn];

CGRect frameInNaviView = [self.navigationController.view convertRect:btn.frame fromView:btn.superview];

UIView *vShow = [[UIView alloc] initWithFrame:frameInNaviView];
vShow.backgroundColor = [UIColor redColor];
[self.navigationController.view addSubview:vShow];

frameInNaviView 就是你需要的

 

https://stackoverflow.com/questions/35611106/how-to-apply-borders-and-corner-radius-to-uibarbuttonitem

https://stackoverflow.com/questions/2994354/figure-out-uibarbuttonitem-frame-in-window

 

Figure out UIBarButtonItem frame in window?

Ask Question

up vote61down votefavorite

29

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 objective-c cocoa-touch uikit

shareimprove this question

asked Jun 8 '10 at 2:05

leolobato

1,42422749

add a comment

11 Answers

activeoldestvotes

up vote125down voteaccepted

+100

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 .itemsafter 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.

shareimprove this answer

edited May 23 '17 at 12:26

Community

11

answered Jun 14 '10 at 14:49

kennytm

383k72867891

  • 5

    I forgot about the event parameter. That's obviously the best choice. – progrmr Jun 14 '10 at 16:09

  • 1

    Event parameter ftw. Thanks a lot. – Colin Barrett Jul 29 '10 at 4:49

  • 2

    Note that [[event.allTouches anyObject] doesn't work well if the user taps multiple places simultaneously. Instead you want to get the touch corresponding to the UITouchPhaseEnded state, as in: for( UITouch* touch in [event allTouches] ) { if( [touch phase] == UITouchPhaseEnded ) { view = [touch view]; } } – zeroimpl Mar 5 '12 at 14:53 

  • 2

    convertRect: is not private anymore – phatmann Sep 6 '13 at 22:58

  • 1

    This answer is now 5 years old. Is this really still the only way to get the frame of a UIBarButtonItem? – devios1 May 14 '15 at 23:57

show 6 more comments

up vote16down vote

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

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

shareimprove this answer

edited May 25 '14 at 20:03

answered Jan 8 '14 at 5:35

Carlos Guzman

329412

  • 2

    This is not a good solution as it uses Apple private API. – Erik Tjernlund Nov 19 '14 at 19:06

  • valueForKey: is the only method being used. It is not a private API. All that is being done here is to make an assumption about Apple's implementation which Apple doesn't guarantee will stay the same. – GerardOct 23 '15 at 16:20

  • @Gerard "view" is the private API here. – bugloaf Jan 14 '16 at 15:55

  • 1

    @bugloaf Apple doesn't seem to have a problem with "view" since I've shipped an app with it and made no attempts at all to hide what I was doing. – Gerard Jan 17 '16 at 21:34

  • @Gerard You just didn't get caught. Apple's screening tools are not omniscient. – bugloaf Jan 18 '16 at 21:59

show 9 more comments

up vote8down vote

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];
}

shareimprove this answer

answered Nov 8 '10 at 19:54

Jeremy Fuller

2,89612526

  • 1

    That only works on iPad. – Scott Berrevoets May 26 '13 at 20:56

add a comment

up vote4down vote

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

shareimprove this answer

edited May 24 '13 at 11:45

answered May 6 '11 at 16:58

Werner Altewischer

6,96333547

  • 1

    Unfortunately, this at least sometimes does not work. I have a programmatically created UIBarButtonItems and I see that when customView is set to non-nil and then to nil, the bar button items disappears and [parentView.subviews objectAtIndex:indexOfView] crashes (indexOfView not less than subviews.count). – 18446744073709551615 Dec 27 '11 at 10:29 

  • This is not working for me – Bogdan Jul 16 '12 at 8:45

  • I edited the answer with the current (and working) implementation – Werner Altewischer May 24 '13 at 11:46

add a comment

up vote2down vote

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];
}

shareimprove this answer

edited Jan 7 '14 at 15:47

tilo

10.8k34268

answered Feb 2 '12 at 10:36

Jim Malak

15112

add a comment

up vote2down vote

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)

shareimprove this answer

edited May 23 '17 at 11:33

Community

11

answered Nov 19 '14 at 19:49

Erik Tjernlund

1,8411212

add a comment

up vote2down vote

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

shareimprove this answer

answered Jan 13 '15 at 7:21

Gank

3,73023536

add a comment

up vote1down vote

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.

shareimprove this answer

edited May 23 '17 at 12:18

Community

11

answered Jun 14 '10 at 14:15

progrmr

59.7k1390138

add a comment

up vote0down vote

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}

ios:如何获取导航栏中rightBarButtonItem对应self.view的坐标?_第1张图片

shareimprove this answer

answered Mar 10 '16 at 21:32

gbk

6,02275486

  • Using the event parameter from the tap action is still the safest way to grab the frame for when a tap occurred. But if you need to figure out the frame without detecting taps, I'd say this is a nice solution if you check rightBarButtonItem for respondsToSelector. – leolobato Mar 11 '16 at 9:53

  • Thank, good point – gbk Mar 11 '16 at 11:38

add a comment

up vote-1down vote

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;
}
}

shareimprove this answer

edited May 21 '13 at 12:08

answered Sep 11 '12 at 20:23

Valerii Pavlov

1,4901629

  • UIBarButtonItem instances don't have a bounds property. – Aaron Brager May 17 '13 at 17:34

  • The code does not access the bounds of the barButtonItem. It is accessing the bounds of the custom view inside the barButtonItem. – Matthew Cawley Dec 3 '15 at 16:52

add a comment

up vote-1down vote

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
}

shareimprove this answer

answered Aug 14 '14 at 13:53

Matthew Cawley

9311228

  • Uses private API so it's not a good solution. – Erik Tjernlund Nov 19 '14 at 20:39

  • "-valueForKey: is a documented, public method. The KVC API provides a way to prevent direct instance variable access, so if the value is accessible, then no action was taken to bar access. One concern might be hitting on an undefined value in a future version of iOS. To address that concern, you could wrap the call in a try-block. Also, even if you feel you can't use this in an app destined for the App Store, that doesn't mean someone targeting jailbroken devices couldn't use this approach in their application. " written by user @jeremy-w-sherman in another post – Matthew Cawley Nov 26 '14 at 3:18

 

你可能感兴趣的:(ios:如何获取导航栏中rightBarButtonItem对应self.view的坐标?)