How do I display the standard checkmark on a UICollectionViewCell?

20,400

Solution 1

I ended up recreating the checkmarks using PaintCode. Here's what they look like:

Custom-made checkmarks

They're drawn with vector graphics, so they'll look great at whatever size you want. These are 30x30. I also included an option to use a grayed-out checkmark instead of the open circle when an item is not selected.

To use these, copy the following class into your project. Then, add a UIView to your storyboard or xib, and set its custom class to SSCheckMark.

SSCheckMark.h

#import <UIKit/UIKit.h>

typedef NS_ENUM( NSUInteger, SSCheckMarkStyle )
{
    SSCheckMarkStyleOpenCircle,
    SSCheckMarkStyleGrayedOut
};

@interface SSCheckMark : UIView

@property (readwrite) bool checked;
@property (readwrite) SSCheckMarkStyle checkMarkStyle;

@end

SSCheckMark.m

#import "SSCheckMark.h"

@implementation SSCheckMark

- (void) drawRect:(CGRect)rect
{
    [super drawRect:rect];

    if ( self.checked )
        [self drawRectChecked:rect];
    else
    {
        if ( self.checkMarkStyle == SSCheckMarkStyleOpenCircle )
            [self drawRectOpenCircle:rect];
        else if ( self.checkMarkStyle == SSCheckMarkStyleGrayedOut )
            [self drawRectGrayedOut:rect];
    }
}

- (void) setChecked:(bool)checked
{
    _checked = checked;
    [self setNeedsDisplay];
}

- (void) setCheckMarkStyle:(SSCheckMarkStyle)checkMarkStyle
{
    _checkMarkStyle = checkMarkStyle;
    [self setNeedsDisplay];
}

- (void) drawRectChecked: (CGRect) rect
{
    //// General Declarations
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Color Declarations
    UIColor* checkmarkBlue2 = [UIColor colorWithRed: 0.078 green: 0.435 blue: 0.875 alpha: 1];

    //// Shadow Declarations
    UIColor* shadow2 = [UIColor blackColor];
    CGSize shadow2Offset = CGSizeMake(0.1, -0.1);
    CGFloat shadow2BlurRadius = 2.5;

    //// Frames
    CGRect frame = self.bounds;

    //// Subframes
    CGRect group = CGRectMake(CGRectGetMinX(frame) + 3, CGRectGetMinY(frame) + 3, CGRectGetWidth(frame) - 6, CGRectGetHeight(frame) - 6);


    //// Group
    {
        //// CheckedOval Drawing
        UIBezierPath* checkedOvalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(group) + floor(CGRectGetWidth(group) * 0.00000 + 0.5), CGRectGetMinY(group) + floor(CGRectGetHeight(group) * 0.00000 + 0.5), floor(CGRectGetWidth(group) * 1.00000 + 0.5) - floor(CGRectGetWidth(group) * 0.00000 + 0.5), floor(CGRectGetHeight(group) * 1.00000 + 0.5) - floor(CGRectGetHeight(group) * 0.00000 + 0.5))];
        CGContextSaveGState(context);
        CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        [checkmarkBlue2 setFill];
        [checkedOvalPath fill];
        CGContextRestoreGState(context);

        [[UIColor whiteColor] setStroke];
        checkedOvalPath.lineWidth = 1;
        [checkedOvalPath stroke];


        //// Bezier Drawing
        UIBezierPath* bezierPath = [UIBezierPath bezierPath];
        [bezierPath moveToPoint: CGPointMake(CGRectGetMinX(group) + 0.27083 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.54167 * CGRectGetHeight(group))];
        [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(group) + 0.41667 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.68750 * CGRectGetHeight(group))];
        [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(group) + 0.75000 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.35417 * CGRectGetHeight(group))];
        bezierPath.lineCapStyle = kCGLineCapSquare;

        [[UIColor whiteColor] setStroke];
        bezierPath.lineWidth = 1.3;
        [bezierPath stroke];
    }
}

