在iOS中,使用表视图披露指示器样式来为UIButton设计样式。

46

我有一个自定义视图,想要模仿在表格视图单元格中发现的披露指示器。这可行吗?有没有提取该图像的方法?

我有一个自定义视图,想要模仿在表格视图单元格中发现的披露指示器。这可行吗?有没有提取该图像的方法?

1
很棒的问题 - 很烦人它不可用! - Fattie
这是一个Photoshop文件中最佳的匹配,带有正确的透明背景:http://www.filedropper.com/fakearrowiosnov2013psd - Fattie
是的,这已经存在了,真是太愚蠢了,因为它在TableView中被使用,但你不能在其他地方使用它。更糟糕的是,当Apple的文档显示使用该图像的截图时。 - lostintranslation
11个回答

88

可以通过代码完全实现,方法是在UIButton中放置带有披露指示器的UITableViewCell

UITableViewCell *disclosure = [[UITableViewCell alloc] init];
disclosure.frame = button.bounds;
disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
disclosure.userInteractionEnabled = NO; 

[button addSubview:disclosure];

Swift:

let disclosure = UITableViewCell()
disclosure.frame = button.bounds
disclosure.accessoryType = .disclosureIndicator
disclosure.isUserInteractionEnabled = false

button.addSubview(disclosure)

7
有点丑,但它能够正常工作并产生所需的效果(操作系统镜像),而无需添加自定义镜像。+1 - Jeroen Bouma
2
也许有点丑,但比使用自己的图像要好得多,因为具备向前兼容性。 - Josh Liptzin
8
请勿在iOS 8中尝试此操作,因为您将会收到以下错误提示:*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Must translate autoresizing mask into constraints to have _setHostsLayoutEngine:YES.'每当您将UITableViewCell或其子类添加到非UITableView的UIView/UIView子类中时,就会发生这种情况。请参阅相关帖子:https://dev59.com/SmAf5IYBdhLWcg3w2Fp6 - Pat
3
这个解决方案对我在使用8.1版本、Swift语言和Xcode 6.2的时候有效。我尝试过将translatesAutoresizingMaskIntoConstraints设置为true和false,两种方案都可以。 - Andrew Duncan
1
@sunny 这段代码是用于将披露指示器添加到UIButton上的。如果您只想在普通表视图单元格中使用披露指示器,在从cellForRowAtIndex path返回单元格之前,请确保设置cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator。 - Avario
显示剩余4条评论

31

由于苹果提供了官方iOS设计资源,您可以从中提取Chevron。


更新

苹果在WWDC'19主题演讲上宣布了图标字体SF Symbols

SF Symbols配套应用程序包含一个名为chevron.right的图标,可供使用。您还可以指定图标的粗细程度。

输入图像描述


12
请注意必须使用透明的背景
这是我在Photoshop中得到的最佳匹配:
请注意,它包括真实的iOS图像(来自屏幕截图)作为底层,以便进行比较。
链接:http://www.filedropper.com/fakearrowiosnov2013psd
VBK似乎想要从UITableView集合中获取单个尖括号,称为“披露指标”,而不是UIButton提供的“详细信息披露”。
我认为您想要类似于以下内容:
图片链接:UITableView Disclosure Indicator Image for Buttons 它是50x80,具有透明的背景。将此图像放在按钮或UIImageView上方。将其调整为所需的任何大小。 Apple建议击中目标不少于40x40。我在storyboard中将其调整为10x16,但我使用了透明的按钮叠加层,因此大小无关紧要。
镜像图片 : http://imgur.com/X00qn0Z.png
请注意,这并不是精确使用于iOS7的图像(2013年11月)。要获取确切的图像,只需在模拟器中运行应用程序并截屏即可。

1
很好的答案--似乎在iOS中你无法获取箭头图片。该网站上许多关于此问题的提问都很混乱,因为人们混淆了细节披露图标(那是愚蠢、无用的“帮助符号”图标)。例如,这个关于旋转的答案非常好,但不幸的是它只适用于无用的“披露”图标。 mevdev是正确的(2013年),你只需要使用PNG格式的图片。我称之为fakeArrow.png :) - Fattie

