Use UIBarButtonItem icon in UIButton
Asked Answered
W

10

38

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.

Winglet answered 17/1, 2014 at 14:12 Comment(0)
N
10

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.

Najera answered 17/1, 2014 at 14:14 Comment(6)
It's not bad but I'd rather it be blue.Winglet
You can easily do that in Photoshop and I even think it is possible to do it with tintColor. Let me check that one.Najera
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.Najera
Really sad that the accepted answer is supposed to break copyright rules... "copy image somewhere and add it to your project"Sampan
@Sampan actually there are many "somewheres" that are made exactly for this purposeStack
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, undoInstitutional
P
28

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;
}
Proletarian answered 24/3, 2016 at 8:55 Comment(4)
to have the image use the tint color of the button, add .imageWithRenderingMode(.AlwaysTemplate)Balsaminaceous
No longer works in Xcode 9. Image must be rendered before it can be extracted.Future
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.Harassed
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 valueVaticide
C
16

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.

Calcariferous answered 14/12, 2014 at 18:27 Comment(7)
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.Dandrea
You're welcome. I tried to break it down as much as possible so it's easily understandable.Calcariferous
How would you translate UIView *itemView = (UIView *)[self.navigationItem.rightBarButtonItem performSelector:@selector(view)]; into Swift?Ultrastructure
Haven't done it in Swift, but I hope answers to this question will help.Calcariferous
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).Lowther
What @Lowther said. This is a good idea, but it's not a general-purpose solution to the problem.Boydboyden
I've added a more bulletproof solution to retrieve the system item images in another answer.Lowther
N
10

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.

Najera answered 17/1, 2014 at 14:14 Comment(6)
It's not bad but I'd rather it be blue.Winglet
You can easily do that in Photoshop and I even think it is possible to do it with tintColor. Let me check that one.Najera
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.Najera
Really sad that the accepted answer is supposed to break copyright rules... "copy image somewhere and add it to your project"Sampan
@Sampan actually there are many "somewheres" that are made exactly for this purposeStack
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, undoInstitutional
V
10

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) 
Vaticide answered 12/11, 2016 at 19:9 Comment(9)
color is changing! default color is blue but your code output color is black . Any solution?Walford
@sanju: i have updated the default rendering mode to .automatic (default blue color)Vaticide
@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.Locate
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.Hoxie
I keep getting a nil return on the line.... let itemView = tempItem.value(forKey: "view") as! UIViewDanita
It appears that it still does not work on Swift 4, when you say Solved, in which version did you test Peter?Indre
This solution is doesn't work on Swift 4, it getting nil image return errorAverir
You are all right. It does not work on Swift 4 anymore. I will try to find a solution ...Vaticide
Where ever you are adding add the function in the Main queue, then it will work perfectly. Enjoy \m/Atherosclerosis
L
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) :-)

Lowther answered 11/4, 2016 at 18:22 Comment(0)
U
4

All the iOS system icons can be extracted using a handy little app called (fittingly enough) iOS Artwork Extractor. I use it all the time when I want to mimic iOS system behaviors.

Download the Xcode project at:

https://github.com/0xced/iOS-Artwork-Extractor

Uella answered 8/10, 2014 at 5:2 Comment(0)
D
2

Using Quartz2D with Swift 4.2

The solutions based in an extension to extract the image from the UIBarButtonSystemItem don't work in iOS 11/12, so I decided to add a custom class to draw the icons without adding any .png

enter image description here

This is implemented for .trash and .action that are the icons I need in my project. Feel free to add the rest.

Use it as follows (You need to conform to the SystemIConDelegate protocol, set the Delegate property and add the required method):

let trashIcon = SystemIcon(withType: .trash)
trashIcon.delegate = self
shareButton = UIButton()
shareButton.addSubview(trashIcon)

The SystemIcon class is here. It is optimized for 30 X 30 points:

import UIKit
protocol SystemIconDelegate {
    func systemIconButtonClicked()
}
class SystemIcon: UIView {
    var type: UIBarButtonItem.SystemItem!
    let color = UIColor.blue
    var delegate: SystemIconDelegate?   
    convenience init(withType: UIBarButtonItem.SystemItem) {
        self.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        self.backgroundColor = .clear
        self.type = withType
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.delegate?.systemIconButtonClicked()
    }
    override func draw(_ rect: CGRect) {
        let w = self.frame.width
        let h = self.frame.height
        let context = UIGraphicsGetCurrentContext()
        context?.setStrokeColor(color.cgColor)
        context?.setLineWidth(1.0)
        switch type! {
        case .action:
            //Box
            context?.stroke(CGRect(x: w * 0.16, y: h * 0.3, width: w * 0.69, height: h * 0.69))
            context?.setFillColor(UIColor.white.cgColor)
            context?.fill(CGRect(x: w * 0.4, y: 0, width: w * 0.2, height: h * 0.5))
            //Arrow
            context?.move(to: CGPoint(x: w * 0.5, y: h * 0.02))
            context?.addLine(to: CGPoint(x: w * 0.5, y: h * 0.64))
            context?.move(to: CGPoint(x: w * 0.33, y: h * 0.19))
            context?.addLine(to: CGPoint(x: w * 0.5, y: h * 0.02))
            context?.addLine(to: CGPoint(x: w * 0.67, y: h * 0.19))
            context?.strokePath()
        case .trash:
            context?.move(to: CGPoint(x: w * 0.1, y: h * 0.15))
            context?.addLine(to: CGPoint(x: w * 0.9, y: h * 0.15))
            //Can
            context?.move(to: CGPoint(x: w * 0.2, y: h * 0.15))
            context?.addArc(tangent1End: CGPoint(x: w * 0.25, y: h * 0.95), tangent2End: CGPoint(x: w * 0.5, y:h * 0.95), radius: CGFloat.x(2.0))
            context?.addArc(tangent1End: CGPoint(x: w * 0.75, y: h * 0.95), tangent2End: CGPoint(x: w * 0.8, y: h * 0.15), radius: CGFloat.x(2.0))
            context?.addLine(to: CGPoint(x: w * 0.8, y: h * 0.15))
            // Handle
            context?.move(to: CGPoint(x: w * 0.34, y: h * 0.15))
            context?.addArc(tangent1End: CGPoint(x: w * 0.34, y: h * 0.02), tangent2End: CGPoint(x: w * 0.5, y: h * 0.02), radius: CGFloat.x(2.0))
            context?.addArc(tangent1End: CGPoint(x: w * 0.66, y : h * 0.02), tangent2End: CGPoint(x: w * 0.66, y: h * 0.15), radius: CGFloat.x(2.0))
            context?.addLine(to: CGPoint(x: w * 0.66, y: h * 0.15))
            //Lines
            context?.move(to: CGPoint(x: w * 0.35, y: h * 0.25))
            context?.addLine(to: CGPoint(x: w * 0.38, y: h * 0.8))
            context?.move(to: CGPoint(x: w * 0.5, y: h * 0.25))
            context?.addLine(to: CGPoint(x: w * 0.5, y: h * 0.8))
            context?.move(to: CGPoint(x: w * 0.65, y: h * 0.25))
            context?.addLine(to: CGPoint(x: w * 0.62, y: h*0.8))
        default:
            break
        }
        context?.strokePath()
    }
}
Donahue answered 4/10, 2018 at 18:58 Comment(0)
L
1

I was using it for imageView in table cell and somehow tint color did not work wherever I set it... So I ended up with this code where you set color for image.

+ (UIImage *)imageFromSystemBarButton:(UIBarButtonSystemItem)systemItem :(UIColor *) color {
    UIToolbar *bar = UIToolbar.new;
    UIBarButtonItem *buttonItem = [UIBarButtonItem createWithItem:systemItem];
    [bar setItems:@[buttonItem] animated:NO];
    [bar snapshotViewAfterScreenUpdates:YES];
    for (UIView *view in [(id) buttonItem view].subviews)
        if ([view isKindOfClass:UIButton.class]) {
            UIImage *image = [((UIButton *) view).imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
            UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
            [color set];
            [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
            image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            return image;
    }
    return nil;
}
Lynden answered 13/4, 2018 at 17:41 Comment(0)
D
1

Based on @yycking answer, here is the Xamarin.iOS C# port:

[DllImport (Constants.ObjectiveCLibrary, EntryPoint = "objc_msgSend")]
static extern IntPtr IntPtr_objc_msgSend (IntPtr receiver, IntPtr selector);

public static UIImage GetImage (UIBarButtonSystemItem systemItem)
{
    var tempItem = new UIBarButtonItem (systemItem);

    // Add to toolbar and render it
    var bar = new UIToolbar ();
    bar.SetItems (new [] { tempItem }, false);
    bar.SnapshotView (true);

    // Get image from real UIButton
    var selHandle = Selector.GetHandle ("view");
    var itemView = Runtime.GetNSObject<UIView> (IntPtr_objc_msgSend (tempItem.Handle, selHandle));
    foreach (var view in itemView?.Subviews) {
        if (view is UIButton button)
            return button.ImageForState (UIControlState.Normal);
    }

    return null;
}
Deceitful answered 24/2, 2019 at 16:25 Comment(0)
P
0

Swift 5

let delete = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(deleteSelected))

Pekingese answered 26/4, 2021 at 5:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.