5

This is what I want to do:

enter image description here

As you can see i want to:

  1. Decrease the width of the tableView (I want more margin on the sides than the grouped tableView provides)

  2. Corner radius (bigger radius than the default for grouped tableView)

  3. Drop shadow around the table and a special shadow beneath the last cell

vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
Michael
  • 791
  • 3
  • 10
  • 23

2 Answers2

4

You can do this by "drawing" the backgroundView of the cells yourself.

I'd recommend getting an image to use as the background (if the cells are all the same height).

You'll need three images.

A "top" image with the top corners rounded. A "bottom" image with the bottom corners rounded and the drop shadow how you want it. And a "middle" image with no rounded corners.

If the cells don't have any texture or gradient within them then you can use stretchable images to reduce the memory footprint of them.

Then I would subclass the UITableViewCell and override the backgroundView to add a UIImageView. I'd also provide an accessor method to change the type (top, middle, bottom) of the cell.

Each cell can then have three placeHolder properties of a UIImage (topImage, bottomImage and middleImage). When the type of the cell is changed these can be accessed (use lazy instantiation to make sure they are only loaded once and only when needed) and then set the backgroundVIew image to be the required image.

Something like this...

In the UITableViewCell subclass define a type enum...

typedef enum {
    CellTypeTop,
    CellTypeMiddle,
    CellTypeBottom
} cellType;

Then a property for the type...

@property (nonatomic) cellType cellType

Then in the .m ...

Define some more internal properties...

@property UIImageView *bgImageView;
@property UIImage *topImage;
@property UIImage *middleImage;
@property UIImage *bottomImage;

Then add the imageView (only once)...

- (void)awakeFromNib //or in the init depends how you are initialising the cell
{
    self.bgImageView = [[UIImageView alloc] initWithFrame:blah];

    [self.backgroundView addSubView:self.bgImageView];
}

Now when the type is changed...

- (void)setCellType:(cellType)cellType
{
    switch(cellType) {
        case CellTypeTop:
            self.bgImageView.image = self.topImage;
            break;
        case CellTypeMiddle:
            self.bgImageView.image = self.middleImage;
            break;
        case CellTypeBottom:
            self.bgImageView.image = self.bottomImage;
            break;
    }
}

Finally a lazy instantiation of the images...

