Which is the best way to change the color/view of disclosure indicator accessory view in a table view cell in iOS?
Asked Answered
L

12

22

I need to change the color of the disclosureIndicatorView accessory in a UITableViewCell. I think there are two ways to get this done, but I'm not able to figure out which one's the optimum. So here is what I think I can do.

There is a property of UITableViewCell - accessoryView. So I can use setAccessoryView:(UIView *)view and pass view as the UIImageView holding the image that I want.

I have written an utility class which creates the content view (stuff like background color, adding other stuff, etc) for my cell and I add this content view to the cell in UITableViewDelegate. The other option is to draw a UIImage overriding the drawRect method of CustomContentView utility class.

Performing option 1 - I can get the things done the apple way. Just give them the view and they do the rest. But I guess adding a new UIView object to every row might turn out to be a heavy object allocation and decreasing the frame rate. As compared to just a UIImage object in my contentView. I believe UIImage is lighter than UIView.

Please throw some light people and help me decide over it.

Longshore answered 5/12, 2009 at 16:39 Comment(3)
Certainly makes one wish you could just share the UIImageView on all the rows.Margaretmargareta
See a Library. very easily change the colors of all kinds accessoryType. MSCellAccessoryViewPreordain
Old question but had no satisfactory answer yet. Here is what you actually asked for: https://mcmap.net/q/451653/-which-is-the-best-way-to-change-the-color-view-of-disclosure-indicator-accessory-view-in-a-table-view-cell-in-iosFaubert
M
4

But I guess adding a new UIView object to every row might turn out to be a heavy obj allocation and decreasing the frame rate. As compared to just a UIImage object in my contentView. I believe UIImage is lighter than UIView.

Drawing an image directly will almost certainly have better performance than adding a subview. You have to determine if that extra performance is necessary. I've used a few accessory views for custom disclosure indicators on basic cells and performance was fine. However, if you're already doing custom drawing for the content rect, it might not be that difficult to do the accessory view also.

Montana answered 6/12, 2009 at 6:16 Comment(0)
T
29

Great post on Cocoanetics that addresses this. The UIControl class inherits the properties selected, enabled and highlighted Custom-Colored Disclosure Indicators

Tengler answered 5/4, 2011 at 17:33 Comment(0)
V
24

If you're interested in drawing the indicator, instead of using an image file, here's code I worked out to do so:

// (x,y) is the tip of the arrow
CGFloat x = CGRectGetMaxX(self.bounds) - RIGHT_MARGIN;
CGFloat y = CGRectGetMidY(self.bounds);
const CGFloat R = 4.5;
CGContextRef ctxt = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctxt, x-R, y-R);
CGContextAddLineToPoint(ctxt, x, y);
CGContextAddLineToPoint(ctxt, x-R, y+R);
CGContextSetLineCap(ctxt, kCGLineCapSquare);
CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
CGContextSetLineWidth(ctxt, 3);
// If the cell is highlighted (blue background) draw in white; otherwise gray
if (CONTROL_IS_HIGHLIGHTED) {
    CGContextSetRGBStrokeColor(ctxt, 1, 1, 1, 1);
} else {
    CGContextSetRGBStrokeColor(ctxt, 0.5, 0.5, 0.5, 1);
}
CGContextStrokePath(ctxt);

If you make a custom UIView subclass, do the above in the drawRect: method, and use that as your accessory view, you'll be able to make the color anything you want.

