Use UIBarButtonItem icon in UIButton

36,158

Solution 1

Download the image from somewhere on the web, add it to your project and set the UIButton's image to the image you just downloaded.

I did not find the same as Apple is using but I found this one. Simply change it's color in Pixelmator or Photoshop.

Solution 2

New iOS 13 support SF Symbols now
UIImage(systemName: "trash")


for swift 4.2 (call it on main thread)

extension UIBarButtonItem.SystemItem {
    func image() -> UIImage? {
        let tempItem = UIBarButtonItem(barButtonSystemItem: self,
                                       target: nil,
                                       action: nil)

        // add to toolbar and render it
        let bar = UIToolbar()
        bar.setItems([tempItem],
                     animated: false)
        bar.snapshotView(afterScreenUpdates: true)

        // got image from real uibutton
        let itemView = tempItem.value(forKey: "view") as! UIView
        for view in itemView.subviews {
            if let button = view as? UIButton,
                let image = button.imageView?.image {
                return image.withRenderingMode(.alwaysTemplate)
            }
        }

        return nil
    }
}

UIBarButtonSystemItem.play.image()

For Objective-C:

+ (UIImage *)imageFromSystemBarButton:(UIBarButtonSystemItem)systemItem {
    UIBarButtonItem* tempItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:systemItem target:nil action:nil];

    // Add to toolbar and render it
    UIToolbar *bar = [[UIToolbar alloc] init];
    [bar setItems:@[tempItem] animated:NO];
    [bar snapshotViewAfterScreenUpdates:YES];

    // Get image from real UIButton
    UIView *itemView = [(id)tempItem view];
    for (UIView* view in itemView.subviews) {
        if ([view isKindOfClass:[UIButton class]]) {
            return [(UIButton*)view imageForState:UIControlStateNormal];
        }
    }

    return nil;
}

Solution 3

Here's a solution that works with ANY System bar button item + it supports tintColor:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.button setImage:[self imageFromSystemBarButton:UIBarButtonSystemItemTrash]
                 forState:UIControlStateNormal];

    self.button.tintColor = [UIColor redColor];
}

- (UIImage *)imageFromSystemBarButton:(UIBarButtonSystemItem)systemItem {
    // Holding onto the oldItem (if any) to set it back later
    // could use left or right, doesn't matter
    UIBarButtonItem *oldItem = self.navigationItem.rightBarButtonItem;

    UIBarButtonItem *tempItem = [[UIBarButtonItem alloc]
                                 initWithBarButtonSystemItem:systemItem
                                 target:nil
                                 action:nil];

    // Setting as our right bar button item so we can traverse its subviews
    self.navigationItem.rightBarButtonItem = tempItem;

    // Don't know whether this is considered as PRIVATE API or not
    UIView *itemView = (UIView *)[self.navigationItem.rightBarButtonItem performSelector:@selector(view)];

    UIImage *image = nil;
    // Traversing the subviews to find the ImageView and getting its image
    for (UIView *subView in itemView.subviews) {
        if ([subView isKindOfClass:[UIImageView class]]) {
            image = ((UIImageView *)subView).image;
            break;
        }
    }

    // Setting our oldItem back since we have the image now
    self.navigationItem.rightBarButtonItem = oldItem;

    return image;
}


P.S. Feel free to improve if you know of a better way, thanks.

Solution 4

Based on @yycking answer, i wrote an suitable Swift 4 extension:

//  UIImage+FromSystemItem.swift

import UIKit

extension UIImage {

    public convenience init?(systemItem sysItem: UIBarButtonItem.SystemItem, renderingMode:UIImage.RenderingMode = .automatic) {
        guard let sysImage = UIImage.imageFromSystemItem(sysItem, renderingMode: renderingMode)?.cgImage else {
            return nil
        }

        self.init(cgImage: sysImage)
    }

    private class func imageFromSystemItem(_ systemItem: UIBarButtonItem.SystemItem, renderingMode:UIImage.RenderingMode = .automatic) -> UIImage? {

        let tempItem = UIBarButtonItem(barButtonSystemItem: systemItem, target: nil, action: nil)

