This seems to be a pretty popular question, so I will try to make my modest contribution.
The code below is a Swift 4 solution for installation without a storyboard. It uses some approaches from the previous answers, so it prevents the Auto Layout warning that occurs when the device is rotated.
Sorry if the code examples are a bit long. I want to provide an βeasy to useβ solution, hosted entirely on StackOverflow. If you have any suggestions for a post, share the idea, and I will update it accordingly.
Setup:
Two classes: ViewController.swift and MultilineLabelCell.swift - A cell containing one UILabel .
MultilineLabelCell.swift
import UIKit class MultilineLabelCell: UICollectionViewCell { static let reuseId = "MultilineLabelCellReuseId" private let label: UILabel = UILabel(frame: .zero) override init(frame: CGRect) { super.init(frame: frame) layer.borderColor = UIColor.red.cgColor layer.borderWidth = 1.0 label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping let labelInset = UIEdgeInsets(top: 10, left: 10, bottom: -10, right: -10) contentView.addSubview(label) label.translatesAutoresizingMaskIntoConstraints = false label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: labelInset.top).isActive = true label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: labelInset.left).isActive = true label.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: labelInset.right).isActive = true label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: labelInset.bottom).isActive = true label.layer.borderColor = UIColor.black.cgColor label.layer.borderWidth = 1.0 } required init?(coder aDecoder: NSCoder) { fatalError("Storyboards are quicker, easier, more seductive. Not stronger then Code.") } func configure(text: String?) { label.text = text } override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { label.preferredMaxLayoutWidth = layoutAttributes.size.width - contentView.layoutMargins.left - contentView.layoutMargins.left layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height return layoutAttributes } }
ViewController.swift
import UIKit let samuelQuotes = [ "Samuel says", "Add different length strings here for better testing" ] class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private(set) var collectionView: UICollectionView // Initializers init() { // Create new 'UICollectionView' and set 'UICollectionViewFlowLayout' as its layout collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { // Create new 'UICollectionView' and set 'UICollectionViewFlowLayout' as its layout collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) super.init(coder: aDecoder) } override func viewDidLoad() { super.viewDidLoad() title = "Dynamic size sample" // Register Cells collectionView.register(MultilineLabelCell.self, forCellWithReuseIdentifier: MultilineLabelCell.reuseId) // Add 'coolectionView' to display hierarchy and setup its appearance view.addSubview(collectionView) collectionView.backgroundColor = .white collectionView.contentInsetAdjustmentBehavior = .always collectionView.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) // Setup Autolayout constraints collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true // Setup 'dataSource' and 'delegate' collectionView.dataSource = self collectionView.delegate = self (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).estimatedItemSize = UICollectionViewFlowLayout.automaticSize (collectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionInsetReference = .fromLayoutMargins } // MARK: - UICollectionViewDataSource - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MultilineLabelCell.reuseId, for: indexPath) as! MultilineLabelCell cell.configure(text: samuelQuotes[indexPath.row]) return cell } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return samuelQuotes.count } // MARK: - UICollectionViewDelegateFlowLayout - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let sectionInset = (collectionViewLayout as! UICollectionViewFlowLayout).sectionInset let referenceHeight: CGFloat = 100 // Approximate height of your cell let referenceWidth = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right - collectionView.contentInset.left - collectionView.contentInset.right return CGSize(width: referenceWidth, height: referenceHeight) } }
To run this example, create a new AppDelegate project, create the appropriate files, and replace the contents of the AppDelegate following code:
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var navigationController: UINavigationController? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) if let window = window { let vc = ViewController() navigationController = UINavigationController(rootViewController: vc) window.rootViewController = navigationController window.makeKeyAndVisible() } return true } }