An accessory view (custom or UIImageView won't be a major performance problem as long as you are properly recycling UITableViewCell instances.

Vitalize answered 4/1, 2010 at 1:53 Comment(5)
Can you tell me how CONTROL_IS_HIGHLIGHTED would be set? I mean, how does the cell know it's highlighted?Quiteri
If you're drawing in/on a table view cell, I'm pretty sure UITableViewCell has a highlighted property.Vitalize
I've been trying to use this code but for some reason I lose the sharp point where the two lines intersect and get a muted angle, any ideas?Duplet
CGContextSetLineJoin() governs what happens at that point. Maybe you left it out? Or do you call it once and then draw something else which changes the current line-join setting?Vitalize
@VanDuTran Yep, it's just drawing code, none of those APIs have changed.Vitalize
F
12

Here is an implementation that works in iOS 8+. It does exactly what's asked for:
change the color of the original Apple disclosure indicator to a custom color.
Use it like this:

#import "UITableViewCell+DisclosureIndicatorColor.h"
// cell is a UITableViewCell
cell.disclosureIndicatorColor = [UIColor redColor]; // custom color
[cell updateDisclosureIndicatorColorToTintColor]; // or use global tint color

UITableViewCell+DisclosureIndicatorColor.h

@interface UITableViewCell (DisclosureIndicatorColor)
@property (nonatomic, strong) UIColor *disclosureIndicatorColor;
- (void)updateDisclosureIndicatorColorToTintColor;
@end

UITableViewCell+DisclosureIndicatorColor.m

@implementation UITableViewCell (DisclosureIndicatorColor)

- (void)updateDisclosureIndicatorColorToTintColor {
    [self setDisclosureIndicatorColor:self.window.tintColor];
}

- (void)setDisclosureIndicatorColor:(UIColor *)color {
    NSAssert(self.accessoryType == UITableViewCellAccessoryDisclosureIndicator,
        @"accessory type needs to be UITableViewCellAccessoryDisclosureIndicator");

    UIButton *arrowButton = [self arrowButton];
    UIImage *image = [arrowButton backgroundImageForState:UIControlStateNormal];
    image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    arrowButton.tintColor = color;
    [arrowButton setBackgroundImage:image forState:UIControlStateNormal];
}

- (UIColor *)disclosureIndicatorColor {
    NSAssert(self.accessoryType == UITableViewCellAccessoryDisclosureIndicator, 
        @"accessory type needs to be UITableViewCellAccessoryDisclosureIndicator");

    UIButton *arrowButton = [self arrowButton];
    return arrowButton.tintColor;
}

- (UIButton *)arrowButton {
    for (UIView *view in self.subviews)
        if ([view isKindOfClass:[UIButton class]])
            return (UIButton *)view;
    return nil;
}

@end
Faubert answered 16/2, 2016 at 8:40 Comment(1)
red color becomes blueMaun
C
10

In swift 3, I have adapted the solution from @galambalazs as a class extention as follows:

import UIKit

extension UITableViewCell {

    func setDisclosure(toColour: UIColor) -> () {
        for view in self.subviews {
            if let disclosure = view as? UIButton {
                if let image = disclosure.backgroundImage(for: .normal) {
                    let colouredImage = image.withRenderingMode(.alwaysTemplate);
                    disclosure.setImage(colouredImage, for: .normal)
                    disclosure.tintColor = toColour
                }
            }
        }
    }
}

Hope this helps some.

Capitalization answered 27/2, 2017 at 12:31 Comment(1)
Works well for me, I call this method in the delegate function willDisplayCell:forRowAtIndexPath.Incrassate
D
5

Use an UIImageView. This will also allow you to change the image when the cell is selected:

UIImageView* arrowView = [[UIImageView alloc] initWithImage:normalImage];
arrowView.highlightedImage = selectedImage;
cell.accessoryView = arrowView;
[arrowView release];
Delorasdelorenzo answered 6/7, 2012 at 15:9 Comment(0)
M
4

But I guess adding a new UIView object to every row might turn out to be a heavy obj allocation and decreasing the frame rate. As compared to just a UIImage object in my contentView. I believe UIImage is lighter than UIView.

Drawing an image directly will almost certainly have better performance than adding a subview. You have to determine if that extra performance is necessary. I've used a few accessory views for custom disclosure indicators on basic cells and performance was fine. However, if you're already doing custom drawing for the content rect, it might not be that difficult to do the accessory view also.

Montana answered 6/12, 2009 at 6:16 Comment(0)
H
2

benzado's solution works fine, but it showed a black background. In the UIView class that you setup (the one who's drawRect function you put in his code) needs to have the following initWithFrame implementation in order for the disclosure drawing to have a transparent background:

- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) {
        [self setBackgroundColor:[UIColor clearColor]];
        // Initialization code.
    }
    return self;
}

Naturally, you can set this to whatever color you want...

Haughay answered 12/1, 2011 at 18:33 Comment(0)
D
1

While galambalazs' answer works, it should be noted that it is somewhat of a hack as it indirectly accesses and updates Apple's private implementation of the disclosure indicator. At best, it could stop working in future iOS releases, and at worst lead to App Store rejection. Setting the accessoryView is still the approved method until Apple exposes a property for directly setting the color.

Regardless, here is the Swift implementation of his answer for those who may want it:

Note: cell.disclosureIndicatorColor has to be set after cell.accessoryType = .DisclosureIndicator is set so that the disclosureIndicator button is available in the cell's subviews:

extension UITableViewCell {