- (void) drawRectGrayedOut: (CGRect) rect
{
    //// General Declarations
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Color Declarations
    UIColor* grayTranslucent = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.6];

    //// Shadow Declarations
    UIColor* shadow2 = [UIColor blackColor];
    CGSize shadow2Offset = CGSizeMake(0.1, -0.1);
    CGFloat shadow2BlurRadius = 2.5;

    //// Frames
    CGRect frame = self.bounds;

    //// Subframes
    CGRect group = CGRectMake(CGRectGetMinX(frame) + 3, CGRectGetMinY(frame) + 3, CGRectGetWidth(frame) - 6, CGRectGetHeight(frame) - 6);


    //// Group
    {
        //// UncheckedOval Drawing
        UIBezierPath* uncheckedOvalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(group) + floor(CGRectGetWidth(group) * 0.00000 + 0.5), CGRectGetMinY(group) + floor(CGRectGetHeight(group) * 0.00000 + 0.5), floor(CGRectGetWidth(group) * 1.00000 + 0.5) - floor(CGRectGetWidth(group) * 0.00000 + 0.5), floor(CGRectGetHeight(group) * 1.00000 + 0.5) - floor(CGRectGetHeight(group) * 0.00000 + 0.5))];
        CGContextSaveGState(context);
        CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        [grayTranslucent setFill];
        [uncheckedOvalPath fill];
        CGContextRestoreGState(context);

        [[UIColor whiteColor] setStroke];
        uncheckedOvalPath.lineWidth = 1;
        [uncheckedOvalPath stroke];


        //// Bezier Drawing
        UIBezierPath* bezierPath = [UIBezierPath bezierPath];
        [bezierPath moveToPoint: CGPointMake(CGRectGetMinX(group) + 0.27083 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.54167 * CGRectGetHeight(group))];
        [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(group) + 0.41667 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.68750 * CGRectGetHeight(group))];
        [bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(group) + 0.75000 * CGRectGetWidth(group), CGRectGetMinY(group) + 0.35417 * CGRectGetHeight(group))];
        bezierPath.lineCapStyle = kCGLineCapSquare;

        [[UIColor whiteColor] setStroke];
        bezierPath.lineWidth = 1.3;
        [bezierPath stroke];
    }
}

- (void) drawRectOpenCircle: (CGRect) rect
{
    //// General Declarations
    CGContextRef context = UIGraphicsGetCurrentContext();


    //// Shadow Declarations
    UIColor* shadow = [UIColor blackColor];
    CGSize shadowOffset = CGSizeMake(0.1, -0.1);
    CGFloat shadowBlurRadius = 0.5;
    UIColor* shadow2 = [UIColor blackColor];
    CGSize shadow2Offset = CGSizeMake(0.1, -0.1);
    CGFloat shadow2BlurRadius = 2.5;

    //// Frames
    CGRect frame = self.bounds;

    //// Subframes
    CGRect group = CGRectMake(CGRectGetMinX(frame) + 3, CGRectGetMinY(frame) + 3, CGRectGetWidth(frame) - 6, CGRectGetHeight(frame) - 6);


    //// Group
    {
        //// EmptyOval Drawing
        UIBezierPath* emptyOvalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(CGRectGetMinX(group) + floor(CGRectGetWidth(group) * 0.00000 + 0.5), CGRectGetMinY(group) + floor(CGRectGetHeight(group) * 0.00000 + 0.5), floor(CGRectGetWidth(group) * 1.00000 + 0.5) - floor(CGRectGetWidth(group) * 0.00000 + 0.5), floor(CGRectGetHeight(group) * 1.00000 + 0.5) - floor(CGRectGetHeight(group) * 0.00000 + 0.5))];
        CGContextSaveGState(context);
        CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        CGContextRestoreGState(context);

        CGContextSaveGState(context);
        CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor);
        [[UIColor whiteColor] setStroke];
        emptyOvalPath.lineWidth = 1;
        [emptyOvalPath stroke];
        CGContextRestoreGState(context);
    }
}

@end

Solution 2

Swift 3 version of Chris Vasellis' nice solution:

import UIKit

enum SSCheckMarkStyle : UInt {
    case OpenCircle
    case GrayedOut
}