4

我喜欢使用UIBezierPath来绘制它。这样我就可以自由地调整大小,而不会失去清晰度。同时,如果需要改变颜色,也可以在不使用照片编辑软件的情况下实现。这个原则是通用的,适用于任何给定的路径。使用方法非常简单:

//suppose we want to apply disclosure arrow image to this button:
@IBOutlet weak var btnDisclosure: UIButton!

现在我所需要做的就是:
//get an image from UIBezierPath, resize it for the button and stroke with white:
let arrowImage = UIImage.imageWithBezierPath(UIBezierPath.disclosureArrowPath().scaleToAspectFitRect(CGRect(x: 0, y: 0, width: 22, height: 22)), fillColor: UIColor.clearColor(), strokeColor: UIColor.whiteColor())

//assign disclosure arrow image to the button:
btnDisclosure.setImage(arrowImage, forState: .Normal)

因此,这是一段绘制外形类似披露按钮的UIBezierPath的代码:
extension UIBezierPath
{
    ///Disclosure arrow path. Use scaleToAspectFitRect to resize it to any given rect.
    class func disclosureArrowPath() -> UIBezierPath
    {
        //// arrow Drawing
        let arrowPath = UIBezierPath()
        arrowPath.moveToPoint(CGPointMake(4, 4))
        arrowPath.addLineToPoint(CGPointMake(26.5, 25.24))
        arrowPath.addLineToPoint(CGPointMake(4, 47.5))
        arrowPath.lineWidth = 3

        return arrowPath
    }  

    ///Makes a path scalable to any size.
    ///- parameter newRect: The path will be resized to aspect fit into this rectangle.
    func scaleToAspectFitRect(newRect: CGRect) -> UIBezierPath
    {
        var scaleFactor : CGFloat = 1.0

        //this is probably only the case of scale factor < 1:
        if bounds.width > bounds.height
        {
            //fit witdth:
            scaleFactor = newRect.width/bounds.width
        }
        else
        {
            //fit height:
            scaleFactor = newRect.height/bounds.height
        }

        //scale to aspect fill rect:
        self.applyTransform(CGAffineTransformMakeScale(scaleFactor, scaleFactor))

        return self
    }
}

接下来,您需要一种方法从UIBezierPath中获取UIImage。同样,您可以像这样添加扩展程序到UIImage中来实现:

extension UIImage
{
    ///Custom fill and stroke colours for our image based on UIBezierPath
    class func imageWithBezierPath(path: UIBezierPath, fillColor: UIColor, strokeColor: UIColor) -> UIImage
    {
        //enlarge the rect so that stroke line is not clipped:
        let rect = CGRectInset(path.bounds, -path.lineWidth / 2, -path.lineWidth / 2)

        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0) //size of the image, opaque, and scale (set to screen default with 0)

        let bezierLayer = CAShapeLayer()
        bezierLayer.path = path.CGPath;
        bezierLayer.fillColor = fillColor.CGColor
        bezierLayer.strokeColor = strokeColor.CGColor
        bezierLayer.lineWidth = path.lineWidth;

        let imgViewTmp = UIImageView(frame: path.bounds)
        imgViewTmp.layer.addSublayer(bezierLayer);
        imgViewTmp.layer.renderInContext(UIGraphicsGetCurrentContext()!)

        let image = UIGraphicsGetImageFromCurrentImageContext()

        //UIGraphicsEndImageContext()
        return image
    }
}

对于这个特定任务来说,可能看起来有些过度,但它是通用的。如果你经常处理调整大小、尝试找出正确设计等工作,那么它真的非常方便。


3
我制作了一个完全由代码实现的解决方案,用于绘制类似于UITableView披露指示器的箭头。
使用方法如下:
let arrowImage = ArrowImageGenerator.generateArrow(withDirection: .down)

默认箭头与UITableView的披露指示符相同。 您可以自定义方向(上,下,左,右),大小,颜色等等。 以下是代码:
//
//  ArrowImageGenerator.swift
//
//  Created by Alessio Orlando on 07/10/15.
//  Copyright © 2015 Alessio Orlando. All rights reserved.
//