    public var disclosureIndicatorColor: UIColor? {
        get {
            return arrowButton?.tintColor
        }
        set {
            var image = arrowButton?.backgroundImageForState(.Normal)
            image = image?.imageWithRenderingMode(.AlwaysTemplate)
            arrowButton?.tintColor = newValue
            arrowButton?.setBackgroundImage(image, forState: .Normal)
        }
    }

    public func updateDisclosureIndicatorColorToTintColor() {
        self.disclosureIndicatorColor = self.window?.tintColor
    }

    private var arrowButton: UIButton? {
        var buttonView: UIButton?
        self.subviews.forEach { (view) in
            if view is UIButton {
                buttonView = view as? UIButton
                return
            }
        }
        return buttonView
    }
}
Devilish answered 6/4, 2016 at 14:52 Comment(2)
Accessing the view hierarchy has never been disallowed by Apple. It's usually private APIs (methods, properties or classes not in the documentations) that get you busted.Faubert
This isn't just accessing the view hierarchy, it's retrieving a private UIButton property indirectly via the hierarchy and modifying it.Devilish
O
1

In iOS 13+ the disclosure indicator is set via a non-templeted UIImage, which is determined by the user's dark mode preference. Since the image is non-templated, it will not respect the cell's tintColor property. In other words, the dark mode preference has top priority. If you don't wan't to use the disclosure indicator derived by iOS, you will have to use a custom image.

Outgo answered 4/12, 2019 at 10:8 Comment(0)
A
0

As a contribution to the solution of @benzado I swiftified his code as followed:

override func drawRect(rect: CGRect) {

  super.drawRect(rect)

  let context = UIGraphicsGetCurrentContext();

  let right_margin : CGFloat = 15.0
  let length : CGFloat = 4.5;

  // (x,y) is the tip of the arrow
  let x = CGRectGetMaxX(self.bounds) - right_margin;
  let y = CGRectGetMidY(self.bounds);

  CGContextMoveToPoint(context, x - length, y - length);
  CGContextAddLineToPoint(context, x, y);
  CGContextAddLineToPoint(context, x - length, y + length);
  CGContextSetLineCap(context, .Round);
  CGContextSetLineJoin(context, .Miter);
  CGContextSetLineWidth(context, 2.5);

  if (self.highlighted)
  {
    CGContextSetStrokeColorWithColor(context, UIColor.appColorSelected().CGColor);
  }
  else
  {
    CGContextSetStrokeColorWithColor(context, UIColor.appColor().CGColor);
  }

  CGContextStrokePath(context);
}

With a change of the app color a call to setNeedsDisplay() on the UITableCellView will update the color. I like to avoid the use of UIImage objects in cell views.

Afford answered 15/6, 2016 at 10:28 Comment(0)
S
0

A swift 3 version of the CocoaNetics solution

public class DisclosureIndicator: UIControl {

    public static func create(color: UIColor?, highlightedColor: UIColor?) -> DisclosureIndicator{
        let indicator = DisclosureIndicator(frame: CGRect(x: 0, y: 0, width: 11, height: 15))
        if let color = color { indicator.color = color }
        if let color = highlightedColor { indicator.highlightedColor = color }
        return indicator
    }

    public var color: UIColor = .black
    public var highlightedColor: UIColor = .white

    override public init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .clear
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        backgroundColor = .clear
    }

    override public func draw(_ rect: CGRect) {
        super.draw(rect)

        let context = UIGraphicsGetCurrentContext()!;

        // (x,y) is the tip of the arrow
        let x = self.bounds.maxX - 3.0;
        let y = self.bounds.midY;

        let length : CGFloat = 4.5;
        context.move(to: CGPoint(x: x - length, y: y - length))
        context.addLine(to: CGPoint(x: x, y: y))
        context.addLine(to: CGPoint(x: x - length, y: y + length))
        context.setLineCap(.round)
        context.setLineJoin(.miter)
        context.setLineWidth(3)

        context.setStrokeColor((isHighlighted ? highlightedColor : color).cgColor)

        context.strokePath()
    }

    override public var isHighlighted: Bool {
        get {
            return super.isHighlighted
        }
        set {
            super.isHighlighted = newValue
            setNeedsDisplay()
        }
    }
}
Snoddy answered 31/7, 2017 at 16:54 Comment(0)
C
-1

Change the tint colour of table view cell. Check the screenshot to do the same via Storyboard.

Catenate answered 8/11, 2018 at 6:25 Comment(1)
This answer is conditional. The checkmark accessory respects the tint color of the cell, however, the disclosure indicator (the property specified in this specific question) does not.Outgo

© 2022 - 2024 — McMap. All rights reserved.