class SSCheckMark: UIView {

private var checkedBool: Bool = false
// choose whether you like open or grayed out non-selected items
private var checkMarkStyleReal: SSCheckMarkStyle=SSCheckMarkStyle.GrayedOut

var checked: Bool {
    get {
        return self.checkedBool
    }
    set(checked) {
        self.checkedBool = checked
        self.setNeedsDisplay()
    }
}

var checkMarkStyle: SSCheckMarkStyle {
    get {
        return self.checkMarkStyleReal
    }
    set(checkMarkStyle) {
        self.checkMarkStyleReal = checkMarkStyle
        self.setNeedsDisplay()
    }
}

override func draw(_ rect: CGRect) {
    super.draw(rect)
    if self.checked {
        self.drawRectChecked(rect: rect)
    } else {
        if self.checkMarkStyle == SSCheckMarkStyle.OpenCircle {
            self.drawRectOpenCircle(rect: rect)
        } else if self.checkMarkStyle == SSCheckMarkStyle.GrayedOut {
            self.drawRectGrayedOut(rect: rect)
        }
    }
}

func drawRectChecked(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    let checkmarkBlue2 = UIColor(red: 0.078, green: 0.435, blue: 0.875, alpha: 1)
    let shadow2 = UIColor.black

    let shadow2Offset = CGSize(width: 0.1, height: -0.1)
    let shadow2BlurRadius = 2.5
    let frame = self.bounds
    let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6)

    let checkedOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5)))

    context!.saveGState()
    context!.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor)
    checkmarkBlue2.setFill()
    checkedOvalPath.fill()
    context!.restoreGState()
    UIColor.white.setStroke()
    checkedOvalPath.lineWidth = 1
    checkedOvalPath.stroke()
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: group.minX + 0.27083 * group.width, y: group.minY + 0.54167 * group.height))
    bezierPath.addLine(to: CGPoint(x: group.minX + 0.41667 * group.width, y: group.minY + 0.68750 * group.height))
    bezierPath.addLine(to: CGPoint(x: group.minX + 0.75000 * group.width, y: group.minY + 0.35417 * group.height))
    bezierPath.lineCapStyle = CGLineCap.square
    UIColor.white.setStroke()
    bezierPath.lineWidth = 1.3
    bezierPath.stroke()
}

func drawRectGrayedOut(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    let grayTranslucent = UIColor(red: 1, green: 1, blue: 1, alpha: 0.6)
    let shadow2 = UIColor.black
    let shadow2Offset = CGSize(width: 0.1, height: -0.1)
    let shadow2BlurRadius = 2.5
    let frame = self.bounds
    let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6)
    let uncheckedOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5)))

    context!.saveGState()
    context!.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor)
    grayTranslucent.setFill()
    uncheckedOvalPath.fill()
    context!.restoreGState()
    UIColor.white.setStroke()
    uncheckedOvalPath.lineWidth = 1
    uncheckedOvalPath.stroke()
    let bezierPath = UIBezierPath()

    bezierPath.move(to: CGPoint(x: group.minX + 0.27083 * group.width, y: group.minY + 0.54167 * group.height))
    bezierPath.addLine(to: CGPoint(x: group.minX + 0.41667 * group.width, y: group.minY + 0.68750 * group.height))
    bezierPath.addLine(to: CGPoint(x: group.minX + 0.75000 * group.width, y: group.minY + 0.35417 * group.height))
    bezierPath.lineCapStyle = CGLineCap.square
    UIColor.white.setStroke()
    bezierPath.lineWidth = 1.3
    bezierPath.stroke()
}

func drawRectOpenCircle(rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    let shadow = UIColor.black
    let shadowOffset = CGSize(width: 0.1, height: -0.1)
    let shadowBlurRadius = 0.5
    let shadow2 = UIColor.black
    let shadow2Offset = CGSize(width: 0.1, height: -0.1)
    let shadow2BlurRadius = 2.5
    let frame = self.bounds
    let group = CGRect(x: frame.minX + 3, y: frame.minY + 3, width: frame.width - 6, height: frame.height - 6)
    let emptyOvalPath = UIBezierPath(ovalIn: CGRect(x: group.minX + floor(group.width * 0.00000 + 0.5), y: group.minY + floor(group.height * 0.00000 + 0.5), width: floor(group.width * 1.00000 + 0.5) - floor(group.width * 0.00000 + 0.5), height: floor(group.height * 1.00000 + 0.5) - floor(group.height * 0.00000 + 0.5)))

    context!.saveGState()
    context!.setShadow(offset: shadow2Offset, blur: CGFloat(shadow2BlurRadius), color: shadow2.cgColor)

    context!.restoreGState()
    context!.saveGState()
    context!.setShadow(offset: shadowOffset, blur: CGFloat(shadowBlurRadius), color: shadow.cgColor)
    UIColor.white.setStroke()
    emptyOvalPath.lineWidth = 1
    emptyOvalPath.stroke()
    context!.restoreGState()

}
}

