I have a typical master-detail app that allows the user to browse a scrolling list of objects, and then drill to detail for any particular object with a push segue. The scrolling master list is a UITableView built with prototype cells, and the detail scene is a static UITableView with a fixed number of sections and cells.
I would like to implement Dynamic Type and Self-Sizing Cells in my app so that the user can change the base font size. So far, I have been successful making self-sizing cells with the scrolling list of prototype cells: by using Auto Layout, setting number of lines in each label to 0, and setting tableView.rowHeight = UITableViewAutomaticDimension
, the height of each prototype cell grows or shrinks to accommodate the size of the text within.
But I can't achieve the same effect in my static table view. Whether I use custom cells or built-in cell types, the font grows/shrinks but the cell height does not.
So my question is actually two questions: 1) is it possible to implement self-sizing cells in static table views, like I've done with my prototype table view? and 2) if the answer to the first question is no, how do I write code that will measure the height of a label in a static table view cell and adjust the cell height appropriately?
Thank you!
Static table views return row height set in Interface Builder. tableView.rowHeight
setting seem to be completely ignored. This is apparently a bug in UIKit.
In order to fix that, simply override -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
and return UITableViewAutomaticDimension
.
First add this two function in your class
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Second in order to make the UITableViewAutomaticDimension work make sure you have added all the left, right, bottom, and top constraints relative to cell container view. Also don't forget to set label's number of line to zero.
Since this is a static table, you can't dequeue the cells, so you will have to create IBOutlets to them, I'd hold them in an array like this:
@IBOutlet var cells: [UITableViewCell]!
Then you will have to implement heightForRowAtIndexPath and calculate the size:
var rowSizes: [Int: CGFloat] = [:]
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return calculateHeightForCell(indexPath)
}
func calculateHeightForCell(indexPath: NSIndexPath) -> CGFloat {
// check if we already calculated the height
if let height = rowSizes[indexPath.row] {
return height
}
// else, calculate it
let cell = cells[indexPath.row]
let size = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
rowSizes[indexPath.row] = size.height
return size.height
}
rowSizes acts like a cache, it will hold the row heights, so they will only be calculated once. When changing the font size, simply empty rowSizes and refresh the table so they will be calculated again.
I'm grateful for the answers provided to the question I posted yesterday. Unfortunately (and likely because of my own inexperience as an iOS programmer) I was not able to make the systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) approach work. After some trial and error, however, I found a different method that seems to work:
import UIKit
class MasterTVC: UITableViewController {
@IBOutlet weak var staticCustomCellLabel: UILabel!
//viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 44.0
staticCustomCellLabel.text = "This is the text for the static custom cell label; This is the text for the static custom cell label; This is the text for the static custom cell label"
//Register handleDynamicTypeChange in observer: self
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleDynamicTypeChange:", name: UIContentSizeCategoryDidChangeNotification, object: nil)
}
//deinit
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
//handleDynamicTypeChange
//called when user changes text size in Settings > General > Accessibility > Larger Text
//or Settings > Display & Brightness > Text Size
func handleDynamicTypeChange(notification: NSNotification) {
staticCustomCellLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
self.tableView.reloadData()
}
//tableView:heightForRowAtIndexPath
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}
On the Storyboard side of this, the setup begins with a single Table View Controller with the following properties:
- Class: MasterTVC (my custom class);
- designated the initial view controller;
- Content: Static Cells;
- Style: Grouped
- Sections: 1.
Section-1:
Table View Cell:
- Style: Custom (using Basic here causes the label to disappear when changing text size in Settings);
- Containing a UILabel.
The UILabel is further configured as:
- Font: Body (a Dynamic Type style);
- Lines: 0;
- Pinned on all four sides to the top, bottom, left, and right edges of the content view (I used the respective constraints 8, 8, 15, 15);
- with an @IBOutlet in the custom class code.
This worked for me designing for iOS 9 in Xcode 7 and Swift 2 with Auto Layout enabled. The interesting thing is that if you strip away the NSNotificationCenter code that is used to respond immediately to changes in text size, the self-sizing cells code is basically two lines: setting estimatedRowHeight in viewDidLoad, and returning UITableViewAutomaticDimension from tableView:heightForRowAtIndexPath.
Based on these results, I believe the answers to my original question are: 1) yes; and 2) see 1.