I have a custom view and i want to imitate the disclosure indicator found in table view cells. Is this possible ? Is there anyway of extracting that image ?
This can be done entirely with code by placing a UITableViewCell
with the disclosure indicator within a UIButton
:
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)
Since Apple provides official iOS design resources for different tools you can extract the chevron from there.
Apple announced the icon font SF Symbols on the WWDC'19 keynote.
The SF Symbols companion app contains an icon called chevron.right
ready for you to use. You can also Specify the thickness of the icon.
Note that you must use a transparent background
Here's the best possible match I can get, in a photoshop file:
Note that it includes the REAL iOS IMAGE (from a screenshot) underneath as a layer, so you can compare.
http://www.filedropper.com/fakearrowiosnov2013psd
It seems like VBK wants the single chevron from the UITableView collection. Which is called 'Disclosure Indicator' as opposed to the one available from UIButton which is 'Detail Disclosure'.
I think you want something like this:
It is 50x80 with a transparent background. Use this image on top of a button or UIImageView. Resize it to whatever size you'd like your button to be. Apple recommends a hit target of no less than 40x40. I sized it to 10x16 in my storyboard, but I am using a transparent button overlay so the size doesn't matter.
Image Mirror : http://imgur.com/X00qn0Z.png
Just note however this is not precisely the image used in iOS7. (Nov 2013.) To get the exact image, simply run an app in retina in your simulator, and make a screenshot.
What I like to do is to draw it using UIBezierPath
. This gives me a freedom to resize if I want to, without loosing clarity. And it also gives me an opportunity to change the colour later on if that's what I need without a photo editor.The principle is generic and applicable to any given path. The usage is quite simple:
//suppose we want to apply disclosure arrow image to this button:
@IBOutlet weak var btnDisclosure: UIButton!
All I need to do now is:
//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)
So, a piece of code for drawing the UIBezierPath which looks like disclosure button:
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
}
}
Next, you need a way to get the UIImage
out of UIBezierPath
. Again, you could add extension to UIImage that will do this like so:
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
}
}
It may seem as an overkill for this specific task, but it's generic. And it's really handy if you deal a lot with resizing, trying to figure out the right design etc.
I made an entirely in code solution to draw arrows similar to UITableView disclosure indicator.
It's used like this:
let arrowImage = ArrowImageGenerator.generateArrow(withDirection: .down)
The default arrow looks the same as the default for the UITableView disclosure indicator. If you want you can customize direction (up, down, left, right), size, color and so on.
Here is the code:
//
// 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)
}
}
}
Here is the complete gist: github gist
You can extract the graphic images from the Xcode Simulator using this Xcode project - https://github.com/0xced/iOS-Artwork-Extractor
This worked for me:
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;
}
}
}
}
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
}()
Aspect Ratio UIView Extension
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
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With