Sam Rayner

Hi, I’m Sam. I design and build apps for iOS and the web. I live in Sheffield, UK and play Ultimate far too much. You can reach me via email or on Twitter.

Statto - The Ultimate stats tracker

I don’t really blog about Ultimate Frisbee but when I’m not looking at a screen it’s what I’ll be out doing or at least thinking about.

For the past two years I’ve had the privilege of representing Great Britain, competing in the Mixed division at the European and World Championships. I also coach and captain the local club team, Sheffield Steal and help coach Sheffield’s university teams.

Ultimate is growing rapidly in popularity and recognition as a sport. With that growth has come an increasing desire for advanced statistical analysis to gain a better insight into player performance and team strategies.

It will be many years before Ultimate has the funding for the kind of GPS player tracking of more mainstream sports. In the meantime, I’ve developed an app for the iPhone and iPad to allow coaches, broadcasters and fans to track and analyse Ultimate stats. It’s called Statto and is available on the App Store.

Try it out today for free, or for more information visit the website.


Name & Shame

Name and Shame screenshot

A couple of days ago my first iOS app went up on the App Store! Name & Shame is an iPhone app for logging and sharing the funny, dumb (or even profound!) things your friends say.

“David Cameron… didn’t he direct Avatar?” – Erwann Cordon (being quizzed on his general knowledge)

It’s free to try out and just 99¢ or 69p to upgrade for unlimited quotations. You can import friends from Facebook and Contacts, share quotes on Twitter and Facebook, star your favourites, tag, search and more!

“…but cats CAN get married!” – Sarah Harrison

The idea came from sitting with my flatmates discussing possible app ideas that gradually got sillier as we got more desperate. It occurred to me how many times a mate has come out with something funny and I’ve kicked myself for not having written it down somewhere.

“There’s a town called Crematorium?” – Joe Crellin (seeing a road sign on the way to Manchester)

The app was developed with help from my mate Sam Millner. Thanks also to Guy Brown for his top lectures on iOS and Dom and Rob for their feedback and advice.

Please check it out on the App Store and take it for a spin! I’d love to know what you think via email or Twitter: @samrayner.


Dynamic Height UITableView Cells in Xcode

I recently started building my first iOS app and quickly ran into problems trying to apply dynamic heights to UITableView cells depending on their content. This seems like the kind of common task that Apple would make simple through their APIs but after hours of research I discovered that:

  1. This is an extremely common problem.
  2. There is no single, agreed-upon solution.
  3. Existing solutions (this one from Cocoa is My Girlfriend seems to be the most widely linked to) appear to involve a lot of code repetition.

Frustrated, I became determined to create my own solution. The goals:

  1. Use a custom UITableViewCell NIB designed in Interface Builder and only adjust things in code when necessary.
  2. Be able to easily apply the custom cell to table views in my app Storyboard.
  3. Avoid code repetition wherever possible. We’re going to need to calculate heights once for the row then again for the cell so let’s streamline that.

Here’s what I came up with.

Prepare a Prototype Cell

First, create a new Empty User Interface. Drag a UITableViewCell object in, fill it with subviews and arrange them to your liking. You’ll probably want to set the cell Style to Custom in the Attributes inspector. This is your prototype cell; styles and positioning will be inherited from it.

I took the approach of including everything that could be shown in the cell in my NIB so I can hide rather than add elements in code later. Make sure your constraints are set up nicely to allow for the height of your cell changing1.

Next, create an Objective-C class that extends UITableViewCell and add properties to its header file for all of the cell elements you’ll want access to in your UITableViewController later:

@interface NaSQuotationTableViewCell : UITableViewCell
  @property (weak) IBOutlet UILabel *quotationLabel, *attributionLabel;
  @property (weak) IBOutlet UIButton *avatarButton, *imageButton;
@end

Go back to your NIB, select your cell, and hook up the IBOutlets in the Connections inspector by ctrl-dragging onto the appropriate subviews.

Finally, set the following on both the cell in your NIB and the prototype cell in each UITableView of your Storyboard.

  • Change the Custom Class in the Identity inspector to your newly created class (NaSQuotationTableViewCell).
  • Give it a unique Identifier in the Attributes inspector. Mine is QuotationCell.

Apply Dynamic Cell Heights

The first thing you’ll want to do is import the new cell class in the header of your UITableViewController:

#import <UIKit/UIKit.h>
#import "NaSQuotationTableViewCell.h"

@interface NaSQuotationsTableViewController : UITableViewController<UITableViewDelegate,UITableViewDataSource>
@end

In the implementation file of your UITableViewController, add a property for your cell prototype:

@interface NaSQuotationsTableViewController ()
  @property (strong) NaSQuotationTableViewCell *cellPrototype;
  @property NSArray *quotes, *names; //for dummy content
