Resizing the tableView's tableHeaderView to account for an attributedText? - ios

I want to make sure that my headerView inside a tableView is dynamically growing to account for the size of my attributed string.
This concept works fine with just autolayout for my cell-views without much work.
However I am having problems with the header (a UIView) and so I am trying to calculate it's height by hand but for some reason the height given by this code is too small?
func setupTableHeader () {
let str = "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
let attributedStr = NSAttributedString(string: str)
headerViewLabel.attributedText = attributedStr
let rect = attributedStr.boundingRect(with: CGSize(width: view.frame.width, height: 100000000000000000), options: [.usesFontLeading, .usesLineFragmentOrigin], context: nil)
tableView.tableHeaderView = headerView
let height = rect.height
headerView.frame = CGRect(x: 0, y: 0, width: self.view.width, height: height + headerView.frame.height)
}

Create an extension on NSAttributedString that calculates a height based on a given width:
extension NSAttributedString {
func height(forConstantWidth width: CGFloat) -> CGFloat {
let constraintBox = CGSize(width: width, height: .greatestFiniteMagnitude)
let calculatedBox = boundingRect(with: constraintBox, options: .usesLineFragmentOrigin, context: nil)
return calculatedBox.height
}
}
You could call that and pass in the table view's width, like:
let height = myAttrString.height(forConstantWidth: tableView.frame.size.width)

You can make UITableView's header resize to account for size of its Sub Elements.
You can use following extension.
extension UITableView {
func automaticHeaderViewSize() {
if let headerView = self.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
//Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableHeaderView = headerView
}
}
}
}
Just make sure, your label has numberOfLines = 0.
So, finally your code is going to be (according to your question).
func setupTableHeader () {
let str = "your long text..."
let attributedStr = NSAttributedString(string: str)
headerViewLabel.attributedText = attributedStr
tableView.tableHeaderView = headerView
tableView.automaticHeaderViewSize()
}

Related

SwiftUI ZStack alignment bug

Here is a simple code that produces a strange top alignment and shrunk text of the second element inside ZStack.
BUT if you change the second text a bit (replace text2 by text2Alt1 or by text2Alt2) making it longer or shorter everything becomes correct.
What is the reason for this behavior?
struct VCardView: View {
let text: String
let color: UIColor
var body: some View {
VStack {
Rectangle()
.fill(Color(self.color))
.frame(idealWidth: 800, idealHeight: 500)
.aspectRatio(contentMode: .fit)
Text(self.text)
}
}
}
struct ContentView: View {
var body: some View {
ZStack(alignment: .topLeading) {
VCardView(text: text1, color: .blue)
.frame(width: 180, height: nil)
.alignmentGuide(.leading) { _ in 180 }
.alignmentGuide(.top) { _ in 0 }
//Replace text2 by text2Alt1 or text2Alt2 here:
VCardView(text: text2, color: .green)
.frame(width: 180, height: nil)
.alignmentGuide(.leading) { _ in 0 }
.alignmentGuide(.top) { _ in 0 }
}
}
let text1 = "1Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidu!"
let text2 = "2Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis nat!"
let text2Alt1 = "2Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. C"
let text2Alt2 = "2Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis nat! Cum sociis nat!"
}
Also, you could replace Rectangle() by Image() with appropriate proportions because it was the initial state. Rectangle with the ideal size is just for demonstration.
Here I use ZStack with explicit alignment guides (not HStack) because it's a part of another library. It is essential.
XCode 11.5
iOS 13.5 iPhone SE 2020
Bug looks like this:
Expected layout:
I've seen such Text truncation problems often in SwiftUI. The workaround which most of the time helps: ensure that Text is allowed to adjust it's font size automatically using the minimumScaleFactor modifier.
By modifying VCardView
Text(self.text)
.minimumScaleFactor(0.5)
The misalignment problem disappears for all possible text values (text1, text2, text2Alt1 and text2Alt2).
The resulting text does not look scaled at all. Everything fits nicely.
I do not have a good explanation, but I think if you tell SwiftUI that your Text is not 100% rigid/stiff, it performs better in calculating the elements extents.
Perhaps it is a SwiftUI bug, but I was always OK with this minimumScaleFactor workaround as it hadn't negatively impacted my results.
it doesn't look like a ZStack problem. Try to remove .aspectRatio(contentMode: .fit) from Rectangle() in VCardView.
For rectangle proportion check something like this.

Which is more performant: replacingOccurrences or components(separatedBy:).joined()