To use it in your code, create a file called SSCheckMark.swift with the above contents, and assign it to your View. I use it in CollectionViewCells, for which I have created a custom class (code simplified heavily):

class myCollectionViewCell: UICollectionViewCell {

    var checkmarkView: SSCheckMark!

    override init(frame: CGRect) {
        super.init(frame: frame)
        checkmarkView = SSCheckMark(frame: CGRect(x: frame.width-40, y: 10, width: 35, height: 35))
        checkmarkView.backgroundColor = UIColor.white
        myView.addSubview(checkmarkView)
    }
}

And in my UIViewController, I have this:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell:ProductCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! myCollectionViewCell
    cell.checkmarkView.checked = myBoolValue
    return cell
}

Solution 3

One possibility is creating a UIView that draws concentric circles, then a checkmark character from your font of choice. To find checkmark characters, go to Edit > Special Characters within Xcode (or any other application with that menu item), and search for "check". When you select one of the search results, you'll see font variations at the bottom right.

enter image description here

Solution 4

C# version of the same code

public class CheckMarkView : UIView
{
    private bool _checked;
    private CheckMarkStyle _checkMarkStyle;

    public CheckMarkView()
    {
        Opaque = false;
    }

    public bool Checked
    {
        get
        {
            return _checked;
        }
        set
        {
            _checked = value;
            SetNeedsDisplay();
        }
    }

    public CheckMarkStyle CheckMarkStyle
    {
        get
        {
            return _checkMarkStyle;
        }
        set
        {
            _checkMarkStyle = value;
            SetNeedsDisplay();
        }
    }

    public override void Draw(CGRect rect)
    {
        if (Checked)
            DrawRectChecked(rect);
        else if (CheckMarkStyle == CheckMarkStyle.OpenCircle)
            DrawRectOpenCircle(rect);
        else if (CheckMarkStyle == CheckMarkStyle.GrayedOut)
            DrawRectGrayedOut(rect);
    }