@end

Then load the prototype NIB when the view loads: - (void)viewDidLoad { [super viewDidLoad]; //load prototype table cell nib static NSString *CellIdentifier = @”QuotationCell”; [self.tableView registerNib:[UINib nibWithNibName:@”NaSQuotationTableViewCell” bundle:nil] forCellReuseIdentifier:CellIdentifier]; self.cellPrototype = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

  //fill dummy content
  self.quotes = [NSArray arrayWithObjects:@"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla hendrerit quam eu nisl pellentesque aliquam.", @"Vestibulum ligula quam, gravida ut convallis semper, bibendum in turpis.", @"Nam quis sapien purus.", @"Donec suscipit lectus in arcu eleifend ac posuere lacus egestas. Nunc gravida quam in urna ultricies at pharetra magna sodales. Nulla placerat mi tincidunt nulla posuere id interdum mi pretium. Aliquam nulla tortor, egestas pharetra sollicitudin a, sagittis ut enim. Vivamus venenatis consectetur commodo.", @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla hendrerit quam eu nisl pellentesque aliquam. Vestibulum ligula quam, gravida ut convallis semper, bibendum in turpis. Nam quis sapien purus. Nunc tincidunt eleifend porta. Donec suscipit lectus in arcu eleifend ac posuere lacus egestas. Nunc gravida quam in urna ultricies at pharetra magna sodales.", nil];

  self.names = [NSArray arrayWithObjects:@"Sam Rayner", @"Sam Millner", @"Rob White", @"Dom Wroblewski", @"Jack Smith", nil];
}

Now we have access to the prototype cell labels, we can pass their attributes into NSString’s sizeWithFont: method to calculate the required row height:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return [self.quotes count];
}

- (NSString *)quotationTextForRow:(int)row {
  return [self.quotes objectAtIndex:row];
}

- (NSString *)attributionTextForRow:(int)row {
  return [@"– " stringByAppendingString:[self.names objectAtIndex:row]];
}

- (CGSize)sizeOfLabel:(UILabel *)label withText:(NSString *)text {
 return [text sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  //set width depending on device orientation
  self.cellPrototype.frame = CGRectMake(self.cellPrototype.frame.origin.x, self.cellPrototype.frame.origin.y, tableView.frame.size.width, self.cellPrototype.frame.size.height);
  
  CGFloat quotationLabelHeight = [self sizeOfLabel:self.cellPrototype.quotationLabel withText:[self quotationTextForRow:indexPath.row]].height;
  CGFloat attributionLabelHeight = [self sizeOfLabel:self.cellPrototype.attributionLabel withText:[self attributionTextForRow:indexPath.row]].height;
  CGFloat padding = self.cellPrototype.quotationLabel.frame.origin.y;
  
  CGFloat combinedHeight = padding + quotationLabelHeight + padding/2 + attributionLabelHeight + padding;
  CGFloat minHeight = padding + self.cellPrototype.avatarButton.frame.size.height + padding;
  
  return MAX(combinedHeight, minHeight);
}

We can reuse sizeOfLabel: in tableView:cellForRowAtIndexPath: to render the labels with the correct heights:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  //register cell identifier from custom cell NIB
  static NSString *CellIdentifier = @"QuotationCell";
  NaSQuotationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
  
  //set width depending on device orientation
  cell.frame = CGRectMake(cell.frame.origin.x, cell.frame.origin.y, tableView.frame.size.width, cell.frame.size.height);
  
  CGFloat quotationLabelHeight = [self sizeOfLabel:cell.quotationLabel withText:[self quotationTextForRow:indexPath.row]].height;
  CGFloat attributionLabelHeight = [self sizeOfLabel:cell.attributionLabel withText:[self attributionTextForRow:indexPath.row]].height;
  
  cell.quotationLabel.frame = CGRectMake(cell.quotationLabel.frame.origin.x, cell.quotationLabel.frame.origin.y, cell.quotationLabel.frame.size.width, quotationLabelHeight);
  cell.quotationLabel.text = [self quotationTextForRow:indexPath.row];
  
  cell.attributionLabel.frame = CGRectMake(cell.attributionLabel.frame.origin.x, cell.attributionLabel.frame.origin.y, cell.attributionLabel.frame.size.width, attributionLabelHeight);
  cell.attributionLabel.text = [self attributionTextForRow:indexPath.row];
  
  return cell;
}

There we have it! Dynamic table cell heights depending on the text included in them. Hopefully this will give you a decent starting point for achieving the look you want in your app. Good luck!

  1. I confused myself for a good twenty minutes by applying a height Equal constraint to a UILabel and wondering why changing the frame height in code wasn’t having any affect. Cheers to my mate Sam for pointing out I wanted Less Than or Equal.

Post Archive

2016

2015

July

2014

February