I wish to remove all intermediate spaces in s String.
Option 1:
func removingWhitespaces() -> String {
return replacingOccurrences(of: " ", with: "")
}
Option 2:
func removingWhitespaces() -> String {
return components(separatedBy: .whitespaces).joined()
}
Which is more performant ?
As per my point of view, I think option 1 is faster than the options 2.
Reason:
In option 2, you are chaining the return value of components(separatedBy:) to joined(). So ultimately the return value of the joined()` is used whereas in the option 1 you are directly calling the build-in function of the String.
As per my understanding, I would like to suggest Option 1
Because,
replacingOccurrences(of: " ", with: "")
will perform only single operation.
where,
components(separatedBy: .whitespaces).joined()
will perform two operation and taking more time. First it will separate elements by whitespace and create array then on array it will perform join operation and give you output.
Figure it out yourself. Basic performance testing is very simple in Xcode. In a XCTestCase class run these 2 tests
func testPerformance1() {
let string = "I wish to remove all intermediate spaces in a String"
self.measure {
for _ in 0..<10000 {
_ = string.replacingOccurrences(of: " ", with: "")
}
}
}
func testPerformance2() {
let string = "I wish to remove all intermediate spaces in a String"
self.measure {
for _ in 0..<10000 {
_ = string.components(separatedBy: .whitespaces).joined()
}
}
}
and read the result in the console. replacingOccurrences is much faster.
There is no big difference between components(separatedBy: " ") and components(separatedBy: .whitespaces)
Space
When talking about performance, one should take space complexity into account. What's meant by this term is how much memory will be needed to run this piece of code and describes the relationship between the number of elements in the input and the reserved memory. For example, we talk about:
O(n) space complexity when the reserved memory grows with the number of elements in the input.
O(1) space complexity when the reserved memory doesn't grow when the number of the input elements grows.
Between replacingOccurrences(of: " ", with: "") and components(separatedBy: .whitespaces).joined(), the former wins on space complexity since the latter creates an intermidiary array, and in performance, less is more.
Time
Given this string :
let str = "Lorem ipsum dolor sit amet, tempor nulla integer morbi, amet non amet pede quis enim, ipsum in a in congue etiam, aenean orci wisi, habitant ipsum magna auctor quo odio leo. Urna nunc. Semper mauris felis vivamus dictumst. Tortor volutpat fringilla sed, lorem dui bibendum ligula faucibus massa, dis metus volutpat nec ridiculus, ac vel vitae. At pellentesque, at sed, fringilla erat, justo eu at porttitor vestibulum hac, morbi in etiam sed nam. Elit consectetuer lorem feugiat, ante turpis elit et pellentesque erat nec, vitae a fermentum vivamus ut. Orci donec nulla justo non id quis, ante vestibulum nec, volutpat a egestas pretium aliquam non sed, eget vivamus vestibulum, ornare sed tempus. Suscipit laoreet vivamus congue, tempor amet erat nulla, nostrum justo, wisi cras ac tempor tincidunt eu, hac faucibus convallis. Ac massa aenean nunc est orci, erat facilisis. Aliquam donec. Ut blandit potenti quam quis pellentesque, cursus imperdiet morbi ea ut, non mauris consectetuer mauris risus vehicula in, sed rutrum pellentesque turpis. Eros gravida volutpat justo proin donec penatibus, suspendisse fermentum sed proin fringilla libero malesuada, nulla lectus ligula, aliquam amet, nemo quis est. Quis imperdiet, class leo, lobortis etiam volutpat lacus wisi. Vestibulum vitae, nibh sem molestie natoque. Elementum ornare, rutrum quisque ultrices odio mauris condimentum et, auctor elementum erat ultrices. Ex gravida libero molestie facilisi rutrum, wisi quam penatibus, dignissim elementum elit mi, mauris est elit convallis. Non etiam mauris pretium id, tempus neque magna, tincidunt odio metus habitasse in maecenas nonummy. Suspendisse eget neque, pretium fermentum elementum."
The benchmarking code is given below. Each code block will be run separately, while the others will be commented out. :
do {
let start = Date()
let result = str.components(separatedBy: " ").joined()
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
do {
let start = Date()
let result = str.split(separator: " ").joined()
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
do {
let start = Date()
let result = str.filter { !$0.isWhitespace }
let end = Date()
print(s.count, end.timeIntervalSince(start))
}
do {
let start = Date()
var s = str
s.removeAll { $0.isWhitespace }
let end = Date()
print(s.count, end.timeIntervalSince(start))
}
do {
let start = Date()
let result = str.components(separatedBy: .whitespaces).joined()
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
do {
let start = Date()
var result = ""
for char in str where char != " " {
result.append(char)
}
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
do {
let start = Date()
let result = str.replacingOccurrences(of: " ", with: "")
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
do {
let start = Date()
var arr = str.utf8CString
arr.removeAll(where: { $0 != 32 })
var result = ""
arr.withUnsafeBufferPointer { ptr in
result = String(cString: ptr.baseAddress!)
}
let end = Date()
print(result.count, end.timeIntervalSince(start))
}
Compiled with optimization in the terminal using this command:
xcrun swiftc -O ReplaceStr.swift -o replaceStr
-O: with optimizations
ReplaceStr.swift: the name of the file containing the code. You should cd to the location of this file first.
-o: to specify the name of the output compiled file
replaceStr is an example name for the output file
And then run with ./replaceStr
After running each block multiple times, here are the best timings:
components(separatedBy: " ").joined() : 0.77ms
components(separatedBy: .whitespaces).joined() : 0.75ms
str.split(separator: " ").joined() : 0.54ms
filter { !$0.isWhitespace } : 0.52ms
removeAll { $0.isWhitespace } : 0.52ms
for char in str where char != " " : 0.26ms
replacingOccurrences(of: " ", with: "") : 0.23ms
str.utf8CString : 0.18ms
Comparable results were found with a shorter string :
let str = "Lorem ipsum dolor sit amet, tempor nulla integer morbi, amet non amet pede quis enim, ipsum in a in congue etiam, aenean orci wisi, habitant ipsum magna auctor quo odio leo."
Verdict
replacingOccurrences(of: " ", with: "") is better than components(separatedBy: .whitespaces).joined() in time complexity too. This is partially because replacingOccurrences(of:with:) is defined on NSString and not String. In a sense it's like comparing 🍏🍎 to 🍊🍊.
Manipulating the underlying CString beats them all 🥊 and is the overall best.
For more on benchmarking code, here is a good thread.
Using Split with joined is faster the other 2 options
class new: XCTestCase {
func testOption1() {
let string = String(repeating: "This is an example of a performance test case.", count: 10000)
self.measure {//0.0231s
_ = string.replacingOccurrences(of: " ", with: "")
}
}
func testOption2() {
let string = String(repeating: "This is an example of a performance test case.", count: 10000)
self.measure {//0.194s
_ = string.components(separatedBy: " ").joined()
}
}
func testOption3() {
let string = String(repeating: "This is an example of a performance test case.", count: 10000)
self.measure {//0.0184s
_ = string.split(separator: " ").joined()
}
}
}

How to update progressView when downloading something in realm

I am trying to figure out how to use a progressView to show the progress of a something being downloaded using Realm. Through reading it seems that a Float called progress needs to be found, but I don't know how to find that in Realm. I have looked at the following questions, but I don't think that they are too helpful in this situation:
Swift: Realm - Update UI (Progress) while adding Data to DB
iOS-Swift How to update progressView
This is what I have at the moment:
ViewController:
import UIKit
import Realm
import RealmSwift
class ViewController: UIViewController {
var data = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
var realm: Realm!
#IBOutlet weak var saveBtn: UIButton!
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
print("Hello")
print(Realm.Configuration.defaultConfiguration.fileURL!)
realm = try! Realm()
}
#IBAction func saveBtnPressed(_ sender: Any) {
saveToRealm(id: 1, name: "One", data: data)
let progress: Float = 0
progressView.setProgress(progress, animated: true)
}
func saveToRealm(id: Int, name: String, data: String) {
let realmSave = RealmSave()
realmSave.id = id
realmSave.name = name
realmSave.data = data
try? realm!.write {
realm.add(realmSave, update: true)
}
}
}
If there is anything that I can help with, please ask. Thank you
First: defining a let constant with a value of 0 will always stay 0. You can't dynamically change that to reflect the state of your download.
As a Realm engineer pointed out in this post "Realm has no way to know the total amount of data."
Estimating the progress should be done in your code.
You could try something like this:
func estimateProgress() {
let dataString = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
let data = dataString.data(using: .utf8)
let count = Float((data?.count)!)
let percentage: Float = count / 100
var progress: Float = 0
if progress < count {
progress += percentage
} else {
progress = count
}
progressView.setProgress(progress, animated: true)
print(progress)
}
Please note that this solution is for updating the UI and letting the user know that the saving to Realm is in progress. Since it uses the amount of bytes in the data, the larger the data, the longer it will take for the progress bar to fill. So it is good for UI, but it is not the actual state of progress.
And if you feel the progress bar is filling to slow/fast you can always tweak the percentage.

Creating Self-Sizing Table View Cells Dependant on UILabel Height

I'm using Swift and am having some issues with making my table view cells self-sizing. From what I have read online the best way is to use:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 138.0
I have put this code in but it hasn't seemed to work. I've tried it within viewDidLoad and also the cellForRowAt function. I'm not sure if I'm putting it in the wrong place or if I've set something up wrong. I have also got my auto constraint for the bottom of my UILabel set in relation to the bottom of the container with the expectation that the cell container will grow but the bottom of the UILabel to the bottom on the container will remain constant. Here is the code I currently have in my TableViewController:
class TableViewController: UITableViewController {
var data = [TextData]()
override func viewDidLoad() {
super.viewDidLoad()
loadSampleData()
}
func loadSampleData() {
let title1 = TextData(title: "Monday", blurb: "Today is Monday and it's a sunny day", photograph: #imageLiteral(resourceName: "mondayone"), article: "Monday Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")
let title2 = TextData(title: "Tuesday", blurb: "Today is Tuesday and it's a bit rainy but will clear up later on today", photograph: #imageLiteral(resourceName: "tuesdaytwo"), article: "Tuesday Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." )
let title3 = TextData(title: "Wednesday", blurb: "Today is Wednesday and the umbrella should be handy because it's going to be stormy", photograph: #imageLiteral(resourceName: "wednesdaythree"), article: "Wednesday Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.")
let title4 = TextData(title: "Thursday", blurb: "Today is Thursday and it's sunny again, no need for any umbrella today!", photograph: #imageLiteral(resourceName: "thursdayfour"), article: "Thursday Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.")
data += [title1, title2, title3, title4]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TitleTextCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! TableViewCell
let realData = data[(indexPath as NSIndexPath).row]
cell.titleLabel.text = realData.title
cell.bodyText.text = realData.blurb
cell.photo.image = realData.photograph
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 138.0
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Here is what my tableview currently looks like:
Here are my current constraints:
Currently photo is preventing your cell from getting taller. Since photo is constrained to both the top and centre of the cell, making the cell taller would always make the image taller, but since the image has a height constraint, photo stops the cell from getting taller.
Remove the Photo.centerY constraint should allow the cell resize based on the label. Or, you could change the constaraint to an inequality (≤ or ≥, cant remember which). That should limit the cell from getting to small if there isn't already another constraint that does that.

Custom cell to display multiline label using UITableViewAutomaticDimension

Aim:
To display a multiline label on a custom cell in a UITableView using autolayout and UITableViewAutomaticDimension.
The cell should vary based on the text in each UILabel.
What I have done:
Used auto layout
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
label.numberOfLines = 0 (label is the new label I created)
label.setTranslatesAutoresizingMaskIntoConstraints(false)
Added UILabel to the contentView
Added constraints from the UILabel to content view
Problem:
Some text gets clipped (...) when I use a custom cell
Sometimes the scrolling is not very smooth
Tried same with UITableViewCell:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
textLabel.numberOfLines = 0 (textLabel already exists in UITableViewCell)
Scrolling seems smooth
No clipping, text is displayed correctly
Printed the cell.textLabel?.preferredMaxLayoutWidth and it shows 0
Question:
Why is it working fine with UITableViewCell and not my custom cell ?
In both cases the preferredMaxLayoutWidth is not being set.
Pls let me know what I am missing.
Code for my custom cell:
class MultilineLabelCell : UITableViewCell {
let label = UILabel()
//MARK: Initializers
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
//MARK: Setup Views
func setup() {
setupLabel()
setupLayout()
}
func setupLabel() {
label.setTranslatesAutoresizingMaskIntoConstraints(false)
label.numberOfLines = 0
contentView.addSubview(label)
}
func setupLayout() {
let views = ["label" : label]
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[label]-|",
options: .allZeros,
metrics: nil,
views: views)
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[label]-|",
options: .allZeros,
metrics: nil,
views: views)
contentView.addConstraints(horizontalConstraints)
contentView.addConstraints(verticalConstraints)
}
}
View Controller Code
override func loadView() {
super.loadView()
tableView.registerClass(MultilineLabelCell.self, forCellReuseIdentifier: ViewController.cellIdentifier)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
func populateData() {
dataArray.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat")
dataArray.append("Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum")
dataArray.append("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam,")
dataArray.append("nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?")
dataArray.append("But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(ViewController.cellIdentifier) as! MultilineLabelCell
cell.label.numberOfLines = 0
cell.label.text = dataArray[indexPath.row]
return cell
}
While giving constraints programatically views/controls in content view of UITableViewCell and want to have dynamic cell size based on your text length, you need to give padding from all four sides explicitly(provide some value). Do changes as per below. Looks like a bug
In setupLayout method
func setupLayout() {
let views = ["label" : label]
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[label]-5-|",
options: .allZeros,
metrics: nil,
views: views)
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|-5-[label]-5-|",
options: .allZeros,
metrics: nil,
views: views)
contentView.addConstraints(horizontalConstraints)
contentView.addConstraints(verticalConstraints)
}

Resources