import Foundation
import UIKit

enum ArrowDirection {
    case up
    case down
    case left
    case right
}

class ArrowImageGenerator {

    static var defaultColor: UIColor = {
        let color = UIColor(red: 0.783922, green: 0.780392, blue: 0.8, alpha: 1)
        return color
    }()

    class func generateArrow(withDirection direction: ArrowDirection = .right,
                             size: CGSize? = nil,
                             lineWidth: CGFloat = 2.0,
                             arrowColor: UIColor = ArrowImageGenerator.defaultColor,
                             backgroundColor: UIColor = UIColor.clear,
                             scale: CGFloat = UIScreen.main.scale)
        -> UIImage? {

            var actualSize: CGSize
            if let size = size {
                actualSize = size
            }
            else {
                actualSize = defaultSize(for: direction)
            }

            let scaledSize = actualSize.applying(CGAffineTransform(scaleX: scale, y: scale))
            let scaledLineWidth = lineWidth * scale

            UIGraphicsBeginImageContext(CGSize(width: scaledSize.width, height: scaledSize.height))
            defer {
                UIGraphicsEndImageContext()
            }

            guard let context = UIGraphicsGetCurrentContext() else { return nil }
            configureForArrowDrawing(context)

            UIGraphicsPushContext(context)
            strokeArrow(context, size: scaledSize, arrowColor: arrowColor, backgroundColor: backgroundColor, lineWidth: scaledLineWidth, direction: direction)
            UIGraphicsPopContext()

            guard let outputImage = UIGraphicsGetImageFromCurrentImageContext(),
                let quartzImage = context.makeImage() else {
                return nil
            }

            let scaledImage = UIImage(cgImage: quartzImage, scale: scale, orientation: outputImage.imageOrientation)
            return scaledImage
    }

    private class func generateResizableArrow(_ arrowImage: UIImage, direction: ArrowDirection) -> UIImage {
        var edgeInset: UIEdgeInsets?
        switch direction {
        case .up:
            edgeInset = UIEdgeInsets(top: 11, left: 0, bottom: 1, right: 0)
        case .down:
            edgeInset = UIEdgeInsets(top: 1, left: 0, bottom: 11, right: 0)
        case .left:
            edgeInset = UIEdgeInsets(top: 1, left: 11, bottom: 1, right: 0)
        case .right:
            edgeInset = UIEdgeInsets(top: 1, left: 0, bottom: 1, right: 11)
        }
        let resizableImage = arrowImage.resizableImage(withCapInsets: edgeInset!)
        return resizableImage
    }

    private class func configureForArrowDrawing(_ context: CGContext) {
        context.setBlendMode(CGBlendMode.normal)
        context.setAllowsAntialiasing(true)
        context.setShouldAntialias(true)
    }

    private class func strokeArrow(_ context: CGContext, size: CGSize, arrowColor: UIColor, backgroundColor: UIColor, lineWidth: CGFloat = 1.0, direction: ArrowDirection) {
        backgroundColor.setFill()
        UIRectFill(CGRect(origin: CGPoint(x: 0, y: 0), size: size))
        arrowColor.setStroke()
        context.setLineWidth(lineWidth)
        let lineWidthOffset = lineWidth / 2 // needed to make the arrow pointy.

        switch direction {
        case .up:
            context.move(to: CGPoint(x: size.width, y: size.height))
            context.addLine(to: CGPoint(x: size.width / 2, y: 0 + lineWidthOffset))
            context.addLine(to: CGPoint(x: 0, y: size.height))
        case .down:
            context.move(to: CGPoint(x: size.width, y: 0))
            context.addLine(to: CGPoint(x: size.width / 2, y: size.height - lineWidthOffset))
            context.addLine(to: CGPoint(x: 0, y: 0))
        case .left:
            context.move(to: CGPoint(x: size.width, y: 0))
            context.addLine(to: CGPoint(x: lineWidthOffset, y: size.height / 2))
            context.addLine(to: CGPoint(x: size.width, y: size.height))
        case .right:
            context.move(to: CGPoint(x: 0, y: 0))
            context.addLine(to: CGPoint(x: size.width - lineWidthOffset, y: size.height / 2))
            context.addLine(to: CGPoint(x: 0, y: size.height))
        }
        context.strokePath()
    }

