I have a UITableView that looks like the following:
When a user changes the iOS text size settings, the layout changes.
Layout gets changed where the detail label gets pushed under the title, and the disclosure indicator gets very large. This happens with standard cell layout and custom cell layout with my own UILabel subviews.
I am not ready to support dynamic type, so is there a way to not allow iOS to change the layout of my UITableViewCells?
Many things have been made for iOS to assist with the Dynamic Type implementation in the table view cells โน automatic cell sizing for instance.
I reproduced the initial problem in a blank project as follows:
With no extra code and only due to the iOS native implementation, notice that:
There's a reordering of the labels when the first accessibility step is reached.
The system accessory element size changes according to the Dynamic Type using.
There's an automatic row height resizing.
[...] is there a way to not allow iOS to change the layout of my UITableViewCells?
A solution may lead to the creation of your own custom table view cell as follows:
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var myTitle: UILabel!
#IBOutlet weak var myDetail: UILabel!
static let reuseIdentifier = "myReuseIdentifier"
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpElementsAndConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpElementsAndConstraints()
}
private func setUpElementsAndConstraints() {
//Custom your labels and their constraints in here.
//For the demo, everything is hard coded within the Interface Builder.
}
}
Add your labels with the property adjustsFontForContentSizeCategory to false in order to keep their initial font size.
Create your own 'chevron image' that won't be resized as the system one:
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> MyTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MyTableViewCell.reuseIdentifier,
for: indexPath) as! MyTableViewCell
let accessoryImageFrame = CGRect(x: 0.0, y: 0.0,
width: 40.0, height: 40.0)
let accessoryImageView = UIImageView(frame: accessoryImageFrame)
accessoryImageView.image = UIImage(named: "MyChevron")
cell.accessoryView = accessoryImageView
return cell
}
Define a static row height in the table view:
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat { return 50.0 }
Don't forget to set up your constraints that may be updated according to the text sizes settings thanks to the traitCollectiondidChange method... even if you don't want to use the Dynamic Type feature yet. ๐
Finally, I get this result that never changes whatever the text sizes in the settings: ๐ฅณ
Following this rationale, your UITableViewCell layout doesn't change when iOS larger text is used. ๐ (I know, my custom chevron isn't really nice ๐)
Related
I am making a simple contacts application with controllers: ContactTableViewController and custome cell: ContactTableViewCell. I created a custom table view cell where the style is custom, identifier is ContactTableViewCell, and class is also ContactTableViewCell.
The cell has two UILabel fields, which are exposed to ContactTableViewCell.swift class as follows:
import UIKit
class ContactTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var info: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
print("cell loaded with name: ", name)
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Now I want to display them in my ContactViewController, and the relevant part of my controller looks like this:
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
// here we communicate with parts of the app that owns the data
override func tableView(_ tableView : UITableView
, cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
let id = "ContactTableViewCell"
// deque a cell as an instance of ContactTableViewCell
guard let cell = tableView.dequeueReusableCell(withIdentifier: id, for : indexPath)
as? ContactTableViewCell else {
fatalError("dequed cell is not instance of ContactTableViewCell")
}
cell.name.text = "hello"
cell.info.text = "hello information"
However, when I run the simulation I only see two rows of "hello", even though it should be something like:
hello
hello information
repeated twice. What is the problem here?
Most probably you don't have enough room to fit both labels. You can either set the row height in storyboard by:
Selecting your tableview
Go to size inspector
Change the Row Height
OR
You can implement tableView(_:heightForRowAt:) method in your tableView's delegate. i.e.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return desired height for your cell
return 90.0
}
It is a contraint issue. You have to define height, width, x and y positioning for both the name UILabel and the info UILabel. Font size places a dominant role for height with UILabels and width can be based on the number of characters in the UILabel x font size so you do not have to explicity express height and width constraints for a UILabel, but for a UIButton you would have to explicitly define constraints for x,y,width,height. For UILabel we only have to define x and y constraints.
What happens when you do not clearly define the constraints for your UI elements is that the rendering of your View will have unpredictable results and UI elements quite often just do not appear on the view.
It looks like, from your code, you are using XCode designer to add your UILabels so that is what you can use to add constraints. You can also add constraints programmatically as well. But I am pretty sure you are using XCode Storyboard designer.
Whether programmatically or through XCode designer you need add a constraint for the name UILabel to the top and left of the super view, you can reposition later, and then constrain x and y alignment of the info UILabel to the name UILabel, horizontally aligning to the name UILabel and +8 vertical spacing to the bottom of the name UILabel, this will place the info UILabel below and centered to the name UILabel.
See this guide: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithConstraintsinInterfaceBuidler.html
And this Stackoverflow answer: https://stackoverflow.com/a/34448801/1258525
Not the first two constraints I have circled in this picture, this defines the x and y for the Video Quality UILabel:
And then see how the next label below, Onset Threshold constraints itself to the leading edge of the Video Quality label and then the divider below the VideoQuality label:
My theory the 2nd label is truncated not getting sufficient width, and may be the number of lines in the second label is 1. Hence try setting number of line to 0 and check the width and auto layout constraints.
I have a UITableViewController where the cell's self sized correctly using Xcode 8 and Swift 3. Now that I'm using Xcode 9 and Swift 4, they aren't expanding and are just using the default height of 44.
(I have about a sentence or two in each UITableViewCell)
I was using this before:
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
... but was able to comment it out because per Updating Your App for iOS 11 said that the default would be self-sizing now:
I've tried playing around with changing the deployment target to iOS 11, playing around in Storyboard (but I'm using a Table View Cell style Basic so there is not much AutoLayout to be done), and I can't figure out what is going on.
I have the UILabel title set to 0 Lines, and have Line Break Word Wrap, but still not getting anywhere close to getting the cell to expand based on the text content inside of it in Xcode 9. Any ideas?
Thanks!
Edit:
Here's the options (that I don't have) for pinning since it is a Basic cell:
I had the same problem and solved it with to lines of code:
class MyTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = UITableViewAutomaticDimension
tableView.rowHeight = UITableViewAutomaticDimension
}
Maybe it is a bug in Xcode.
Update
New in Xcode 9 beta 3:
Interface Builder now supports setting the estimatedRowHeight of UITableView. This allows self-sizing table cells by setting the estimated height to a value other than zero, and is on by default. (17995201)
I had the same broken table view issue. Fix was just one click.
Go to your xib or storyboard scenes with table views, go to the size inspector, and you'll see the table view heights (even on dynamic table views) as 44, and sections will be 22. Just click "automatic" and boom, it will present as expected.
Note that I also specify the following in viewDidLoad of the UITableViewController subclass (layoutSubviews solves issues with the first load of a tableViewController not positioning correctly in relation to a non-translucent navBar).
self.tableView.estimatedRowHeight = 180;
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView layoutSubviews];
In addition to
tableView.estimatedRowHeight = UITableViewAutomaticDimension
tableView.rowHeight = UITableViewAutomaticDimension
you should set a height constraint for the contentView of the tabeleViewCell.
class CustomTableViewCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let height: CGFloat = 200
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
I got the same issue and I read about it in many documentation, satisfying answer was something like this, You have to check both options in order to get proper height, because estimated height is needed for initial UI setup like scrollview bars and other such stuff.
Providing a nonnegative estimate of the height of rows can improve the performance of loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
When you create a self-sizing table view cell, you need to set this property and use constraints to define the cellโs size.
The default value is 0, which means there is no estimate. (Apple Documentation)>
see this image for storyboard
Also note that there is a bug in xCode 9, when you try to apply Lazy loading in automatic height calculation, it will scroll unexpectedly, so I'll recommend you to use programmatic way in this regard.
self.postTableView.estimatedRowHeight = 200;
self.postTableView.rowHeight = UITableViewAutomaticDimension;
something Like this. Thanks!
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var tblview: UITableView!
var selectindex = -1
var arrnumber = ["1","2","3","4","5"]
var image = ["index.jpg","rose-blue-flower-rose-blooms-67636.jpeg","index.jpg","rose-blue-flower-rose-blooms-67636.jpeg","index.jpg"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrnumber.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tblview.dequeueReusableCell(withIdentifier: "cell", for: indexPath)as! ExpandableTableViewCell
cell.lblnumber.text = arrnumber[indexPath.row]
cell.img.image = UIImage(named: image[indexPath.row] as! String)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (selectindex == indexPath.row)
{
return 250
}
else{
return 60
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(selectindex == indexPath.row)
{
selectindex = -1
}else{
selectindex = indexPath.row
}
self.tblview.beginUpdates()
self.tblview.endUpdates()
}
}
For me, Safe Area was checked. Unchecking "Safe Area" did the work for me.
I need to support UITableViewAutomaticDimension (for dynamic height) with variations in the constraints: some need to be active, some not.
I setup the storyboard with aConstraint not installed, and bConstraint installed. I activate/deactivate them on need in tableView(_:cellForRowAt:).
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.estimatedRowHeight = 10
tableView.rowHeight = UITableViewAutomaticDimension
}
}
}
class MyTableViewCell: UITableViewCell {
#IBOutlet var aConstraint: NSLayoutConstraint!
#IBOutlet var bConstraint: NSLayoutConstraint!
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
cell.contentView.translatesAutoresizingMaskIntoConstraints = false
if indexPath.row % 2 == 0 {
NSLayoutConstraint.deactivate([cell.aConstraint])
NSLayoutConstraint.activate([cell.bConstraint])
} else {
NSLayoutConstraint.deactivate([cell.bConstraint])
NSLayoutConstraint.activate([cell.aConstraint])
}
return cell
}
}
Issue
The initial visible layout is ignoring all those activations/deactivations, and all the cells are identical to the original storyboard state.
You will notice that the correct constraints are only applied after scrolling.
Attempts
I did try without success some cell.setNeedsUpdateConstraints(); cell.updateConstraintsIfNeeded(); cell.layoutIfNeeded(); ...
Sample project shared on https://github.com/Coeur/dynamic-cell-height
Setup storyboard with both 'aConstraint' and 'bConstraint' installed, but put a lower priority on 'aConstraint' to remove warnings and it works :)
A couple of problems setting up constraints in your code.
1.
UILabel intrinsicContentSize will participate auto layout.
Auto layout system will create a width and height constraints based on intrinsicContentSize.
You explicitly set a height constraint of the label in xib file, causing an ambiguity, then added a vertical centre Y constraint to cover the problem.
2
If you want to try active or deactivate constraints, you may want to do that with a simple UIView.
3
If you want to do various height rows, take advantage of intrinsicContentSize, and set preferredWidth of UILabel.
I've scoured through all of the questions here regarding a similar issue and I still seem to have this problem no matter what I do. I'm on iOS 8.3.
You can find the full code at: https://github.com/DanielRakh/CellHeightTest.git
I have a cell that dynamically resizes based on the content of a UILabel that varies from single to multi-line text. Everything seems to be fine initially but when I start scrolling fast and quickly switch directions
I get a warning in the debugger:
2015-03-10 02:02:00.630 CellHeight[21115:3711275] Warning once only:
Detected a case where constraints ambiguously suggest a height of zero
for a tableview cell's content view. We're considering the collapse
unintentional and using standard height instead.
Here's how it looks in the simulator. I've marked the discrepancies in red:
Here's the simple setup in IB where I have a UILabel pinned top(11), bottom(11), leading(8), and trailing(8) with a custom cell at 45pts:
Here is my code. The cell is a custom subclass thats pretty much empty except for the IBOutlet for the label:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var arr = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for _ in 1...10 {
arr.append("This is an amount of text.")
arr.append("This is an even larget amount of text. This should move to other lines of the label and mace the cell grow.")
arr.append("This is an even larger amount of text. That should move to other lines of the label and make the cell grow. Crossing fingers. This is an even larger amount of text. That should move to other lines of the label and make the cell grow. Crossing fingers.")
}
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 45.0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TstTableViewCell
cell.tstLabel.text = arr[indexPath.row]
return cell
}
}
EDIT: I've found that if I created the cells programmatically and set the constraints programmatically everything seems to work as intended. Weird. This seems to be an IB + AL issue.
I'm having trouble getting a custom UITableViewCell to size properly when an Accessory is set. Why does the Accessory blow the cell sizing up on the initial display? When I scroll, with the accessory set, the sizing corrects itself - however, the initial view is not sizing correctly.
I've watched the WWDC14 (what's new in tableview) video several times and I've read many stackoverflow questions and tried many solutions. I think I have most of the problem solved - cells do resize for dynamic text - but I'm stumped on this strange initial behavior. I am running XCode 6.1.1 deploying to iOS 8.1.
I'm using a storyboard. I have a UITableViewController and custom UITableViewCell. I define constraints in the storyboard and there are no constraint warnings and I see no constraint messages in the console at runtime.
In my UITableViewController viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
with override
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TableViewCell", forIndexPath: indexPath) as TableViewCell
cell.configure(titles[indexPath.row])
return cell
}
This is my entire custom UITableViewCell
class TableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configure(title: NSString) {
titleLabel.text = title
titleLabel.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.preferredMaxLayoutWidth = frame.width
}
}
This is what it looks like when Accessory None is set on the cell
This is what it looks like when I set Accessory Disclosure Indicator on the cell
Again, when I scroll with the accessory set the sizing corrects itself. I've tried adding a vertical constraint to the cell both in storyboard and also by adding all constraints programmatically with no success. Thank you for any thoughts.
I've seen this problem too with storyboard designed self-sizing cells. Try adding self.layoutIfNeeded() to an override of didMoveToSuperview in your cell class. I don't know if this is a bug, or we're just missing something that we're supposed to be doing.