Skip to content

Instantly share code, notes, and snippets.

@damionvega
Last active January 20, 2016 05:13
Show Gist options
  • Save damionvega/85e2b17cdcde07ca4533 to your computer and use it in GitHub Desktop.
Save damionvega/85e2b17cdcde07ca4533 to your computer and use it in GitHub Desktop.
iOS 9 / Swift Custom Dynamic TableViewCell height

Note

This was done by dragging in a TableViewController into IB and then dragging a TableView into it. (Like any sane person would.)

I previously tried this with dragging in a ViewController then dragging a TableView onto that. Then I dragged an Object into it and assigned that Object's class as my TableViewController, then set my TableView's delegate and data source to that Object. This has always worked for me (I do this because I like to separate all of that tableView's logic out to a different file rather than having the VC's and TVC's code in the same place), but you lose viewDidLoad (it never fires, neither does viewWillAppear and the like), and we need the ever-so-necessary code below inside viewDidLoad for dynamic cell height to trigger.

	tableView.rowHeight = UITableViewAutomaticDimension
	tableView.estimatedRowHeight = 70.0
	tableView.reloadData()

Result

TableView

Here's my tableView and tableViewCell in IB:

TableView - Nothing fancy- no separator, no selection, no scroll bars

screen shot 2016-01-19 at 9 35 21 pm

TableView - Assign data source & delegate to your TVC

screen shot 2016-01-19 at 9 35 48 pm

TableViewCell - Assign class

screen shot 2016-01-19 at 9 35 28 pm

TableViewCell - Assign reuse identifier

screen shot 2016-01-19 at 9 35 35 pm

//
// FeedTableViewCell.swift
// Created by Damion Nabarrete on 1/18/16.
//
import UIKit
import PureLayout
class FeedTableViewCell: UITableViewCell {
private var hasBeenViewed: Bool!
private var timeWrap: UIView!
private var separatorWrap: UIView!
private var separator: UIView!
private var separatorWidthConstraint: NSLayoutConstraint!
private var subtitleWrap: UIView!
private var addEventButton: UIButton!
var titleLabel: UILabel!
var descriptionLabel: UILabel!
var timeLabel: UILabel!
var timePeriodLabel: UILabel!
init() {
super.init(style: UITableViewCellStyle.Default, reuseIdentifier: "FeedCell")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// MARK: - Cell methods
func setAsViewed() {
hasBeenViewed = true
separatorWidthConstraint.constant = 1
separator.backgroundColor = UIColor.groupTableViewBackgroundColor()
}
// MARK: - Setup
private func setupViews(eventType: String) {
hasBeenViewed = false
timeWrap = UIView.newAutoLayoutView()
timeLabel = UILabel.newAutoLayoutView()
timeLabel.textColor = UIColor.customPlaceholderColor()
timeLabel.font = UIFont.systemFontOfSize(11)
timeLabel.textAlignment = .Right
timePeriodLabel = UILabel.newAutoLayoutView()
timePeriodLabel.textColor = UIColor.customPlaceholderColor()
timePeriodLabel.font = UIFont.systemFontOfSize(11)
timePeriodLabel.textAlignment = .Right
separatorWrap = UIView.newAutoLayoutView()
separator = UIView.newAutoLayoutView()
separator.backgroundColor = UIColor.customGreenColor()
titleLabel = UILabel.newAutoLayoutView()
titleLabel.preferredMaxLayoutWidth = 280
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .ByWordWrapping
titleLabel.textColor = UIColor.customTextColor()
subtitleWrap = UIView.newAutoLayoutView()
descriptionLabel = UILabel.newAutoLayoutView()
descriptionLabel.preferredMaxLayoutWidth = 280
descriptionLabel.numberOfLines = 0
descriptionLabel.lineBreakMode = .ByWordWrapping
descriptionLabel.font = UIFont.systemFontOfSize(11)
descriptionLabel.textColor = UIColor.customPlaceholderColor()
addEventButton = UIButton.newAutoLayoutView()
addEventButton.setTitle("+", forState: UIControlState.Normal)
addEventButton.setTitleColor(UIColor.groupTableViewBackgroundColor(), forState: .Normal)
if #available(iOS 8.2, *) {
addEventButton.titleLabel!.font = UIFont.systemFontOfSize(24, weight: UIFontWeightLight)
} else {
addEventButton.titleLabel!.font = UIFont.systemFontOfSize(24)
}
var views: [String : UIView] = [
"timeWrap": timeWrap,
"titleLabel": titleLabel,
"descriptionLabel": descriptionLabel,
"separatorWrap": separatorWrap,
"addEventButton": addEventButton,
]
timeWrap.addSubview(timeLabel)
timeWrap.addSubview(timePeriodLabel)
separatorWrap.addSubview(separator)
subtitleWrap.addSubview(descriptionLabel)
for (_, view) in views {
contentView.addSubview(view)
}
}
var didSetupConstraints = false
override func updateConstraints() {
if !didSetupConstraints {
let xPadding: CGFloat = 5
let xPaddingPlus: CGFloat = 7
let xTimeWrapWidth: CGFloat = 36
let xTimeInset: CGFloat = 16
let xTitleInset: CGFloat = 10
timeWrap.autoSetDimension(.Width, toSize: xTimeWrapWidth)
timeWrap.autoPinEdgeToSuperviewEdge(.Leading)
timeWrap.autoPinEdgeToSuperviewEdge(.Top, withInset: xTimeInset)
timeLabel.autoPinEdgeToSuperviewEdge(.Trailing)
timeLabel.autoPinEdgeToSuperviewEdge(.Top)
timePeriodLabel.autoPinEdgeToSuperviewEdge(.Trailing)
timePeriodLabel.autoPinEdge(.Top, toEdge: .Bottom, ofView: timeLabel, withOffset: xPaddingPlus)
separatorWrap.autoSetDimension(.Width, toSize: 2)
separatorWrap.autoPinEdge(.Leading, toEdge: .Trailing, ofView: timeWrap, withOffset: xPaddingPlus)
separatorWrap.autoPinEdgeToSuperviewEdge(.Top, withInset: xPadding)
separatorWrap.autoPinEdgeToSuperviewEdge(.Bottom, withInset: xPadding)
separatorWidthConstraint = separator.autoSetDimension(.Width, toSize: 2)
separator.autoPinEdgeToSuperviewEdge(.Leading)
separator.autoPinEdgeToSuperviewEdge(.Top)
separator.autoPinEdgeToSuperviewEdge(.Bottom)
// I ended up not needing compression resistance set
// NSLayoutConstraint.autoSetPriority(UILayoutPriorityRequired) {
// self.titleLabel.autoSetContentCompressionResistancePriorityForAxis(.Vertical)
// self.descriptionLabel.autoSetContentCompressionResistancePriorityForAxis(.Vertical)
// }
titleLabel.autoPinEdge(.Leading, toEdge: .Trailing, ofView: separatorWrap, withOffset: xPadding)
titleLabel.autoPinEdgeToSuperviewEdge(.Top, withInset: xTitleInset)
descriptionLabel.autoPinEdge(.Top, toEdge: .Bottom, ofView: titleLabel, withOffset: 6)
descriptionLabel.autoPinEdge(.Leading, toEdge: .Trailing, ofView: separatorWrap, withOffset: xPadding)
descriptionLabel.autoPinEdgeToSuperviewEdge(.Bottom, withInset: xTitleInset)
addEventButton.autoPinEdgeToSuperviewEdge(.Trailing)
addEventButton.autoPinEdgeToSuperviewEdge(.Top, withInset: -2)
didSetupConstraints = true
}
super.updateConstraints()
}
}
//
// FeedTableViewController.swift
// Created by Damion Nabarrete on 1/18/16.
//
import UIKit
class FeedTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(FeedTableViewCell.self, forCellReuseIdentifier: "FeedCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 70.0
tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "contentSizeCategoryChanged:", name: UIContentSizeCategoryDidChangeNotification, object: nil)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIContentSizeCategoryDidChangeNotification, object: nil)
}
// Called when the Dynamic Type user setting changes (from the system Settings app)
func contentSizeCategoryChanged(notification: NSNotification) {
tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = FeedTableViewCell()
cell.timeLabel.text = "8:00"
cell.timePeriodLabel.text = "pm"
cell.titleLabel.text = "LIVE: Levi Panther and the Panther Party rocking all night long. In attendance: Alice Cooper along with the KISS Army!"
cell.descriptionLabel.text = "Music: Rock, Classic Rock"
if indexPath.row != 3 {
NSTimer.schedule(4) { timer -> Void in
cell.setAsViewed()
}
} else {
cell.timeLabel.text = "10:00"
cell.timePeriodLabel.text = "pm"
cell.titleLabel.text = "Open Mic: Acoustic"
cell.descriptionLabel.text = "Music: Rock"
}
// Ensure contraints have been added
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
return cell
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = UIView()
header.backgroundColor = UIColor.customTextLightColor()
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.LongStyle
dateFormatter.dateFormat = "E MMM dd"
let headerLabel = UILabel()
headerLabel.translatesAutoresizingMaskIntoConstraints = false
headerLabel.textColor = UIColor.grayColor().colorWithAlphaComponent(0.8)
let text = section == 0
? dateFormatter.stringFromDate(NSDate())
: dateFormatter.stringFromDate(NSDate().dateByAddingTimeInterval(
NSTimeInterval(60 * 60 * 24 * section)))
let textArr = text.componentsSeparatedByString(" ")
let weekday = textArr[0]
let dateMonth = textArr[1]
let dateDay = textArr[2]
headerLabel.text = "\(weekday) \(dateMonth) \(dateDay)"
header.addSubview(headerLabel)
// These are just old constraints I need to convert to PureLayout
header.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-10-[headerLabel]", options: [], metrics: nil, views: ["headerLabel": headerLabel]))
header.addConstraint(NSLayoutConstraint(
item: headerLabel, attribute: .CenterY, relatedBy: .Equal,
toItem: header, attribute: .CenterY, multiplier: 1, constant: 0))
return header
}
}
@damionvega
Copy link
Author

If anyone knows how to fix xcode's ugly indentation, I'd love to hear it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment