<a data-cke-saved-href="https://segmentfault.com/q/1010000005592396" href="https://segmentfault.com/q/1010000005592396">https://segmentfault.com/q/1010000005592396</a>
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/2994354/figure-out-uibarbuttonitem-frame-in-window
Figure out UIBarButtonItem frame in window?
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
asked Jun 8 '10 at 2:05
1,42422749
11 Answers
+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 .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.
11
answered Jun 14 '10 at 14:49
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
I didn't see this option posted (which in my opinion is much simpler), so here it is:
UIView *barButtonView = [barButtonItem valueForKey:@"view"];
answered Jan 8 '14 at 5:35
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
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];
}
answered Nov 8 '10 at 19:54
2,89612526
-
1
That only works on iPad. – Scott Berrevoets May 26 '13 at 20:56
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
answered May 6 '11 at 16:58
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
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];
}
10.8k34268
answered Feb 2 '12 at 10:36
15112
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)
11
answered Nov 19 '14 at 19:49
1,8411212
-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
UIView *view = [item valueForKey:@"view"];
return [view frame];
}
answered Jan 13 '15 at 7:21
3,73023536
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.
11
answered Jun 14 '10 at 14:15
59.7k1390138
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}
answered Mar 10 '16 at 21:32
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
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;
}
}
answered Sep 11 '12 at 20:23
1,4901629
-
UIBarButtonItem
instances don't have abounds
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
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
}
answered Aug 14 '14 at 13:53
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