I have a button in a toolbar. How can I grab its frame? Do UIBarButtonItem
s not have a frame
property?

- 21,000
- 15
- 120
- 146

- 3,606
- 6
- 32
- 55
-
3That's because `UIBarButtonItems` are not `UIViews`. Why do you need the frame? maybe there is a better approach – Ismael Jan 14 '13 at 12:28
-
basically i'm using a this https://github.com/Ciechan/BCGenieEffect#bcgenieeffect and trying to simulate a view 'sucking' into a button on my toolbar – totalitarian Jan 14 '13 at 12:59
15 Answers
Try this one;
UIBarButtonItem *item = ... ;
UIView *view = [item valueForKey:@"view"];
CGFloat width;
if(view){
width=[view frame].size.width;
}
else{
width=(CGFloat)0.0 ;
}

- 46,283
- 15
- 111
- 140
-
6There is no definition of "Appstore safe". Your program is not crashing, you are not hacking, you are not invalidating anything that apple doesn't want then its fine. Many a times apps are rejected for strange reason. – Anoop Vaidya Jan 14 '13 at 13:08
-
3OK, I saw mentioned before that [item valueForKey:@"view"] is using an undocumented API method... – totalitarian Jan 14 '13 at 13:13
-
Or you can post another question on that... can we use that undoc api... :) – Anoop Vaidya Jan 14 '13 at 13:16
-
I found an alternative method that looks a little safer, i'll still mark you correct for this question though. Thanks again – totalitarian Jan 14 '13 at 13:19
-
1And please post your answer also. So that people searching will find few answers and go for better answer. – Anoop Vaidya Jan 14 '13 at 13:21
-
28"There is no definition of Appstore safe". That statement is invalid. Using the private API is NOT "Appstore safe". Period – aryaxt Jun 02 '15 at 21:41
-
@aryaxt: I said wrt to this answer. And for other cases yes you are correct. – Anoop Vaidya Mar 17 '16 at 16:48
-
HI @AnoopVaidya I wanted to know how to get more information regarding the undocumented APIs that are been used in the UIKit user interfaces? – LearneriOS Oct 22 '16 at 19:00
-
1@LearneriOS: The more you code, the more you learn. Once you start diving you will come to know obj-c runtime & also wwdc and other legacy codes...etc will help you learn more *undocumented* things. – Anoop Vaidya Oct 23 '16 at 12:14
-
-
@PoolHallJunkie Try out the method used in this answer: https://stackoverflow.com/a/46965131/4980464 – Dan Stenmark Oct 30 '17 at 06:11
-
and? tried to get frame for `rightBarButtonItem`. Your way returns origin (0,0). Downvoted – Vyachaslav Gerchicov Nov 24 '17 at 08:15
This way works best for me:
UIView *targetView = (UIView *)[yourBarButton performSelector:@selector(view)];
CGRect rect = targetView.frame;

- 8,341
- 5
- 56
- 75
-
I really like your solution, for it's simple and based on the identifier of the button directly. It works well, however it seems its frame is larger than expected. I'm using it to calculate the position of a `PopOver`, and it's placed a little low beneath the button. Is this related to your solution, and if so, do you have a(nother) solution for this? Thanks in advance! – Tum Feb 14 '14 at 13:07
-
@Tumtum sounds like an issue with how the popover is added. Add the popover to self.navigationController.view instead of self.view – MobileMon Feb 14 '14 at 13:32
-
@Turnturn either that or just subtract some pixels from the CGRect's y origin: rect.origin.y = rect.origin.y - 5; – MobileMon Feb 14 '14 at 13:42
-
You're a lifesaver! I used a combination of both of your solutions as the popover was positioned a little too high using just the first option you gave me, so I corrected that using the second option. Thanks for taking the time to answer my subquestion. – Tum Feb 15 '14 at 13:23
Oof, lots of rough answers in this thread. Here's the right way to do it:
import UIKit
class ViewController: UIViewController {
let customButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
customButton.setImage(UIImage(named: "myImage"), for: .normal)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: customButton)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(self.customButton.convert(self.customButton.frame, to: nil))
}
}

- 591
- 6
- 11
-
1Even though this example uses a navigation item, the same should work for toolbars. Just make sure the view controller is currently presented and showing the toolbar. – Dan Stenmark Oct 26 '17 at 23:08
-
This only works for UIBarButtonItems that have a custom view, of course. – fishinear Aug 06 '20 at 18:03
-
This **definitely works** but make sure to call it in `viewDidAppear()` – Lance Samaria Jun 29 '23 at 08:50
With Swift, if you needs to often work with bar button items, you should implement an extension like this:
extension UIBarButtonItem {
var frame: CGRect? {
guard let view = self.value(forKey: "view") as? UIView else {
return nil
}
return view.frame
}
}
Then in your code you can access easily:
if let frame = self.navigationItem.rightBarButtonItems?.first?.frame {
// do whatever with frame
}