    class func defaultSize(for direction: ArrowDirection) -> CGSize {
        switch direction {
        case .up, .down:
            return CGSize(width: 12, height: 7)
        case .left, .right:
            return CGSize(width: 7, height: 12)
        }
    }
}

这是完整的要点:github gist

2

1
这对我有效:

UITableViewCell *disclosure = [[UITableViewCell alloc] init];
disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
for (UIView*v1 in disclosure.subviews)
{
   if ([v1 isKindOfClass:[UIButton class]])
   {
       for (UIView*v2 in v1.subviews)
       {
           if ([v2 isKindOfClass:[UIImageView class]])
           {
               return ((UIImageView*)v2).image;
           }
       }
   }
}

1

SWIFT 5

private lazy var iconImageView: UIImageView = {
    let imageView = UIImageView()
    let configuration = UIImage.SymbolConfiguration(pointSize: 13, weight: .medium)
    imageView.image = UIImage(systemName: "chevron.right", withConfiguration: configuration)
    imageView.tintColor = .lightGray
    imageView.contentMode = .scaleAspectFit
    imageView.constrainAspectRatio(17.0/10.0)
    imageView.translatesAutoresizingMaskIntoConstraints = false
    return imageView
}()

纵横比 UIView 扩展
extension UIView {

/// Ratio height/width.  Example: 20/40 (20 is height, 40 is width)
func constrainAspectRatio(_ ratio: CGFloat) {
    NSLayoutConstraint(item: self,
                       attribute: .height,
                       relatedBy: .equal,
                       toItem: self,
                       attribute: .width,
                       multiplier: ratio,
                       constant: 0).isActive = true
    }
}

0

Swift3 / Swift4:

为按钮添加披露指示器

        let disclosureIndicator = UITableViewCell(style: .value1, 
        reuseIdentifier: nil)
        let theWidth = UIScreen.main.bounds.width
        let theHeight = yourButton.frame.height
        yourButton.frame = CGRect(0,0, theWidth, theHeight)
        disclosureIndicator.textLabel?.text = "title"
        disclosureIndicator.detailTextLabel?.textColor = .black
        disclosureIndicator.detailTextLabel?.text = "subtitle"
        disclosureIndicator.accessoryType = .disclosureIndicator
        disclosureIndicator.isUserInteractionEnabled = false
        disclosureIndicator.frame = yourButton.bounds

        yourButton.addSubview(disclosureIndicator)

为CGRect添加此扩展

extension CGRect {
    init(_ x:CGFloat, _ y:CGFloat, _ w:CGFloat, _ h:CGFloat) {
        self.init(x:x, y:y, width:w, height:h)
    }
}

如果您给出负面评价,请留下评论! - khanbdse

0

对于 Xamarin.iOS

//create your button
var systolicWell = new UIButton(UIButtonType.RoundedRect);
systolicWell.BackgroundColor = UIColor.White;

//create the UITableViewCell
var systolicDisclosure = new UITableViewCell();
systolicDisclosure.Accessory = UITableViewCellAccessory.DisclosureIndicator;
systolicDisclosure.UserInteractionEnabled = false;

//add the button, then the UITableViewCell to the View
View.AddSubviews(systolicWell, systolicDisclosure);

//using FluentLayout https://github.com/slodge/Cirrious.FluentLayout
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();

View.AddConstraints(
                systolicWell.AtTopOf(View).Plus(5),
                systolicWell.Width().EqualTo().WidthOf(View),
                systolicWell.Height().EqualTo(10),

                systolicDisclosure.WithSameTop(systolicWell),
                systolicDisclosure.WithSameWidth(systolicWell),
                systolicDisclosure.WithSameHeight(systolicWell));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接