        // add to toolbar and render it
        let bar = UIToolbar()
        bar.setItems([tempItem], animated: false)
        bar.snapshotView(afterScreenUpdates: true)

        // got image from real uibutton
        let itemView = tempItem.value(forKey: "view") as! UIView

        for view in itemView.subviews {
            if view is UIButton {
                let button = view as! UIButton
                let image = button.imageView!.image!
                image.withRenderingMode(renderingMode)
                return image
            }
        }

        return nil
    }
}

Example with action button (default coloring):

let actionImage = UIImage(systemItem: .action)
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
myButton.setImage(actionImage, for: .normal)

view.addSubview(myButton)

If you want your image to always be treated as a template regardless of context, set renderingMode to .alwaysTemplate

 let actionImage = UIImage(systemItem: .action, renderingMode: .alwaysTemplate) 

Solution 5

As already mentioned in the comments to @Islam Q's answer, the there presented solution might fail, if the UINavigationItem isn't currently rendered onscreen. It fails, for example, if the view controller isn't currently loaded. In fact the problem seems to be the missing layout of the UINavigationBar in these cases.

A more 'bulletproof' version would be to use a specially created UINavigationBar object just for getting the system item images. This would also make save saving and restoring of any existing UIBarButtonItems obsolete.

I've packed this into a small helper class:

LEABarButtonSystemItemImage.h:

#import <UIKit/UIKit.h>


/**
 LEABarButtonSystemItemImage interface

 */
@interface LEABarButtonSystemItemImage : NSObject

+ (UIImage *)imageFromBarButtonSystemItem:(UIBarButtonSystemItem)pBarButtonSystemItem;
+ (UIImage *)customImageForBarButtonSystemItem:(UIBarButtonSystemItem)pBarButtonSystemItem;

+ (NSDictionary<__kindof NSNumber*, __kindof UIImage*> *)barButtonItemImages;

@end

LEABarButtonSystemItemImage.m

#import "LEABarButtonSystemItemImage.h"


/**
 LEABarButtonSystemItemImage implementation

 */
@implementation LEABarButtonSystemItemImage

/*
 imageFromBarButtonSystemItem:

 */
+ (UIImage *)imageFromBarButtonSystemItem:(UIBarButtonSystemItem)pBarButtonSystemItem {

    static const CGFloat    defaultNBBtnHW  = 44.0;

    UINavigationBar *   tempNavigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, defaultNBBtnHW, defaultNBBtnHW)];
    UINavigationItem *  tempNavigationItem = [[UINavigationItem alloc] init];
    UIBarButtonItem *   tempBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:pBarButtonSystemItem target:nil action:NULL];

    tempNavigationBar.items = @[tempNavigationItem];
    tempNavigationItem.rightBarButtonItems = @[tempBarButtonItem];

    UIImage *           barButtonSystemItemImage = nil;
    @try {
        UIView *        barButtonItemView = [tempBarButtonItem valueForKey:@"view"];
        for (UIView* subview in barButtonItemView.subviews) {
            if ([subview isKindOfClass:UIImageView.class]) {
                barButtonSystemItemImage = ((UIImageView *)subview).image;
                break;
            }
        }
    } @catch (...) { NSLog(@"%s: Exception while retrieving image from UIBarButtonItem!", __PRETTY_FUNCTION__); }

    return (barButtonSystemItemImage ?: [LEABarButtonSystemItemImage customImageForBarButtonSystemItem:pBarButtonSystemItem]);
}

/*
 customImageForBarButtonSystemItem:

 */
