I need to change default icon for moving cells in UITableView.
This one:
Is it possible?
I need to change default icon for moving cells in UITableView.
This one:
Is it possible?
This is a really hacky solution, and may not work long term, but may give you a starting point. The re-order control is a UITableViewCellReorderControl
, but that's a private class, so you can't access it directly. However, you could just look through the hierarchy of subviews and find its imageView.
You can do this by subclassing UITableViewCell
and overriding its setEditing:animated:
method as follows:
- (void) setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing: editing animated: YES];
if (editing) {
for (UIView * view in self.subviews) {
if ([NSStringFromClass([view class]) rangeOfString: @"Reorder"].location != NSNotFound) {
for (UIView * subview in view.subviews) {
if ([subview isKindOfClass: [UIImageView class]]) {
((UIImageView *)subview).image = [UIImage imageNamed: @"yourimage.png"];
}
}
}
}
}
}
Or in Swift
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
for view in subviews where view.description.contains("Reorder") {
for case let subview as UIImageView in view.subviews {
subview.image = UIImage(named: "yourimage.png")
}
}
}
}
Be warned though... this may not be a long term solution, as Apple could change the view hierarchy at any time.
Ashley Mills' answer was excellent at the time it was offered, but as others have noted in the comments, the view hierarchy has changed from version to version of iOS. In order to properly find the reorder control, I'm using an approach that traverses the entire view hierarchy; hopefully this will give the approach an opportunity to continue working even if Apple changes the view hierarchy.
Here's the code I'm using to find the reorder control:
-(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;
}
And here's the code I'm using to override the -(void) setEditing:animated:
method in my subclass:
-(void) setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if (editing)
{
// I'm assuming the findReorderView method noted above is either
// in the code for your subclassed UITableViewCell, or defined
// in a category for UIView somewhere
UIView *reorderView = [self findReorderView:self];
if (reorderView)
{
// I'm setting the background color of the control
// to match my cell's background color
// you might need to do this if you override the
// default background color for the cell
reorderView.backgroundColor = self.contentView.backgroundColor;
for (UIView *sv in reorderView.subviews)
{
// now we find the UIImageView for the reorder control
if ([sv isKindOfClass:[UIImageView class]])
{
// and replace it with the image we want
((UIImageView *)sv).image = [UIImage imageNamed:@"yourImage.png"];
// note: I have had to manually change the image's frame
// size to get it to display correctly
// also, for me the origin of the frame doesn't seem to
// matter, because the reorder control will center it
sv.frame = CGRectMake(0, 0, 48.0, 48.0);
}
}
}
}
}
Swift 4
// Change default icon (hamburger) for moving cells in UITableView
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let imageView = cell.subviews.first(where: { $0.description.contains("Reorder") })?.subviews.first(where: { $0 is UIImageView }) as? UIImageView
imageView?.image = #imageLiteral(resourceName: "new_hamburger_icon") // give here your's new image
imageView?.contentMode = .center
imageView?.frame.size.width = cell.bounds.height
imageView?.frame.size.height = cell.bounds.height
}
Swift version of Rick's answer with few improvements:
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
if let reorderView = findReorderViewInView(self),
imageView = reorderView.subviews.filter({ $0 is UIImageView }).first as? UIImageView {
imageView.image = UIImage(named: "yourImage")
}
}
}
func findReorderViewInView(view: UIView) -> UIView? {
for subview in view.subviews {
if String(subview).rangeOfString("Reorder") != nil {
return subview
}
else {
findReorderViewInView(subview)
}
}
return nil
}
Updated solution of Ashley Mills (for iOS 7.x)
if (editing) {
UIView *scrollView = self.subviews[0];
for (UIView * view in scrollView.subviews) {
NSLog(@"Class: %@", NSStringFromClass([view class]));
if ([NSStringFromClass([view class]) rangeOfString: @"Reorder"].location != NSNotFound) {
for (UIView * subview in view.subviews) {
if ([subview isKindOfClass: [UIImageView class]]) {
((UIImageView *)subview).image = [UIImage imageNamed: @"moveCellIcon"];
}
}
}
}
}
I use editingAccessoryView to replace reorder icon.
- (void) setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing: editing animated: YES]; self.showsReorderControl = NO; self.editingAccessoryView = editing ? [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"yourReorderIcon"]] : nil; }
If you are not using editing accessory view, this may be a good choice.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
for (UIControl *control in cell.subviews)
{
if ([control isMemberOfClass:NSClassFromString(@"UITableViewCellReorderControl")] && [control.subviews count] > 0)
{
for (UIControl *someObj in control.subviews)
{
if ([someObj isMemberOfClass:[UIImageView class]])
{
UIImage *img = [UIImage imageNamed:@"reorder_icon.png"];
((UIImageView*)someObj).frame = CGRectMake(0.0, 0.0, 43.0, 43.0);
((UIImageView*)someObj).image = img;
}
}
}
}
}
I could not get any other answer to work for me, but I found a solution.
Grzegorz R. Kulesza's answer almost worked for me but I had to make a couple changes.
This works with Swift 5 and iOS 13:
// Change default reorder icon in UITableViewCell
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let imageView = cell.subviews.first(where: { $0.description.contains("Reorder") })?.subviews.first(where: { $0 is UIImageView }) as? UIImageView
imageView?.image = UIImage(named: "your_custom_reorder_icon.png")
let size = cell.bounds.height * 0.6 // scaled for padding between cells
imageView?.frame.size.width = size
imageView?.frame.size.height = size
}
I did this on iOS 12 with swift 4.2
I hope this helps:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
for view in cell.subviews {
if view.self.description.contains("UITableViewCellReorderControl") {
for sv in view.subviews {
if (sv is UIImageView) {
(sv as? UIImageView)?.image = UIImage(named: "your_image")
(sv as? UIImageView)?.contentMode = .center
sv.frame = CGRect(x: 0, y: 0, width: 25, height: 25)
}
}
}
}
}
After debuging the UITableViewCell, you can use KVC in UITableViewCell subclass to change it.
// key
static NSString * const kReorderControlImageKey = @"reorderControlImage";
// setting when cellForRow calling
UIImage *customImage;
[self setValue:customImage forKeyPath:kReorderControlImageKey];
// to prevent crash
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:kReorderControlImageKey]) return;
else [super setValue:value forUndefinedKey:key];
}
Swift 5 solution:
Subclass UITableViewCell and override didAddSubview method:
override func didAddSubview(_ subview: UIView) {
if !subview.description.contains("Reorder") { return }
(subview.subviews.first as? UIImageView)?.removeFromSuperview()
let imageView = UIImageView()
imageView.image = UIImage()
subview.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.height.width.equalTo(24)
make.centerX.equalTo(subview.snp.centerX)
make.centerY.equalTo(subview.snp.centerY)
}
}
I've used SnapKit to set constraints, you can do it in your way.
Please note, it could be temporary solution in order of Apple updates.
Working with iOS 16 and Swift 5
I tried the above solution, but sometimes my custom image was not displayed in some cells.
This code works fine for me into the UITableViewCell
subclass:
private lazy var customReorderImgVw: UIImageView = {
let img = UIImage(named: "imgCustomReorder")!
let imgVw = UIImageView(image: img)
imgVw.contentMode = .center
imgVw.frame = CGRect(origin: .zero, size: img.size)
imgVw.alpha = 0
return imgVw
}()
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
for subVw in subviews {
if "\(subVw.classForCoder)" == "UITableViewCellReorderControl" {
subVw.subviews.forEach { $0.removeFromSuperview() }
customReorderImgVw.center.y = subVw.center.y
subVw.addSubview(customReorderImgVw)
break
}
}
}
showOrHideCustomReorderView(isToShow: editing)
}
private func showOrHideCustomReorderView(isToShow: Bool) {
let newAlpha: CGFloat = (isToShow ? 1 : 0)
UIView.animate(withDuration: 0.25) {
self.customReorderImgVw.alpha = newAlpha
}
}
You can also simply add your own custom reorder view above all others inside your cell.
All you have to do is ensure this custom view is always above others, which can be checked in [UITableViewDelegate tableView: willDisplayCell: forRowAtIndexPath: indexPath:].
In order to allow the standard reorder control interaction, your custom view must have its userInteractionEnabled
set to NO.
Depending on how your cell looks like, you might need a more or less complex custom reorder view (to mimic the cell background for exemple).