- 21,000
- 15
- 120
- 146
-
@PoolHallJunkie for iOS11 try to call this code in ViewDidAppear() method – Hosny Nov 20 '17 at 12:05
-
This not a proper solution as it uses private API. You should subclass to have a solution that will not fail. – PoolHallJunkie Nov 20 '17 at 15:48
Thanks to Anoop Vaidya for the suggested answer. An alternative could be (providing you know the position of the button in the toolbar)
UIView *view= (UIView *)[self.toolbar.subviews objectAtIndex:0]; // 0 for the first item
CGRect viewframe = view.frame;

- 3,606
- 6
- 32
- 55
-
This is good way, cant u find Index by looping all the views and chekcing some tag or outlet name? – Anoop Vaidya Jan 14 '13 at 13:24
-
Thanks. Good idea about the loop, but for my situation the I know the index so a loop wouldn't be required. – totalitarian Jan 14 '13 at 14:11
-
in the case...wen u dont knoe the index...then u can find it using loop. it will be a general code for all. – Anoop Vaidya Jan 14 '13 at 15:56
-
Also keep in mind that `subviews` might be empty, in which case the code would lead to a crash. So in a shipping application you should check for that. – de. May 07 '13 at 14:35
-
1The order of the subviews changes for iOS 6, right? the first button is now the last one and viceversa – aprunedamtz Jun 06 '13 at 16:48
Here's what I'm using in iOS 11 & Swift 4. It could be a little cleaner without the optional but I'm playing it safe:
extension UIBarButtonItem {
var view: UIView? {
return perform(#selector(getter: UIViewController.view)).takeRetainedValue() as? UIView
}
}
And usage:
if let barButtonFrame = myBarButtonItem.view?.frame {
// etc...
}
Edit: I don't recommend using this anymore. I ended up changing my implementation to use UIBarButtonItems with custom views, like Dan's answer

- 578
- 6
- 14
-
I really liked this answer, and used it in a production setting. Sadly, it caused the occasional crash by objc_retain. – Ron Regev Sep 22 '21 at 19:48
-
Perhaps use takeUnretainedValue()? Is the risk of a memory leak here greater than that of a crash? – Ron Regev Sep 22 '21 at 20:37
-
@RonRegev see edit. I ended up using custom views. It was more work, but a better and safer solution in the end. Good luck! – jday Oct 03 '21 at 22:08
-(CGRect) getBarItemRc :(UIBarButtonItem *)item{
UIView *view = [item valueForKey:@"view"];
return [view frame];
}

- 4,507
- 4
- 49
- 45
You can create a UIBarButtonItem with a custom view, which is a UIButton, then you can do whatever you want. :]

- 814
- 10
- 18
in Swift 4.2 and inspired with luca
extension UIBarButtonItem {
var frame:CGRect?{
return (value(forKey: "view") as? UIView)?.frame
}
}
guard let frame = self.navigationItem.rightBarButtonItems?.first?.frame else{ return }

- 4,278
- 40
- 52
You can roughly calculate it by using properties like layoutMargins
and frame
on the navigationBar, combined with icon size guides from Human Interface Guidelines and take into count the current device orientation:
- (CGRect)rightBarButtonFrame {
CGFloat imageWidth = 28.0;
CGFloat imageHeight = UIDevice.currentDevice.orientation == UIDeviceOrientationLandscapeLeft || UIDevice.currentDevice.orientation == UIDeviceOrientationLandscapeRight ? 18.0 : 28.0;
UIEdgeInsets navigationBarLayoutMargins = self.navigationController.navigationBar.layoutMargins;
CGRect navigationBarFrame = self.navigationController.navigationBar.frame;
return CGRectMake(navigationBarFrame.size.width-(navigationBarLayoutMargins.right + imageWidth), navigationBarFrame.origin.y + navigationBarLayoutMargins.top, imageWidth, imageHeight);
}

- 6,356
- 7
- 32
- 47
Try this implementation:
@implementation UIBarButtonItem(Extras)
- (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

- 10,080
- 4
- 53
- 60
-
This has the same disadvantage as the other answers (the undocumented selector "view" needs to be defined), and it does a totally pointless lookup in the superview. – fishinear Aug 06 '20 at 18:08
You should do a loop over the subviews and check their type or their contents for identifying. It is not safe to access view by kvo and you cannot be sure about the index.

- 1
- 3
Check out this answer: How to apply borders and corner radius to UIBarButtonItem? which explains how to loop over subviews to find the frame of a button.

- 1
- 1

- 314
- 2
- 9
I used a view on the bar button item with a tag on the view:
for view in bottomToolbar.subviews {
if let stackView = view.subviews.filter({$0 is UIStackView}).first {
//target view has tag = 88
if let targetView = stackView.subviews.filter({$0.viewWithTag(88) != nil}).first {
//do something with target view
}
}
}

- 374
- 5
- 9
Swift 4 up The current best way to do it is to access its frame from :
self.navigationItem.rightBarButtonItems
by
let customView = navigationItem.rightBarButtonItems?.first?.customView // access the first added customView
Accessing this way is safer than accessing private api.
- check out the answer in this :
After Add a CustomView to navigationItem, CustomView always return nil

- 594
- 4
- 27
-
This only works if the UIBarButtonItem is based on a customView (most of them are not). – fishinear Aug 06 '20 at 18:14