UITableViewCell custom reorder control

I recently ran across the need to change the image for the reorder control, because I subclassed UITableViewCell to provide my own custom table cell. As part of this effort, I changed the background of the cell to something other than the default color.

Everything works correctly, but when I put the UITableView into editing mode, the reorder control would appear, with a white background - instead of the background color I was using for the cell. This didn't look good, and I wanted the background to match.

During the course of various versions of iOS, the hierarchy of views in a UITableViewCell has changed. I've taken an approach that will traverse the entire set of views until it finds the UITableViewCellReorderControl private class. I believe this will work for iOS 5 and all subsequent iOS versions at the time of this answer. Please note that while the UITableViewCellReorderControl class itself is private, I am not using any private API's to find it.

First, here's the code to scan for the reorder control; I'm assuming that the text "Reorder" will be in the class name - which Apple could change in the future:

-(UIView *) findReorderView:(UIView *) view
{
    UIView *reorderView = nil;
    for (UIView *subview in view.subviews)
    {
        if ([[[subview class] description] rangeOfString:@"Reorder"].location != NSNotFound)
        {
            reorderView = subview;
            break;
        }
        else
        {
            reorderView = [self findReorderView:subview];
            if (reorderView != nil)
            {
                break;
            }
        }
    }
    return reorderView;
}

In your custom UITableViewCell subclass, you will override -(void) setEditing:animated: and find the reorder control here. If you try to find this control when the table is not in editing mode, the reorder control will not be in the view hierarchy for the cell:

-(void) setEditing:(BOOL)editing animated:(BOOL)animated
{
    [super setEditing:editing animated:animated];
    if (editing)
    {
        // find the reorder view here
        // place the previous method either directly in your
        // subclassed UITableViewCell, or in a category
        // defined on UIView
        UIView *reorderView = [self findReorderView:self];
        if (reorderView)
        {
            // here, I am changing the background color to match my custom cell
            // you may not want or need to do this
            reorderView.backgroundColor = self.contentView.backgroundColor;
            // now scan the reorder control's subviews for the reorder image
            for (UIView *sv in reorderView.subviews)
            {
                if ([sv isKindOfClass:[UIImageView class]])
                {
                    // and replace the image with one that you want
                    ((UIImageView *)sv).image = [UIImage imageNamed:@"yourImage.png"];
                    // it may be necessary to properly size the image's frame
                    // for your new image - in my experience, this was necessary
                    // the upper left position of the UIImageView's frame
                    // does not seem to matter - the parent reorder control
                    // will center it properly for you
                    sv.frame = CGRectMake(0, 0, 48.0, 48.0);
                }
            }
        }
    }
}

Your mileage may vary; I hope this works for you.


Here is my Swift solution based on Rick Morgan's answer:

func adjustSize() {
    // we're trying to leverage the existing reordering controls, however that means the table must be kept in editing mode,
    // which shrinks the content area to less than full width to make room for editing controls
    let cellBounds = bounds
    let contentFrame = contentView.convert(contentView.bounds, to: self)

    let leftPadding = contentFrame.minX - cellBounds.minX
    let rightPadding = cellBounds.maxX - contentFrame.maxX

    // adjust actual content so that it still covers the full length of the cell
    contentLeadingEdge.constant = -leftPadding
    // this should pull our custom reorder button in line with the system button
    contentTrailingEdge.constant = -rightPadding

    // make sure we can still see and interact with the content that overhangs
    contentView.clipsToBounds = false

    // recursive search of the view tree for a reorder control
    func findReorderControl(_ view: UIView) -> UIView? {
        // this is depending on a private API, retest on every new iPad OS version
        if String(describing: type(of: view)).contains("Reorder") {
            return view
        }
        for subview in view.subviews {
            if let v = findReorderControl(subview) {
                return v
            }
        }
        return nil
    }

    // hunt down the system reorder button and make it invisible but still operable
    findReorderControl(self)?.alpha = 0.05   // don't go too close to alpha 0, or it will be considered hidden
}

This worked pretty well. contentLeadingEdge and contentTrailingEdge are layout constraints I set up in Interface Builder between the contentView and the actual content. My code calls this adjustSize method from the tableView(_:, willDisplay:, forRowAt:) delegate method.

Ultimately, however, I went with Clifton's suggestion of just covering the reorder control. I added a UIImageView directly to the cell (not contentView) in awakeFromNib, positioned it, and when adjustSize is called I simply bring the image view to the front, and it covers the reorder control without having to depend on any private APIs.


I guess you're a long way past this by now, but this has come up in a new question.

See my answer here:

Change default icon for moving cells in UITableView