    private void DrawRectChecked(CGRect rect)
    {
        var context = UIGraphics.GetCurrentContext();

        var checkmarkBlue2 = UIColor.FromRGBA(0.078f, 0.435f, 0.875f, 1f);

        // Shadow Declarations
        var shadow2 = UIColor.Brown;
        var shadow2Offset = new CGSize(0.1, -0.1);
        nfloat shadow2BlurRadius = 2.5f;

        var frame = Bounds;

        // Subframes
        var group = new CGRect(frame.GetMinX() + 3, frame.GetMinY() + 3, frame.Width - 6, frame.Height - 6);


        // CheckedOval Drawing
        var checkedOvalPath = UIBezierPath.FromOval(new CGRect(group.GetMinX() + Math.Floor(group.Width * 0.00000 + 0.5), group.GetMinY() + Math.Floor(group.Height * 0.00000 + 0.5), Math.Floor(group.Width * 1.00000 + 0.5) - Math.Floor(group.Width * 0.00000 + 0.5), Math.Floor(group.Height * 1.00000 + 0.5) - Math.Floor(group.Height * 0.00000f + 0.5f)));
        context.SaveState();
        context.SetShadow(shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        checkmarkBlue2.SetFill();
        checkedOvalPath.Fill();
        context.RestoreState();

        UIColor.White.SetStroke();
        checkedOvalPath.LineWidth = 1;
        checkedOvalPath.Stroke();


        // Bezier Drawing
        var bezierPath = new UIBezierPath();
        bezierPath.MoveTo(new CGPoint(group.GetMinX() + 0.27083f * group.Width, group.GetMinY() + 0.54167f * group.Height));
        bezierPath.AddLineTo(new CGPoint(group.GetMinX() + 0.41667f * group.Width, group.GetMinY() + 0.68750f * group.Height));
        bezierPath.AddLineTo(new CGPoint(group.GetMinX() + 0.75000f * group.Width, group.GetMinY() + 0.35417f * group.Height));
        bezierPath.LineCapStyle = CGLineCap.Square;

        UIColor.White.SetStroke();
        bezierPath.LineWidth = 1.3f;
        bezierPath.Stroke();
    }

    private void DrawRectGrayedOut(CGRect rect)
    {
        var context = UIGraphics.GetCurrentContext();

        var grayTranslucent = UIColor.FromRGBA(1, 1, 1, 0.6f);

        // Shadow Declarations
        var shadow2 = UIColor.Black;
        var shadow2Offset = new CGSize(0.1, -0.1);
        nfloat shadow2BlurRadius = 2.5f;

        var frame = Bounds;

        // Subframes
        var group = new CGRect(frame.GetMinX() + 3, frame.GetMinY() + 3, frame.Width - 6, frame.Height - 6);

        // UncheckedOval Drawing
        var uncheckedOvalPath = UIBezierPath.FromOval(new CGRect(group.GetMinX() + Math.Floor(group.Width * 0.00000 + 0.5), group.GetMinY() + Math.Floor(group.Height * 0.00000 + 0.5), Math.Floor(group.Width * 1.00000 + 0.5) - Math.Floor(group.Width * 0.00000 + 0.5), Math.Floor(group.Height * 1.00000 + 0.5) - Math.Floor(group.Height * 0.00000 + 0.5)));
        context.SaveState();
        context.SetShadow(shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        grayTranslucent.SetFill();
        uncheckedOvalPath.Fill();
        context.RestoreState();
        UIColor.White.SetStroke();
        uncheckedOvalPath.LineWidth = 1f;
        uncheckedOvalPath.Stroke();


        // Bezier Drawing
        var bezierPath = new UIBezierPath();
        bezierPath.MoveTo(new CGPoint(group.GetMinX() + 0.27083 * group.Width, group.GetMinY() + 0.54167 * group.Height));
        bezierPath.AddLineTo(new CGPoint(group.GetMinX() + 0.41667 * group.Width, group.GetMinY() + 0.68750 * group.Height));
        bezierPath.AddLineTo(new CGPoint(group.GetMinX() + 0.75000 * group.Width, group.GetMinY() + 0.35417 * group.Height));
        bezierPath.LineCapStyle = CGLineCap.Square;
        UIColor.White.SetStroke();
        bezierPath.LineWidth = 1.3f;
        bezierPath.Stroke();
    }

    private void DrawRectOpenCircle(CGRect rect)
    {
        var context = UIGraphics.GetCurrentContext();

        // Shadow Declarations
        var shadow = UIColor.Black;
        var shadowOffset = new CGSize(0.1, -0.1);
        nfloat shadowBlurRadius = 0.5f;
        var shadow2 = UIColor.Black;
        var shadow2Offset = new CGSize(0.1, -0.1);
        nfloat shadow2BlurRadius = 2.5f;

        var frame = Bounds;

        // Subframes
        var group = new CGRect(frame.GetMinX() + 3, frame.GetMinY() + 3, frame.Width - 6, frame.Height - 6);


        // EmptyOval Drawing
        var emptyOvalPath = UIBezierPath.FromOval(new CGRect(group.GetMinX() + Math.Floor(group.Width * 0.00000 + 0.5), group.GetMinY() + Math.Floor(group.Height * 0.00000 + 0.5), Math.Floor(group.Width * 1.00000 + 0.5) - Math.Floor(group.Width * 0.00000 + 0.5), Math.Floor(group.Height * 1.00000 + 0.5) - Math.Floor(group.Height * 0.00000 + 0.5)));
        context.SaveState();
        context.SetShadow(shadow2Offset, shadow2BlurRadius, shadow2.CGColor);
        context.RestoreState();
        context.SaveState();
        context.SetShadow(shadowOffset, shadowBlurRadius, shadow.CGColor);
        UIColor.White.SetStroke();
        emptyOvalPath.LineWidth = 1;
        emptyOvalPath.Stroke();
        context.RestoreState();
    }
}

public enum CheckMarkStyle
{
    OpenCircle,
    GrayedOut
}
Share:
20,400
Chris Vasselli
Author by

Chris Vasselli

Updated on April 14, 2020

Comments

  • Chris Vasselli
    Chris Vasselli about 4 years

    I'm designing an iOS app with a UICollectionView, and I'd like users to be able to select multiple items within this view. It seems there's a standard style of checkmark Apple uses in this kind of situation. For example, in the image below you can see it when selecting multiple photos in a share sheet.

    According to the documentation, you are responsible for updating the UI of your cells to reflect their selection state. I know on a UITableViewCell you can set the accessoryType property to add a checkmark, but I can't seem to find any equivalent for a UICollectionViewCell.

    Is there a way Apple provides to use this checkmark in my app, besides trying to rip this icon out of a screenshot?

    enter image description here