+ (UIImage *)customImageForBarButtonSystemItem:(UIBarButtonSystemItem)pBarButtonSystemItem {

    NSString *  customBarButtonSystemItemImageName = nil;
    switch (pBarButtonSystemItem) {
        case UIBarButtonSystemItemDone:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemDone";          break;
        case UIBarButtonSystemItemCancel:           customBarButtonSystemItemImageName = @"customBarButtonSystemItemCancel";        break;
        case UIBarButtonSystemItemEdit:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemEdit";          break;
        case UIBarButtonSystemItemSave:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemSave";          break;
        case UIBarButtonSystemItemAdd:              customBarButtonSystemItemImageName = @"customBarButtonSystemItemAdd";           break;
        case UIBarButtonSystemItemCompose:          customBarButtonSystemItemImageName = @"customBarButtonSystemItemCompose";       break;
        case UIBarButtonSystemItemReply:            customBarButtonSystemItemImageName = @"customBarButtonSystemItemReply";         break;
        case UIBarButtonSystemItemAction:           customBarButtonSystemItemImageName = @"customBarButtonSystemItemAction";        break;
        case UIBarButtonSystemItemOrganize:         customBarButtonSystemItemImageName = @"customBarButtonSystemItemOrganize";      break;
        case UIBarButtonSystemItemBookmarks:        customBarButtonSystemItemImageName = @"customBarButtonSystemItemBookmarks";     break;
        case UIBarButtonSystemItemSearch:           customBarButtonSystemItemImageName = @"customBarButtonSystemItemSearch";        break;
        case UIBarButtonSystemItemRefresh:          customBarButtonSystemItemImageName = @"customBarButtonSystemItemRefresh";       break;
        case UIBarButtonSystemItemStop:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemStop";          break;
        case UIBarButtonSystemItemCamera:           customBarButtonSystemItemImageName = @"customBarButtonSystemItemCamera";        break;
        case UIBarButtonSystemItemTrash:            customBarButtonSystemItemImageName = @"customBarButtonSystemItemTrash";         break;
        case UIBarButtonSystemItemPlay:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemPlay";          break;
        case UIBarButtonSystemItemPause:            customBarButtonSystemItemImageName = @"customBarButtonSystemItemPause";         break;
        case UIBarButtonSystemItemRewind:           customBarButtonSystemItemImageName = @"customBarButtonSystemItemRewind";        break;
        case UIBarButtonSystemItemFastForward:      customBarButtonSystemItemImageName = @"customBarButtonSystemItemFastForward";   break;
        case UIBarButtonSystemItemUndo:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemUndo";          break;
        case UIBarButtonSystemItemRedo:             customBarButtonSystemItemImageName = @"customBarButtonSystemItemRedo";          break;
        case UIBarButtonSystemItemPageCurl:         customBarButtonSystemItemImageName = @"customBarButtonSystemItemPageCurl";      break;
        default:    break;
    }

    return (customBarButtonSystemItemImageName
            ? [UIImage imageNamed:customBarButtonSystemItemImageName]
            : nil);
}

/*
 barButtonItemImages

 */
+ (NSDictionary<__kindof NSNumber*, __kindof UIImage*> *)barButtonItemImages {

    NSMutableDictionary<__kindof NSNumber*, __kindof UIImage*> *    barButtonItemImages = [NSMutableDictionary dictionary];
    // From:    https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIBarButtonItem.h
    //          unsigned int systemItem : 7;
    for (NSUInteger uIndex = 0; uIndex < (1<<7); ++uIndex) {
        UIImage*    systemImage = [LEABarButtonSystemItemImage imageFromBarButtonSystemItem:uIndex];
        if (systemImage) {
            [barButtonItemImages setObject:systemImage forKey:@(uIndex)];
        }
    }
    NSLog(@"%s: %@", __PRETTY_FUNCTION__, barButtonItemImages);
    return barButtonItemImages;
}

@end

As an add on/fallback , the method returns an custom image, if no system item image could be retrieved. Of course these custom images must be present in the apps bundle.

The last method 'barButtonImages' was implemented just for curiosity... in the UIBarButtonItem header the member systemItem is declared to use 7 bits (0..127). Currently only 22 values are documented from UIBarButtonSystemItemDone to UIBarButtonItemSystemItemPageCurl... and in fact; I found some undocumented images starting with indexes above 100 (tested on iOS 9.3 in the 6S+ simulator) :-)

Share:
36,158
ZviBar
Author by

ZviBar

Full stack beginner;-).

Updated on April 26, 2021

