UIBarButtonItem: How can I find its frame?

IosObjective CSwift

Ios Problem Overview


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

Ios Solutions


Solution 1 - Ios

Try this one;

UIBarButtonItem *item = ... ;
UIView *view = [item valueForKey:@"view"];
CGFloat width;
if(view){
    width=[view frame].size.width;
}
else{
    width=(CGFloat)0.0 ;
}

Solution 2 - Ios

This way works best for me:

UIView *targetView = (UIView *)[yourBarButton performSelector:@selector(view)];
CGRect rect = targetView.frame;

Solution 3 - Ios

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            
}

Solution 4 - Ios

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

Solution 5 - Ios

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;

Solution 6 - Ios

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

Solution 7 - Ios

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

Solution 8 - Ios

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

Solution 9 - Ios

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

Solution 10 - Ios

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

Solution 11 - Ios

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.

Solution 12 - Ios

Check out this answer: https://stackoverflow.com/questions/35611106/how-to-apply-borders-and-corner-radius-to-uibarbuttonitem which explains how to loop over subviews to find the frame of a button.

Solution 13 - Ios

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 }

Solution 14 - Ios

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

}

Solution 15 - Ios

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 :

https://stackoverflow.com/questions/60183720/after-add-a-customview-to-navigationitem-customview-always-return-nil?noredirect=1#comment106658947_60183720

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
QuestiontotalitarianView Question on Stackoverflow
Solution 1 - IosAnoop VaidyaView Answer on Stackoverflow
Solution 2 - IosMobileMonView Answer on Stackoverflow
Solution 3 - IosLuca DavanzoView Answer on Stackoverflow
Solution 4 - IosDan StenmarkView Answer on Stackoverflow
Solution 5 - IostotalitarianView Answer on Stackoverflow
Solution 6 - IosjdayView Answer on Stackoverflow
Solution 7 - IosGankView Answer on Stackoverflow
Solution 8 - IosNix WangView Answer on Stackoverflow
Solution 9 - IosturingtestedView Answer on Stackoverflow
Solution 10 - IosWerner AltewischerView Answer on Stackoverflow
Solution 11 - Iosuser4091416View Answer on Stackoverflow
Solution 12 - IosfrandoggerView Answer on Stackoverflow
Solution 13 - IosReimond HillView Answer on Stackoverflow
Solution 14 - IosdawidView Answer on Stackoverflow
Solution 15 - IosvhongView Answer on Stackoverflow