Dynamic Table View Cell Height in iOS 8 and Swift

Dynamic Table View Cell Height in iOS 8 and Swift
Learn how to create dynamic height table view cells using auto layout.

Learn how to create dynamic height table view cells in iOS 8 and Swift.

If you’ve ever created custom table view cells before, chances are good that you wrote a lot of sizing code. You may even be familiar with having to calculate the height of every label, image view, text field, and everything else within the cell — manually.

Frankly, this approach is mind-boggling, error prone and drool-inducing.

In this tutorial, you’ll learn how to create custom table view cells and dynamically size them to fit their contents. You might be thinking, “That’s going to take a lot of work…!”

Nope. :]

Lucky for you, Apple has made this much easier in iOS 8. Not only will you experience glee as you find that you are free from the need to write sizing code, but there will be no implementation of a bunch of table view data sources or delegate methods!

Note: This tutorial requires Xcode 6.1 or newer in order to be compatible with the latest Swift changes and method signatures.

This tutorial also assumes you have a basic familiarity with auto layout, UITableView and Swift development. If you’re completely new to iOS and/or Swift development, you should first check out some of the other written and/or video tutorials on this site.

Getting Started

Way back in the days of iOS 6, Apple introduced a wonderful new technology: auto layout. Developers rejoiced; parties commenced in the streets; bands wrote songs to celebrate its greatness…

Okay, so that might be a stretch, but it was a big deal.

While it inspired hope for countless developers, auto layout was still cumbersome. Manually writing auto layout code was, and still is, a great example of the verbosity of iOS development. Also, up until now, Interface Builder tends to be a bit counterproductive when you go to set up constraints.

Flash forward to now. With all the improvements to Interface Builder and the introduction of iOS 8, it’s finally easy to use auto layout to create dynamic table view cells!

All you really have to do is:

  1. Use auto layout when creating your table view cells.
  2. Set the table view rowHeight to equal UITableViewAutomaticDimension.
  3. Set the estimatedRowHeight or implement the height estimation delegate method.

There are a few ins and outs that you’ll need to know, but that’s the gist of it.

But, you don’t want to delve into theory right now, do you? You’re ready to get down to coding, so let’s get down to business with the project.

Tutorial App Overview

Imagine that your top client has come to you and said, “Our users are clamoring for a way to view their favorite Deviant Artists’ submissions.”

“What’s Deviant Art?” you ask.

“It’s a popular social networking platform where artists can share their art creations, called deviations, and blog posts.” your client explains. “You really should check out the Deviant Art website and its Media RSS endpoint, which allows you to access deviations and posts by artist.

“We started making the app, but we’re stumped at how to display the content in a table view,” your client admits. “Can you make it work?”

You suddenly feel the urge to slide into the nearest phone booth and change into your cape.

Super Dev

But you don’t need gimmicks to be your client’s hero – your programming skills will suffice!

First, download the “client’s code” (the starter project for this tutorial) here.

This project uses CocoaPods, so open DeviantArtBrowser.xcworkspace (not the .xcodeproj file). The pods are included in the zip for you, so you don’t need to run pod install.

Note: If you’re going, “Cocoa … what?” at this point, and want to grok all that is CocoaPods, you might want to take a look at this tutorial.

Open Main.storyboard (in the DeviantArtBrowserViews group under the DeviantArtBrowser project), and you’ll see four scenes:


From left to right, they are:

  • A top-level navigation controller
  • FeedViewController, titled Deviant Browser
  • Two scenes for DetailViewController, titled Deviant Article and Deviant Media — one displays only text content and the other displays both text and an image

Build and run. You’ll see some output in your console log and an activity indicator in the app for a brief moment, but no content will appear in the app just yet.

The log output should look something like this:

2014-11-08 14:30:02.746 DeviantArtBrowser[70847:829282] GET 'http://backend.deviantart.com/rss.xml?q=boost%3Apopular'

