Self sizing cells with rotation assist

0/5 No votes

Report this app




So let’s begin with a normal single-view template for iOS. Title the undertaking, and go straight to the Essential.storyboard file. Choose your ViewController, delete it and create a brand new UITableViewController scene.

Set the desk view controller scene as preliminary view controller and create a TableViewController.swift file with the corresponding class.

import UIKit

class TableViewController: UITableViewController {

    var dataSource: [String] = [
        "Donec id elit non mi porta gravida at eget metus.",
        "Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
        "Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Vestibulum id ligula porta felis euismod semper. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo.",
        "Maecenas faucibus mollis interdum.",
        "Donec ullamcorper nulla non metus auctor fringilla. Aenean lacinia bibendum nulla sed consectetur. Cras mattis consectetur purus sit amet fermentum.",
        "Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas faucibus mollis interdum.",

extension TableViewController {

    override func tableView(_ tableView: UITableView, numberOfRowsInSection part: Int) -> Int {
        return self.dataSource.depend

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell

        cell.dynamicLabel?.textual content = self.dataSource[indexPath.row]
        cell.dynamicLabel.font  = UIFont.preferredFont(forTextStyle: .physique)

        return cell

The setup is absolutely self-descriptive. You’ve got obtained a string array as knowledge supply, and the required implementation of the UITableViewDataSource protocol.

The one factor that’s lacking is the TableViewCell class.

class TableViewCell: UITableViewCell {

    @IBOutlet weak var dynamicLabel: UILabel!

Firstly, create the category itself, then with interface builder choose the desk view controller scene and drag a label to the prototype cell. Set the category of the prototype cell to TableViewCell. The reusable identifier might be merely “Cell”. Join the dynamicLabel outlet to the view. Give the label prime, backside, main, trailing constraints to the superview with the default worth of 8. Choose the label, set the font to physique fashion and the strains property to zero. That is how easy it’s. 😂

Now you’re virtually prepared. You simply have to set the estimated row peak on the desk view. Contained in the TableViewController class change the viewDidLoad methodology like this:

override func viewDidLoad() {

    self.tableView.estimatedRowHeight = 44
    self.tableView.rowHeight = UITableView.automaticDimension

The estimatedRowHeight property will inform the system that the tableview ought to strive to determine the peak of every cell dynamically. You must also change the rowHeight property to computerized dimension, in case you do not do then the system will use a static cell peak – that one from interface builder which you can set on the cell. Now construct & run. You might have an exquisite desk view with self sizing cells. You may even rotate your system, it may work in each orientations.

Another factor

If you happen to change the textual content measurement underneath the iOS accessibility settings, the desk view will replicate the adjustments, so it’s going to adapt the format to the brand new worth. The font measurement of the desk view goes to alter accordint to the slider worth. You would possibly need to subscribe to the UIContentSizeCategory.didChangeNotification to be able to detect measurement adjustments and reload the UI. This characteristic is known as dynamic kind.

NotificationCenter.default.addObserver(self.tableView, selector: #selector(UITableView.reloadData), title: UIContentSizeCategory.didChangeNotification,, object: nil)


So we have completed the straightforward half. Now let’s attempt to obtain the identical performance with a group view. UICollectionView is a generic class, that’s designed to create customized layouts, due to this generic behaviour you won’t be able to create self sizing cells from interface builder. It’s a must to do it from code.

Earlier than we begin, we are able to nonetheless play with IB a bit bit. Create a brand new assortment view controller scene, and drag a push segue from the earlier desk view cell to this new controller. Lastly embed the entire thing in a navigation controller.

The cell goes to be the very same as we used for the desk view, nevertheless it’s a subclass of UICollectionViewCell, and we’re going to assemble the format straight from code.

class CollectionViewCell: UICollectionViewCell {

    weak var dynamicLabel: UILabel!

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been applied")

    override init(body: CGRect) {
        tremendous.init(body: body)

        self.translatesAutoresizingMaskIntoConstraints = false

        let label = UILabel(body: self.bounds)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = UIFont.preferredFont(forTextStyle: .physique)
        label.backgroundColor = UIColor.darkGray
        label.numberOfLines = 0
        label.preferredMaxLayoutWidth = body.measurement.width

        self.dynamicLabel = label

            self.contentView.topAnchor.constraint(equalTo: self.dynamicLabel.topAnchor),
            self.contentView.bottomAnchor.constraint(equalTo: self.dynamicLabel.bottomAnchor),
            self.contentView.leadingAnchor.constraint(equalTo: self.dynamicLabel.leadingAnchor),
            self.contentView.trailingAnchor.constraint(equalTo: self.dynamicLabel.trailingAnchor),

    override func prepareForReuse() {

        self.dynamicLabel.font = UIFont.preferredFont(forTextStyle: .physique)

    func setPreferred(width: CGFloat) {
        self.dynamicLabel.preferredMaxLayoutWidth = width

We now have a subclass for our cell, now let’s create the view controller class. Contained in the viewDidLoad methodology it’s important to set the estimatedItemSize property on the gathering view. There in case you give mistaken measurement, the autorotation will not work as anticipated.

override func viewDidLoad() {

    self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .refresh, goal: self, motion: #selector(self.toggleColumns))

    self.collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")

    if let flowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
        flowLayout.itemSize = CGSize(width: 64, peak: 64)
        flowLayout.minimumInteritemSpacing = 10
        flowLayout.minimumLineSpacing = 20
        flowLayout.sectionInset = UIEdgeInsets(prime: 10, left: 10, backside: 10, proper: 10)
        flowLayout.estimatedItemSize = CGSize(width: self.preferredWith(forSize: self.view.bounds.measurement), peak: 64)


    NotificationCenter.default.addObserver(self.collectionView!, selector: #selector(UICollectionView.reloadData), title: UIContentSizeCategory.didChangeNotification, object: nil)

Contained in the rotation strategies, it’s important to invalidate the gathering view format, and recalculate the seen cell sizes when the transition occurs.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

        let previousTraitCollection = previousTraitCollection,
        self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass ||
        self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass
    else {


override func viewWillTransition(to measurement: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    tremendous.viewWillTransition(to: measurement, with: coordinator)

    self.estimateVisibleCellSizes(to: measurement)

    coordinator.animate(alongsideTransition: { context in

    }, completion: { context in

There are two helper strategies to calculate the popular width for the estimated merchandise measurement and to recalculate the seen cell sizes.

func preferredWith(forSize measurement: CGSize) -> CGFloat {
    var columnFactor: CGFloat = 1.0
    if self.twoColumns {
        columnFactor = 2.0
    return (measurement.width - 30) / columnFactor

func estimateVisibleCellSizes(to measurement: CGSize) {
    guard let collectionView = self.collectionView else {

    if let flowLayout = self.collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
        flowLayout.estimatedItemSize = CGSize(width: self.preferredWith(forSize: measurement), peak: 64)

    collectionView.visibleCells.forEach({ cell in
        if let cell = cell as? CollectionViewCell {
            cell.setPreferred(width: self.preferredWith(forSize: measurement))

You may even have a number of columns in case you do the suitable calculations.

There is just one factor that I couldn’t remedy, however that is only a log message. If you happen to rotate again the system among the cells are usually not going to be seen and the format engine will complain about that these cells can’t be snapshotted.

Snapshotting a view that has not been rendered ends in an empty snapshot. Guarantee your view has been rendered a minimum of as soon as earlier than snapshotting or snapshot after display updates.

If you can also make this message disappear someway OS_ACTIVITY_MODE=disable, please do not hesitate to submit a pull request for the tutorials repo on github. 😉


Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.