【问题标题】:Use UIBarButtonItem icon in UIButton在 UIButton 中使用 UIBarButtonItem 图标
【发布时间】:2014-01-17 14:12:20
【问题描述】:

UIBarButtonItem 有多个图标可用。是否可以使用将其标识符设置为“垃圾”后出现的图标:

UIButton?没有像设置 identifierstyle 这样的简单方法。

【问题讨论】:

    标签: ios cocoa-touch uibutton uibarbuttonitemstyle


    【解决方案1】:

    iOS 13 支持 SF Symbols 现在
    UIImage(systemName: "trash")


    对于 swift 4.2 (在主线程上调用)

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

    对于 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;
    }
    

    【讨论】:

    • 要让图像使用按钮的色调,添加 .imageWithRenderingMode(.AlwaysTemplate)
    • 在 Xcode 9 中不再工作。图像必须先渲染才能提取。
    • 此解决方案在 Xcode 9/iOS 11 中不起作用。请用警卫将答案更新为?查看并且不要强制展开。我的应用因此崩溃了。
    • 部署目标 11.4 (Swift 4.2):让 itemView = tempItem.value(forKey: "view") as! UIView = 线程 1:致命错误:在展开可选值时意外发现 nil
    【解决方案2】:

    这是一个适用于ANY系统栏按钮项目的解决方案+它支持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;
    }
    


    附言如果您知道更好的方法,请随时改进,谢谢。

    【讨论】:

    • 谢谢,这对我帮助很大。我发现我可以直接使用 tempItem 进入酒吧。虽然不完全是 UIButton,但我很难用谷歌搜索答案。
    • 不客气。我试图尽可能地对其进行分解,以便于理解。
    • 如何将UIView *itemView = (UIView *)[self.navigationItem.rightBarButtonItem performSelector:@selector(view)]; 翻译成 Swift?
    • 没有在 Swift 中做过,但我希望对这个 question 的回答会有所帮助。
    • 隐藏了一个陷阱 ;-) 仅当 UINavigationItem 当前呈现在屏幕上时,此代码才有效。例如,如果当前未加载视图控制器(例如,如果它是 UITabBarController 中未选择的视图控制器之一),则会失败。
    【解决方案3】:

    从网络上的某处下载图像,将其添加到您的项目中并将UIButton 的图像设置为您刚刚下载的图像。

    我没有找到与 Apple 使用的相同,但我找到了 this one。只需在 Pixelmator 或 Photoshop 中更改它的颜色。

    【讨论】:

    • 还不错,但我宁愿它是蓝色的。
    • 您可以在 Photoshop 中轻松做到这一点,我什至认为可以使用 tintColor 做到这一点。让我检查一下。
    • 无法使其与 tintColor 一起使用。应该不是问题,因为您可以在 Photoshop 或 Pixelmator 中简单地更改它的颜色。
    • 很遗憾接受的答案应该违反版权规则......“将图像复制到某处并将其添加到您的项目中”
    • @Lepidopteron 实际上有许多“某处”正是为此目的而制作的
    【解决方案4】:

    根据@yycking 的回答,我写了一个合适的 Swift 4 扩展:

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

    动作按钮示例(默认颜色):

    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)
    

    如果您希望您的图像始终被视为模板,无论如何 上下文,将renderingMode设置为.alwaysTemplate

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

    【讨论】:

    • 颜色正在改变!默认颜色为 blue 但您的代码输出颜色为 black 。有什么解决办法吗?
    • @sanju:我已将默认渲染模式更新为 .automatic(默认蓝色)
    • @Sanju,那是因为 UIBarButtonItem 使用模板图像。图像是黑色的,但是当它们用条的色调绘制时。 More about template rendering mode.
    • 感谢彼得的回答,我只想指出它不再适用于 Swift 4,tempItem.value(forKey: "view") 为 nil 并且强制展开会出错。
    • 我一直得到零回报......让 itemView = tempItem.value(forKey: "view") as!界面视图
    【解决方案5】:

    正如@Islam Q 的答案中已经提到的那样,如果 UINavigationItem 当前未在屏幕上呈现,则所提出的解决方案可能会失败。例如,如果当前未加载视图控制器,它会失败。事实上,在这些情况下,问题似乎是 UINavigationBar 缺少布局。

    更“防弹”的版本是使用专门创建的 UINavigationBar 对象来获取系统项目图像。这也将使保存和恢复任何现有的 UIBarButtonItems 过时。

    我已经把它打包到一个小的帮助类中:

    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
    

    作为 add on/fallback ,如果无法检索到系统项目图像,该方法将返回自定义图像。当然,这些自定义图像必须存在于应用程序包中。

    最后一个方法“barButtonImages”是为了好奇而实现的……在 UIBarButtonItem 标头中,成员 systemItem 被声明为使用 7 位 (0..127)。目前,从 UIBarButtonSystemItemDone 到 UIBarButtonItemSystemItemPageCurl 仅记录了 22 个值......事实上;我发现了一些索引超过 100 的未记录图像(在 6S+ 模拟器中的 iOS 9.3 上测试):-)

    【讨论】:

      【解决方案6】:

      所有 iOS 系统图标都可以使用一个方便的小应用程序(非常合适)iOS Artwork Extractor 来提取。当我想模仿 iOS 系统行为时,我一直使用它。

      在以下位置下载 Xcode 项目:

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

      【讨论】:

        【解决方案7】:

        在 Swift 4.2 中使用 Quartz2D

        基于扩展从UIBarButtonSystemItem 中提取图像的解决方案在 iOS 11/12 中不起作用,因此我决定添加一个自定义类来绘制图标而不添加任何 .png

        这是为 .trash 和 .action 实现的,它们是我项目中需要的图标。随意添加其余部分。

        如下使用(需符合SystemIConDelegate协议,设置Delegate属性并添加所需方法):

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

        SystemIcon 课程在这里。它针对 30 X 30 点进行了优化:

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

        【讨论】:

          【解决方案8】:

          我将它用于表格单元格中的 imageView,但不知何故,无论我在哪里设置,色调颜色都不起作用......所以我最终得到了这段代码,你可以在其中为图像设置颜色。

          + (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;
          }
          

          【讨论】:

            【解决方案9】:

            基于@yycking answer,这里是 Xamarin.iOS C# 端口:

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

            【讨论】:

              【解决方案10】:

              斯威夫特 5

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-05-03
                • 1970-01-01
                • 2017-11-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-06-30
                相关资源
                最近更新 更多