2014-11-08 14:30:03.297 DeviantArtBrowser[70847:829282] 200 'http://backend.deviantart.com/rss.xml?q=boost%3Apopular' [0.5506 s]

The app is making a network request and getting a response, but it isn’t doing anything with the received data.

Now, open FeedViewController.swift (it’s under the Controllers group). This is where most of the new code will go. Have a look at this snippet from parseForQuery:

func parseForQuery(query: String?) {
    parameters: parametersForQuery(query),
    success: {(let channel: RSSChannel!) -> Void  in
      self.convertItemPropertiesToPlainText(channel.items as [RSSItem])
      self.items = (channel.items as [RSSItem])
    }, failure: {(let error:NSError!) -> Void in
      println("Error: \(error)")

parser is an instance of RSSParser, which is part of MediaRSSParser.

This method initiates a network request to Deviant Art to get the RSS feed, and then it returns an RSSChannel to the success block. After parsing the data, which simply converts HTML to plain text, the success block stores the channel.items as a local property called items.

The channel.items array contains RSSItem objects, each of which represents a single item element in an RSS feed. (Now you know what you’ll need to display in the table view: the items array!)

Lastly, notice that there are three // TODO: Write this... comments in the project. What are these about?

Huh, it looks like someone has put these comments to highlight what needs to be implemented.

Well, that's convenient!

Create a Basic Custom Cell

After a quick dive into the source code, you now know the app is fetching data but not yet displaying anything. To get things to show up, you need to create a custom table view cell to show the data.

  • Add a new class to the DeviantArtBrowser project
  • Name it BasicCell and make it a subclass of UITableViewCell
  • Ensure that Also create xib file is not checked
  • Set the language to Swift.

Open BasicCell.swift and add the following properties:

@IBOutlet var titleLabel: UILabel!
@IBOutlet var subtitleLabel: UILabel!

Next, open Main.storyboard, and drag and drop a new UITableViewCell onto the table view of FeedViewController.

Set the Custom Class of the cell to BasicCell.

Basic Cell Class

Set the Identifier (Reuse Identifier) to BasicCell.

Basic Cell Identifier

Set the Row Height of the cell to 82.

Set BasicCell Row Height

Drag and drop a new UILabel onto the cell, and set its text to Title.

Add Title Label to BasicCell

Set the new label’s Lines (the number of lines the label can have at most) to 0, meaning unlimited.

Screen Shot 2014-11-17 at 9.27.07 PM

Next, set the label’s size and position to the values shown in the screenshot below.

Screen Shot 2014-11-17 at 9.27.54 PM

Connect the titleLabel outlet of BasicCell to the label on the cell.

Screen Shot 2014-11-17 at 9.28.32 PM

Next, drag and drop a second UILabel onto the cell, right below the first label, and set its text to Subtitle.

Add Subtitle Label to BasicCell

As you did with the first label, change the values of the size and position to match the values of the screenshot below.

Screen Shot 2014-11-17 at 9.29.12 PM

Set the subtitle label’s Color to Light Gray Color; its Font to System 15.0; and its Lines to 0.

Screen Shot 2014-11-17 at 9.29.29 PM

Connect the subtitleLabel outlet of BasicCell to the subtitle label on the cell.

Screen Shot 2014-11-17 at 9.56.54 PM

Awesome, you’ve laid out and configured BasicCell. Now you just need to add auto layout constraints.

Note: If you are not familiar with auto layout, or would like a refresher to understand how to set up auto layout constraints via Xcode 6.x, you might want to take a look at this tutorial.

Select the title label and pin the top, trailing and leading edges to 20 points from the superview. Make sure that you uncheck Constrain to margins from the pin menu.

Screen Shot 2014-11-17 at 10.08.09 PM

This ensures that no matter how big or small the cell may be, the title label is always:

  • 20 points from the top
  • Spans the entire width of the content view, minus 20 points of padding on the left and right

Now, select the subtitle label and pin the leading and trailing edges to 20 points from its superview, and the bottom edge to 19.5 points from its superview (to account for the 0.5 point tableview cell separator).

Once again, uncheck Constrain to margins from the pin menu.

Screen Shot 2014-11-17 at 9.34.11 PM

Similar to the title label, these constraints work together to ensure the subtitle label is 20 points from the bottom and spans the entire width of the content view, minus some padding.

Pro Tip: the trick to getting auto layout to work on a UITableViewCell is to ensure you have constraints to pin each subview on all sides — that is, each subview should have leading, top, trailing and bottom constraints.

Furthermore, you need a clear line of constraints going from the top to the bottom of the contentView. This ensures that auto layout correctly determines the height of the contentView based on its subviews.

The tricky part is that Interface Builder often won’t warn you if you’re missing some of these constraints; auto layout simply doesn’t return the correct heights when you run the project. For example, it may return 0 for the cell height, which is a clue that your constraints need more work.

If you run into issues when working with your own projects, try adjusting your constraints until the above criteria are met.

Now, select the subtitle label, hold down Control and drag to the title label. Choose Vertical Spacing to pin the top of the subtitle label to the bottom of the title label.

On the title label, set the Horizontal and Vertical constraints for Content Hugging Priority and Content Compression Resistance Priority to 751.

Set Title Label Constraints

On the subtitle label, set the Horizontal and Vertical constraints for Content Hugging Priority and Content Compression Resistance Priority to 750.

Set Subtitle Label Constraints

This tells auto layout how to size the labels to fit their text – to prioritize the title label’s constraints over the subtitle label’s constraints. In nearly all instances, however, all of these constraints should be fulfilled by auto layout.

Review: Does this satisfy the previous auto layout criteria?

  1. Does each subview have constraints that pin all of their sides? Yes.
  2. Are there constraints going from the top to the bottom of the contentView? Yes.

The titleLabel connects to the top by 20 points, it’s connected to the subtitleLabel by 4 points, and the subtitleLabel connects to the bottom by 19.5 points.

So, auto layout can now determine the height of the cell!

Next, you need to create a segue from the BasicCell to the scene titled Deviant Article:

Select your BasicCell and control-drag to the Deviant Article scene. Select Push from the Selection Segue options.

Interface Builder will also automatically change the cell Accessory property to Disclosure Indicator, to indicate that you can navigate to a detailed view from the cell. However, this doesn’t mesh with the app’s design. Select the BasicCell and change its Accessory value back to None.

Screen Shot 2014-11-17 at 9.37.17 PM

Now, whenever a user taps on a BasicCell, the app will segue to DetailViewController.

Awesome, your BasicCell is set up! If you build and run the app now, you’ll see that… nothing has changed. What the what?!

Say What...!?

Remember those TODO comments? Yep, those are your troublemakers. You need to write the code for those TODOs now.

Configure the Table View

First, you need to configure the table view.

Open FeedViewController.swift and replace configureTableView() with the following:

func configureTableView() {
  tableView.rowHeight = UITableViewAutomaticDimension
  tableView.estimatedRowHeight = 160.0

When you set the rowHeight as UITableViewAutomaticDimension, the table view knows to use the auto layout constraints to determine each cell’s height.

In order for the table view to do this, you must also provide an estimatedRowHeight. In this case, 160.0 is just an arbitrary value that works well in this particular instance. For your own projects, you probably want to pick a value that better conforms to the type of data that you’ll be displaying.

Implement UITableView Data Source

Next, you need to implement the UITableViewDataSource methods.

First, add the following at the top of FeedViewController.swift, right after the other instance variables:

let basicCellIdentifier = "BasicCell"

This will allow you to get the BasicCell by its identifier you set in the storyboard.

Next, implement tableView(_:numberOfRowsInSection:) to return the number of items that were downloaded from Deviant Art:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  return items.count

Then, replace the tableView(_:cellForRowAtIndexPath:) stub with the following code block:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  return basicCellAtIndexPath(indexPath)
func basicCellAtIndexPath(indexPath:NSIndexPath) -> BasicCell {
  let cell = tableView.dequeueReusableCellWithIdentifier(basicCellIdentifier) as BasicCell
  setTitleForCell(cell, indexPath: indexPath)
  setSubtitleForCell(cell, indexPath: indexPath)
  return cell
func setTitleForCell(cell:BasicCell, indexPath:NSIndexPath) {
  let item = items[indexPath.row] as RSSItem
  cell.titleLabel.text = item.title ?? "[No Title]"
func setSubtitleForCell(cell:BasicCell, indexPath:NSIndexPath) {
  let item = items[indexPath.row] as RSSItem
  var subtitle: NSString! = item.mediaText ?? item.mediaDescription
  // Some subtitles are really long, so only display the first 200 characters  
  if subtitle != nil {
    cell.subtitleLabel.text = subtitle.length > 200 ? "\(subtitle.substringToIndex(200))..." : subtitle
  } else {
    cell.subtitleLabel.text = ""

Here’s what’s happening:

  • From tableView(_:cellForRowAtIndexPath:), you call basicCellAtIndexPath(_:) to get a BasicCell.
  • In basicCellAtIndexPath(_:), you dequeue a BasicCell, set the title label text via setTitleForCell(_:indexPath:), the subtitle label text via setSubtitleForCell(_:indexPath:), and return the cell.
  • Now you need to just implement…wait, that’s all there is! Pretty simple, right? :]

    Build and run, and you should see a populated table view:

    Populated Table View with Basic Cells

    Where Are the Images?

    The app is looking great, but doesn’t it feel like there’s…something missing? Oh, snap! Where is the art?

    Deviant Art is all about images, but the app doesn’t show any of them. You need to fix that or your client will think you’ve lost your mind!

    One approach you might take is to add an image view to your BasicCell.

    While this might work for some apps, Deviant Art has both deviations (posts with images) and blog posts (without images), so in this instance it’s actually better to create a new custom cell.

    Add a new class to the project. Name it ImageCell and make it a subclass of BasicCell. The reason you make it a subclass is because your new cell will need a titleLabel and subtitleLabel too. So why do everything from scratch when you can make use of the existing functionality in BasicCell?

    Open ImageCell.swift and add the following property:

    @IBOutlet var customImageView: UIImageView!

    This property’s name is customImageView rather than imageView because there is already an imageView property on UITableViewCell.

    Open Main.storyboard, select the basic cell you were working on before and copy it using ⌘C, or select Edit > Copy from the menu.

    Select the table view and press ⌘V, or click Edit > Paste, to create a new copy of the cell.

    Note: For the above step, selecting the table view in the document outline does not get you the same results as selecting the table view in the Interface Builder editing view. So, if you don’t get the correct results, you might want to undo your current action using ⌘Z, or clicking Edit > Undo, and try again.

    Select the new cell and change its Custom Class to ImageCell. Likewise, change its Reuse Identifier to ImageCell.

    Select the title label on ImageCell and change its x position to 128 and width to 172. Do the same for the subtitle label.

    Interface Builder should now give you warnings about misplaced views because the constraints on those labels put them at a different position than the values you just entered.

    To correct this, select the title label for ImageCell to show its constraints, and then select its leading constraint and delete it. Delete the subtitle label’s leading constraint as well.

    Now, select the title label for ImageCell and change its Intrinsic Size to Placeholder with the same values for width and height as shown in the screenshot below. Do the same to the subtitle label’s Intrinsic Size.
    (These tell Interface Builder to update the Placeholder to the current frame of the view. Interface Builder shouldn’t be showing any warnings now.)
    Set Intrinsic Size to Placeholder

    You need to add an image view to the cell next, but the height is currently a bit too small for it. So, select ImageCell and change its Row Height to 141.

    Now, drag and drop an image view in to ImageCell. Set the new view’s size and position to the values shown in the screenshot below.

    Screen Shot 2014-11-17 at 9.38.11 PM

    Next, select the image view and do the following via the Pin button:

    • Set leading, top and bottom to 20.
    • Set width and height to 100.
    • Make sure that Constrain to margins is not checked.
    • Tap the Add 5 constraints button.

    Set Image View Pin Constraints

    Select the image view to show its constraints, and then select its bottom constraint to edit it. In the attributes editor, change its Relation to Greater Than or Equal and its Priority to 999.

    Screen Shot 2014-11-17 at 9.41.38 PM

    Similarly, select the subtitle label to show its constraints, and then select its bottom constraint. In the attributes editor, change its Relation to Greater Than or Equal, yet leave its Priority set to 1000.

    Note: This basically tells auto layout that there should be at least 20 points below both the imageView and subtitleLabel, but if you must, you may break the bottom constraint on imageView.

    Next, select the image view to show its constraints and then select its height constraint. In the attributes editor, change its Priority to 999.

    Likewise, select the image view’s width constraint and change its Priority to 999.

    These changes are needed because the auto layout engine can sometimes get confused between system-defined constraints and custom-defined size constraints. This setting basically tells auto layout, “Try not to break these constraints, but if you really have to, you can.”

    In most cases, auto layout will be able to fulfill all of these constraints. In the rare case it does break these constraint(s) — which can happen during an orientation change — it’s usually only a difference of 1-2 pixels, which isn’t noticeable.

    Pro Tip: It isn’t always obvious how auto layout will interpret your custom constraints, especially on a table view cell.

    If you ever see console warnings that auto layout had to break a constraint, try tweaking your constraints’ priorities.

    Finally, select the title label for ImageCell and use the Pin button to set a leading constraint of 8. Do the same for the subtitle label.

    Set the Labels Leading Constraints

    At this point, the auto layout constraints should look like this on your ImageCell.

    RWImageCell Constraints

    You also need to select the ImageCell and connect the customImageView outlet to the image view.

    Lastly, you need a segue from ImageCell to the scene titled Deviant Media, so the app shows this screen when a user clicks on an ImageCell.

    Similar to how you set up the basic cell, select ImageCell, control-drag to the scene titled Deviant Media, and then select Push from the Selection Segue options.

    Make sure you also change the Accessory back to None.

    Great, your ImageCell is set up! Now you just need to add the code to display it.

    Show me the Images!

    Open FeedViewController.swift and add the following right after the other instance variables (at the top):

    let imageCellIdentifier = "ImageCell"

    Next, replace the code for tableView(_:cellForRowAtIndexPath:) again, but this time with the following code:

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
      if hasImageAtIndexPath(indexPath) {
        return imageCellAtIndexPath(indexPath)
      } else {
        return basicCellAtIndexPath(indexPath)
    func hasImageAtIndexPath(indexPath:NSIndexPath) ->; Bool {
      let item = items[indexPath.row]
      let mediaThumbnailArray = item.mediaThumbnails as [RSSMediaThumbnail]
      for mediaThumbnail in mediaThumbnailArray {
        if mediaThumbnail.url != nil {
          return true
      return false
    func imageCellAtIndexPath(indexPath:NSIndexPath) -> ImageCell {
      let cell = self.tableView.dequeueReusableCellWithIdentifier(imageCellIdentifier) as ImageCell
      setImageForCell(cell, indexPath: indexPath)
      setTitleForCell(cell, indexPath: indexPath)
      setSubtitleForCell(cell, indexPath: indexPath)
      return cell
    func setImageForCell(cell:ImageCell, indexPath:NSIndexPath) {
      let item: RSSItem = items[indexPath.row]
      // mediaThumbnails are generally ordered by size,
      // so get the second mediaThumbnail, which is a
      // "medium" sized image
      var mediaThumbnail: RSSMediaThumbnail?
      if item.mediaThumbnails.count >= 2 {
        mediaThumbnail = item.mediaThumbnails[1] as? RSSMediaThumbnail
      } else {
        mediaThumbnail = (item.mediaThumbnails as NSArray).firstObject as? RSSMediaThumbnail
      cell.customImageView.image = nil
      if let url = mediaThumbnail?.url {

    The above is similar to how you created a BasicCell before, but there are a few differences, so here’s a quick walkthrough of what the new code does:

    • hasImageAtIndexPath(_:) checks if the item at the indexPath has a mediaThumbnail with a non-nil URL. Deviant Art automatically generates thumbnails for all uploaded deviations that have images. So, if the item does have a non-nil URL, you’ll need to display the item using an ImageCell.
    • imageCellAtIndexPath(_:) is similar to basicCellAtIndexPath(_:), but it also sets an image on the cell via setImageForCell(_:indexPath:).
    • setImageForCell(_:indexPath:) attempts to get the second media thumbnail, which is a “medium” sized image that should fit the given image view size. That image is then set on customImageView by using a convenience method provided by AFNetworking, setImageWithURL(_:).

    Build and run, and this time you’ll be greeted by some pretty sweet art! By default, the app retrieves items from the “Popular” category on Deviant Art, but you can also search by artist.

    Try entering by:CheshireCatArt (case insensitive) into the text field and pressing Search. This is a friend of mine, Devin Kraft, who’s an excellent graphic artist. (check out his website).

    In addition to being one of my favorites, he’s very active on Deviant Art and posts a good mix of deviations and blog posts. So, bias aside, his account is a good test case to show how the app displays cells with and without images.

    CheshireCatArt Posts

    Awesome! The app is looking pretty sharp, but you can still kick it up a notch or two to really make your client a believer in your skills.

    Optimizing the Table View

    Remember earlier when you set the estimatedRowHeight to be 160.0? This attribute works for a BasicCell in portrait orientation, but it isn’t very accurate otherwise.

    Instead, you can implement a UITableViewDelegate method to provide an estimated height for each cell at run time.

    Before you do, however, you need to delete this line from configureTableView():

    tableView.estimatedRowHeight = 160.0

    Now, add the following methods right before the // MARK: UITextFieldDelegate section. (Actually, you can add them anywhere in the class, but adding them there keeps all the UITableView delegate methods in one place.):

    // MARK: UITableViewDelegate
    func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
      if isLandscapeOrientation() {
        return hasImageAtIndexPath(indexPath) ? 140.0 : 120.0
      } else {
        return hasImageAtIndexPath(indexPath) ? 235.0 : 155.0
    func isLandscapeOrientation() -> Bool {
      return UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)

    This cell height estimation method is pretty simple. It checks if the current orientation is landscape and if there is an image at the index path, and simply returns a predetermined value based on its findings.

    Pro Tip: Whether you implement this delegate method or simply set a constant estimatedRowHeight is an important consideration.

    The table view uses either this delegate method (if implemented) or the estimatedRowHeight to determine the total height of the table view. This affects both the scrolling indicator and scrolling performance.

    If your cell estimates are inaccurate, then scrolling might be jumpy, the scroll indicator may be misleading or the content offset may be messed up.

    If your estimates are accurate, yet slow to calculate, the table view’s scrolling may also be slow.

    The key to success is finding a good balance between accurately estimating cell height and the performance cost of the necessary calculations.

    You’re still using constant values to estimate cell height, but now you’re picking the best estimate based on the device orientation and cell type. You could get even fancier with your estimation, but do remember the point of this method is to be fast.

    Build and run, and you should see that the table view scrolls smooth and looks great.

    And with this final method implemented, the app is now complete!

    Oh Yeah!

    Where to Go From Here

    You can download the completed project from here.

    Table views are perhaps the most fundamental of structured data views in iOS. As your apps get more complex, you’re very likely to use all sorts of custom table view cell layouts. Fortunately, auto layout and iOS 8 make this task much easier now.

    If you have any comments or questions, please respond below! I’d love to hear from you.

    Dynamic Table View Cell Height in iOS 8 and Swift is a post from: Ray Wenderlich

    The post Dynamic Table View Cell Height in iOS 8 and Swift appeared first on Ray Wenderlich.



Write a comment