- (UIImage *)topImage
{
    if (_topImage == nil) {
        _topImage = [UIImage imageNamed:@"topImage"];
        //alternatively...
        _topImage = [[UIImage imageNamed:@"topImage"] stretchableImageWith...
    }

    return _topImage;
}

Now repeat these for the other images.

This will be more performant (by a long way) than using a CALayer alternative and, especially if using the stretchable images, will have a very small memory footprint.

Several other users have said that this is not good for performance, memory, design, whatever, but it really is the best way to get the best performance for UserExperience than CALayers. Yes, it will use more memory than CALayers but only marginally and it will get to a limit as there are only a few dequeueable cells created.

A couple of links explaining performance issues when using CALayers in scrollViews...

http://www.quora.com/iOS-Development/What-is-the-best-way-to-optimize-the-performance-of-a-non-paging-but-view-recycling-UIScrollView-involving-loading-potentially-caching-and-displaying-bundled-images

Bad performance on scroll view loaded with 3 view controllers, drawn with CALayer

::EDIT:: Edit to answer Michael's question.

  1. In the storyboard create a UITableViewController (rename the Class in the inspector so that it matches your subclass UITableViewController - I'll call it MyTableViewController).

  2. Create a subclass of UITableViewCell (I'll call mine MyTableViewCell) in the code (i.e. the .h and .m).

  3. Add the above code to do with properties and types and imageViews to your MyTableViewCell.h file.

  4. In the storyboard select the cell in the TableViewController and rename the class to MyTableViewCell. Also set the reuse identifier on it.

  5. In the MyTableViewController code you will need a function like this...

    -(UITableViewCell*)tableView:(UITabelView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
    {
        MyTableViewCell *cell = [tableView dequeueCellWithReuseIdentifier:@"Cell"];
    
        cell.cellType = CellTypeTop; //or whichever it needs to be
        cell.textLabel.text = @"Blah";
    
        return cell;
    }
    

Oh, another thing, in the storyboard you will be able to layout your cell how you want it to look and link up all the labels and imageviews etc... Make sure you add IBOutlet to the UIImageView so that you can link it up in the storyboard.

Community
  • 1
  • 1
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • This is not provide better performance. The image will take more memory to load, it may seem better but its not. CALayer uses less memory to and will release this memory after allocation is complete. Whereas the image is going to be hanging around till you release it. So all in all this is does not provide better performance. – Popeye Oct 03 '12 at 14:15
  • No worries, ask away, I'll help if I can. – Fogmeister Oct 04 '12 at 13:12
  • Sorry, but Im new to this so I need to clarify some things. Correct me if im wrong: 1. Create tableViewController in storyboard 2. Create a new class that inherit from UITableViewCell 3. Add the code to the class 4. In cellForRowAtIndexPath you use the custom class instead of the regular UITableViewCell – Michael Oct 04 '12 at 13:17
  • Thx for that! I still got some minor code issues though. Do you think I could mail you the project including some comments? – Michael Oct 04 '12 at 13:50
  • 1
    Hey @JohnDude, could you elaborate, why this is not a good design practice? It is also my experience that this approach is second best approach when it comes to shadows on scrollviews — with not using shadows at all as the very best approach. – vikingosegundo Oct 12 '12 at 05:53
  • 1
    @JohnDude, refer to [WWDC12, Session 238 - iOS App Performance: Graphics and Animations, 12:50](https://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/adc.apple.com.16351493766.016351493772.16495242607?i=1239179764). It warn about using the layer's shadows. – vikingosegundo Oct 19 '12 at 17:51
  • @Fogmeister, look at that: JohnDude turned into Popeye – vikingosegundo Apr 23 '13 at 01:30
0

make sure you have #import <QuartzCore/QuartzCore.h> imported, then you can start accessing the layers of the UITableView like.

  UITableView *yourTable =  [[UITableView alloc] initWithStyle:UITableViewStyleGrouped];

  [[yourTable layer] setCornerRadius:10.0f];
  [[yourTable layer] setShadowColor:[[UIColor blackColor] CGColor]];
  [[yourTable layer] setShadowOffset:CGSizeMake([CALayer ShadowOffSetWidthWithFloat:10.0f], [CALayer ShadowOffSetWidthWithFloat:10.0f])];
  [[yourTable layer] setShadowOpacity:[CALayer ShadowOpacity:1]];
  [[yourTable layer] setMasksToBounds:NO];
  UIBezierPath *path = [UIBezierPAth bezierPathWithRect:yourTable.bounds];
  [[yourTable layer] setShadowPath:[path CGPath]];

This will add shadow affect to your table view with the shadow not masked to the bounds of the UITableView, at setCornerRadius you can set the corners of the table to whatever you want. You can also set the frame of the UITableView by doing

  [yourTable setFrame:CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)];

EDIT

As another user has tried to point out that CALayer is very slow, this is not the case

CALayer was introduced to help performance issues around animation. Please read documentation. Loading an image straight in may seem like a good idea but in the long run will take up more memory. Please this question about memory allocation for images. As you can see it may seem faster, but it takes up 2.25 MByte of memory per image which after loading each image so many times your app will start to become slow.

Community
  • 1
  • 1
Popeye
  • 11,839
  • 9
  • 58
  • 91
  • I'd avoid using this method. Using transparency in CALayer is really not very good for performance so the cornerRadius and shadow will be a massive hit on FPS when animating. I'm not talking about advanced animation either, just simple animations like scrolling and transitioning between views. – Fogmeister Oct 03 '12 at 13:59
  • 1
    @Fogmeister This is the correct way to access the corner radius and such, just doing it as an image is the cheap why to do things, and it is not good design. CALayer might not be best for performance but this is the correct way. This doesn't deserve a down vote. – Popeye Oct 03 '12 at 14:05
  • 1
    I disagree, this is not "the correct way" this is one way of doing it among hundreds of ways. You could draw a UIBezierPath into a UIView and mask off the area etc... It is also slower in terms of performance than using a UIImage as a background for the Table. So slow in fact that I swapped from using CALayer like this to using a UIImage to get a very similar look to my tables in one of the apps I have. – Fogmeister Oct 03 '12 at 14:08
  • @Fogmeister I use this all the time and have no issues. Just adding an image over it is not a good design, and in fact when it comes to load time between the two it would take longer for the image to load then the CALayer to act in this case. – Popeye Oct 03 '12 at 14:12
  • @Popeye - Actual experience of performance measuring between the two shows you are not correct. Using images in this way outperforms the shadows and roundedCorners of CALayer considerably. – Fogmeister Oct 03 '12 at 14:13
  • I'm not saying yours is wrong I am just saying its bad design. You didn't need to go off on one. I have provided some links that back my case you provided some to back yours its the decision of Micheal now which he would like to use. And if you must know i have had a look and for what he has asked they are both as good and bad as each other in performance. I used instruments to measure the performance of each. Response time was down for CALayer and overall memory usage was up for using images. So it really comes down to which you would like to sacrifice. – Popeye Oct 03 '12 at 15:44
  • I didn't "go off on one" I just don't like being told I'm wrong when I'm actually not and in fact your findings have proved exactly what I said in the first place. In terms of design, the best design is the one that gives the user a better experience. When it comes down to it that means faster response times. Sacrificing a couple MB of memory is not going to affect them. – Fogmeister Oct 03 '12 at 15:52
  • In the long run with all other memory usage though this will build up and will start affecting the performance a hell of a lot more then CALayer will ever affect it. Where as CALayer only affects this in the milliseconds and not seconds like this will build up into over time. My finding didn't show any noticeable difference at all it was only through instruments I saw the minor difference, but I also saw the huge difference between the memory usage. This is why it is bad design in the long run this will show a significant difference in performance. And this is not recommend by apple. – Popeye Oct 03 '12 at 17:25
  • Please could you link to somewhere where Apple says that it is not recommended. You keep saying the memory usage will "build up over time" this is not the case. There is a finite number of cells created and reused. Yes the memory usage will increase slightly but there will only ever be a maximum of three images (tiny if stretchable images used) created for each dequeue-able cell. Even if 20 cells are created then if stretchable images are 20x20 pixels then that equates to a maximum of only 96kB (using the same maths as provided in your link) and this is for 3 images for each of 20 cells. – Fogmeister Oct 03 '12 at 19:40
  • Just doing a bit of testing around this. I have a tableView that has 25 "rows" in the data and at most 6 cells displayed on screen at a time. I put a message in the cell's init method (well, awakeFromNib in this case) and only 7 were ever created. This takes the memory usage (of my above example) down to an absolute upper limit of 33.6KB. And this is with keeping each of the three images in memory. This could be reduced further by removing them each time they are not needed but would probably cause a slight loss of performance. – Fogmeister Oct 03 '12 at 19:53
  • Look ever way its not down to us its up to the person who asked the question which way they are going to do it, if they even use one of our answers. Ever way its been a good debate over different developers ways of doing things. I just may give that little bit more consideration to your way next time. Even though I prefer doing it this way. I hope you would take into consideration this way over yours as well even if you don't use it. – Popeye Oct 03 '12 at 20:11
  • Agreed :D Just happy that I was able to show you that this way is not always bad :D It *COULD* be very bad but if done well can be *VERY* good :D – Fogmeister Oct 03 '12 at 20:13
  • Thanks for your answers, ill try them both. However, i'm kind of new to this so I think I need some extra explanation for your answer, Popeye. Now, in storyboard, should I create a tableViewController or just a viewController with a tableView in it? Also, the 3 lines with shadowColor, shadowOffset and shadowOpacity do need seem to work properly, it looks like there's something wrong with declaring the properties to CALayer. Sorry for my ignorance – Michael Oct 04 '12 at 07:46
  • 1
    Even Apple tells you, that this isnt the best way: [WWDC12, Session 238 - iOS App Performance: Graphics and Animations, 12:50](https://deimos.apple.com/WebObjects/Core.woa/BrowsePrivately/adc.apple.com.16351493766.016351493772.16495242607?i=1239179764) – vikingosegundo Oct 19 '12 at 17:49