Comments

  • ZviBar
    ZviBar about 3 years

    UIBarButtonItem has multiple icons available. Is it possible to use the icon which appears after setting its identifier to 'trash':

    trash icon

    with an UIButton? There is no straighforward method to do that like setting the identifier or style.

  • ZviBar
    ZviBar over 10 years
    It's not bad but I'd rather it be blue.
  • yoeriboven
    yoeriboven over 10 years
    You can easily do that in Photoshop and I even think it is possible to do it with tintColor. Let me check that one.
  • yoeriboven
    yoeriboven over 10 years
    Could not get it to work with tintColor. Shouldn't be a problem since you can simply change it's color in Photoshop or Pixelmator.
  • Gram
    Gram almost 9 years
    Thank you, this helped me a lot. I found that I could use the tempItem directly into the bar. Although not exactly a UIButton I had a hard time googling the answer.
  • Islam
    Islam almost 9 years
    You're welcome. I tried to break it down as much as possible so it's easily understandable.
  • Hans Brende
    Hans Brende over 8 years
    How would you translate UIView *itemView = (UIView *)[self.navigationItem.rightBarButtonItem performSelector:@selector(view)]; into Swift?
  • Islam
    Islam over 8 years
    Haven't done it in Swift, but I hope answers to this question will help.
  • LaborEtArs
    LaborEtArs over 8 years
    One trap is hidden ;-) This code only works, if the UINavigationItem is currently rendered onscreen. It fails, for example, if the view controller isn't currently loaded (e.g. if it is one of the not selected view controllers in a UITabBarController).
  • aroth
    aroth about 8 years
    What @LaborEtArs said. This is a good idea, but it's not a general-purpose solution to the problem.
  • LaborEtArs
    LaborEtArs about 8 years
    I've added a more bulletproof solution to retrieve the system item images in another answer.
  • Frans
    Frans over 7 years
    to have the image use the tint color of the button, add .imageWithRenderingMode(.AlwaysTemplate)
  • Lepidopteron
    Lepidopteron over 7 years
    Really sad that the accepted answer is supposed to break copyright rules... "copy image somewhere and add it to your project"
  • quemeful
    quemeful about 7 years
    @Lepidopteron actually there are many "somewheres" that are made exactly for this purpose
  • Sanju
    Sanju about 7 years
    color is changing! default color is blue but your code output color is black . Any solution?
  • Peter Kreinz
    Peter Kreinz about 7 years
    @sanju: i have updated the default rendering mode to .automatic (default blue color)
  • kelin
    kelin about 7 years
    @Sanju, that's because UIBarButtonItem use template image. Images are black, but when they are drawn with tint color of the bar. More about template rendering mode.
  • David
    David almost 7 years
    No longer works in Xcode 9. Image must be rendered before it can be extracted.
  • Darkwonder
    Darkwonder almost 7 years
    This solution doesn't work in Xcode 9/iOS 11. Please update the answer with guard as? View and don't force unwrap. My app crashed because of this.
  • Denis Balko
    Denis Balko almost 7 years
    Thanks for the answer Peter, I just want to point out it no longer works for Swift 4, tempItem.value(forKey: "view") is nil and forceful unwrapping gives an error.
  • RubberDucky4444
    RubberDucky4444 over 6 years
    I keep getting a nil return on the line.... let itemView = tempItem.value(forKey: "view") as! UIView
  • Efren
    Efren over 6 years
    It appears that it still does not work on Swift 4, when you say Solved, in which version did you test Peter?
  • oguzhan00
    oguzhan00 over 5 years
    This solution is doesn't work on Swift 4, it getting nil image return error
  • Peter Kreinz
    Peter Kreinz over 5 years
    Deployment Target 11.4 (Swift 4.2): let itemView = tempItem.value(forKey: "view") as! UIView = Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
  • Peter Kreinz
    Peter Kreinz over 5 years
    You are all right. It does not work on Swift 4 anymore. I will try to find a solution ...
  • Peter Dietz
    Peter Dietz about 5 years
    Apple has several icons available at: developer.apple.com/design/human-interface-guidelines/ios/… action, add, bookmarks, camera, cancel, compose, done, edit, fastForward, organize, pause, play, redo, refresh, reply, rewind, save, search, stop, trash, undo
  • adijazz91
    adijazz91 almost 5 years
    Where ever you are adding add the function in the Main queue, then it will work perfectly. Enjoy \m/