UITableViewCell custom reorder control
Is there any way to change the image of the reorder control that is displayed when the UITableView is in edit mode? I have a UIImage that I’d like to display instead of the 开发者_运维问答usual grey bars.
Do I have to subclass UITableViewCell to accomplish this?
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
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 put a little work into this recently, but came up short. I tried setting my own editingAccesoryView but couldn't get the reorder control to change. Odd.
My guess is that it has something to do with the following comment in the UITableviewCell docs re: showsReorderControl:
If the value is YES , the reordering control temporarily replaces any accessory view.
In other words, the editingAccessoryView is being replaced by the reordering control view, which might be why we cannot override the reordering control. Hoping someone can find a workaround.
You set the cell's editingAccessoryView
property to an image view containing the image you want.
As an aside, I would caution you to be careful when doing this. When you substitute a custom graphic for a system standard such as the reorder graphic, you run a serious risk of confusing the user. The standard UI grammar has told them to expect the standard graphic when reordering and they may not understand the significance of your custom graphic.
Maybe we're all overthinking this. :)
Just put a custom UIImageView
over the top of the default move accessory so it covers it up. Done.
精彩评论