Tom

Development Lead (iOS)

20th September 2011

Removing reorder cell shadows from a UITableView

Guides | Tutorial By 3 years ago

(Now updated for iOS 7 and ARC)

Last week I explained how to reorder a cell from any point for a Scramble type game we had created. Another issue we faced with our Scramble game was the shadow behind each cell, which looked a tad weird. If you are facing the same issue, here is how you can correct it quite easily.

First download the sample project, which has everything set up. Go to the NoShadowTableView class, which is a subclass of UITableView. Here we will monitor all subviews of the table. First we need to know which subviews are used for shadows, so let’s make a dummy init method to set up a timer to print a list of subviews every second (a timer because the shadow is only visible when we’re reordering a cell).

Add the following code to NoShadowTableView.m

- (id) initWithCoder:(NSCoder *)aDecoder
{
	self = [super initWithCoder:aDecoder];

	if(self != nil)
	{
		[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(printSubviews) userInfo:nil repeats:YES];
	}

	return self;
}

- (void) printSubviews
{
	NSLog(@"%@nn-------------", self.subviews);
}

This blog was originally written before iOS7, some things have changed a bit since then. Because of that, I will provide both code snippets to keep this compatible with both iOS6 and iOS7.

Run the project, you will see a print off of all subviews once every second. Hold down on a cell and read through the list. On iOS7 you will see a subview called UITableViewWrapperView. On iOS6 you will see a subview called UIShadowView – both of these are private classes, not publicly accessible to developers. Delete the init and printSubview methods, we dont need those now we know what the problem classes are. To remove this class from our table we will override the didAddSubview: method, which is called after any subviews are added to a view.

On iOS7, the UIShadowView is still there, but it’s a further subview of the UITableViewWrapperView.

Just like last week we will check the subview’s class type as a string. You may be tempted to remove the UIShadowView from the table when we find it, but to ensure retain counts are kept as intended, we will just set it to be hidden. If you immediately check the UITableViewWrapperView‘s subviews, the shadow view will not be there. It needs to be checked at a later stage, so instead I will keep a weak reference to the wrapper view and look through the subviews whenever the table is laid out. You will notice at the top of the NoShadowTableView.m file, I’ve already got a weak reference for __weak UIView* wrapperView;

- (void) didAddSubview:(UIView *)subview
{
	[super didAddSubview:subview];

	//	iOS7
	if(wrapperView == nil && [[[subview class] description] isEqualToString:@"UITableViewWrapperView"])
		wrapperView = subview;

	//	iOS6
	if([[[subview class] description] isEqualToString:@"UIShadowView"])
		[subview setHidden:YES];
}

Now obviously on iOS7 this is not all that needs to be done. You need to do the same UIShadowView checks inside layoutSubviews

- (void) layoutSubviews
{
	[super layoutSubviews];

	//	iOS7
	for(UIView* subview in wrapperView.subviews)
	{
		if([[[subview class] description] isEqualToString:@"UIShadowView"])
			[subview setHidden:YES];
	}
}

That should be all, run the project and there will no longer be a shadow behind the cell being dragged. Hardly any code, but this technique can be used on many objects where unsupported customisation is required.

Here is the final project.

  • http://samfoot.co.uk Sam Foot

    Your code works perfectly for me :)

    For me I have very custom UITableView and the reordering shadows were horrible.

    A quick question, if UIShadowView isn’t documented by Apple is there any implications with using this method in apps that are going to go up on the AppStore?

    I know that Apple do not approve of using private APIs in public AppStore apps but as this is just a customisation of their base control then there shouldn’t be an issue?

    Cheers
    Sam

    • tom

      Hi Sam,

      It should be no problem, although we know the name of the private class as UIShadowView, we are not diving into any private APIs as we distinguish the view only by name, and treat it like a regular UIView (all via public methods).

      We currently have an app on the AppStore that uses this method to remove shadows while reordering, and Apple accepted it first time, without any issues.

  • Pirakach

    Hi Tom, this has been extremely helpful….I am wondering if there is any way we can access the UITableViewCell that is being reordered. I have an UITableView with like, 50 cells displayed vertically. They are custom cells displayed with 3 levels of indentation. Now, during reorder, I would like to find the previous cell’s indentation level(the position during reordering) and change the indentation level of dragged cell accordingly. I am breaking my head on how to get a reference to the cell that is being reordered and its position in UITableView. Google is not helping much. I tried to find out any events or properties in vain. I am not sure how to proceed. Have you any idea…

    Thanks.

    • tom

      Hi there,
      I have had a quick try and have managed to get this working by subclassing the UITableViewCell and overriding the setAlpha: method, then checking the value of this. When you reorder a cell the alpha goes to 0.5, and when you’re not reordering it goes back to 1.

      Note that this is somewhat volatile, it may work now but if Apple changes the way cells act when reordered it could break. I would recommend to continue searching for a better solution:
      http://b2cloud.com.au/wp-content/uploads/2012/03/NoShadowTable-Reorder-listening.zip

  • Pirakach

    Hi Tom…Thank you so much. I was able to get the reference to the cell being reordered from setAlpha method. As you said, I’ll use it for now and keep looking if there is a better alternative…Thanks again.

  • tomashanacek

    what about ios 7? Solution with UIShadowView hide doesn’t work

    • http://b2cloud.com.au/ Tom

      I’ve just updated this with an iOS7 solution :)

  • domo

    ios7 “solution”

    http://pastebin.com/j6CJBts5

    very ugly though, as it uses an infinite timer

    • http://b2cloud.com.au/ Tom

      Don’t do this, the NSTimer holds a strong reference to it’s target – because it’s infinite this means unless it’s validated, it will result in a retain loop.

      I’ve updated my code to contain a clean iOS7 solution ;)

      • domo

        thank you nice!
        you may remove these unuseful comments :)

  • Bill Hudson

    Tom, I have problem with table cell reordering and have been unable to find the solution. Perhaps you can help out. I have a project using ARC and stroyboard. In a UIViewController, I have a UITableView. I use the normal methods to populate the cells data. The cell is custom and created using the storyboard interface. Everything loads fine in the cell and all looks as expected. When I put the table into editing mode, everything looks and behaves fine as well. However, when I select the reordering control, the cell background image goes “clear”. I can not find out what is doing this. I can’t find the event or the method handling the event that makes the cell background go clear. I can fix the cell when the method targetIndexPathForMoveFromRowAtIndexPath is invoked, but that is too late in the process from a user UI perspective. Any ideas or clues on how to prevent the recorder engagement from turning the cell background to clear? Thanks!

Recommended Posts

App processing vs server processing

Post by 3 years ago

When building an app that connects to a web service it’s often best to put the processing load on your server, as opposed to the app. For example, say your web service returned a list

Got an idea?

We help entrepreneurs, organizations and established brands from around
the country bring ideas to life. We would love to hear from you!