Self Sizing Cell in Swift, how do I make constraints programmatically? - ios

I'm trying to do a sidebar with self sizing cells in swift. I found this great tutorial : http://www.appcoda.com/self-sizing-cells/
As far as I know you need 3 things to make a self sizing cell:
Define auto layout constraints for your prototype cell
Specify the estimatedRowHeight of your table view
Set the rowHeight of your table view to UITableViewAutomaticDimension
The last two are covered in the tutorial by code, but the first one is explained by the story board, and my problem is how do I implement it by code??
I have this method where I get my custom cell, I think that I have to implement the constraints(in the tutorial you can see what kind of constrains) here but I don't know how, so please could you give me some help?
override func tableView(tableView: (UITableView!), cellForRowAtIndexPath indexPath: (NSIndexPath!)) -> UITableViewCell{
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CellId")
cell!.backgroundColor = UIColor.clearColor()
cell!.textLabel?.textColor = UIColor.darkTextColor()
let selectedView:UIView = UIView(frame:CGRect(x: 0, y: 0, width: cell!.frame.size.width, height: cell!.frame.size.height))
selectedView.backgroundColor = UIColor.orangeColor().colorWithAlphaComponent(0.3)
cell!.selectedBackgroundView = selectedView
//asignar valores a la celda
cell!.textLabel?.text = tableData[indexPath.row]
cell!.textLabel?.numberOfLines = 0
cell!.setTranslatesAutoresizingMaskIntoConstraints(false)
}
return cell!
}
Update 1:
The text goes beyond the 3rd break of line in the 1st and the 3rd row, but here only show me max. of 3 breaklines
Thanks!

Don't add layout constraints in tableView(_:cellForRowAtIndexPath:).
You should not instantiate a table view cell in that method yourself. Instead, you should register one with the UITableView (probably in the viewDidLoad() method of your view controller) (registerClass(_:forCellReuseIdentifier:) or registerNib(_:forCellReuseIdentifier:)).
In tableView(_:cellForRowAtIndexPath:) you use dequeueReusableCellWithIdentifier(_:forIndexPath:) to get an instance of the cell. The table view will reuse cells as you scroll, so that it doesn't have to create new ones all the time. Essentially, let's say you never see more than 15 cells on screen at the same time, then there won't be more than that many instances.
Now, when you register your table view cells (see above) you should probably subclass UITableViewCell and then either set your layout constraints in code (maybe override init(style:reuseIdentifier:)) or you can create a .nib file and use that. Then you can set up the constraints in Xcode through the graphical UI.
Let me know if you have any questions.

I found a good function to make cell dynamic size
func dynamicHeight(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.height
}
Call this function in heightForRowAtIndexPath
I hope it works.

Try this:-
First add this two functions 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.

Related

How to use Dynamic Type in TextView inside Table View Cell

I want to implement Dynamic Type in my application, so the app can adapt to user font preference. I have a Table View with 3 static cells. The first and second one has UITextField and they work fine without any problems. The third one which is a cell with UITextView doesn't scale. Dynamic Type is set to on and the font is Body.
Here's my code in my UITableViewController.
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
setupTextFieldsAndTextView()
}
func setupTextFieldsAndTextView() {
emailTF.keyboardType = UIKeyboardType.asciiCapable
titleTF.keyboardType = UIKeyboardType.asciiCapable
messageTextView.keyboardType = UIKeyboardType.asciiCapable
emailTF.returnKeyType = .next
titleTF.returnKeyType = .next
messageTextView.returnKeyType = .default
emailTF.delegate = self
titleTF.delegate = self
messageTextView.delegate = self
}
and UITableView delegate
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
I want to have the same design as in the iOS calendar app. Cell scale based on chosen font type but UITextView is still scrollable and the text inside is also chosen by the user. UITextView in the screen is a cell with text placeholder "Notes".
Few steps to be checked for the UITextView :
It must be inside your cell.
Its constraints must be set up with the cell.
Its text style MUST be selected if you want to use the Dynamic Type feature.
Its Automatically Adjusts Font property MUST be ticked in the Interface Builder if you want to use the Dynamic Type feature.
Turn off the Scrolling Enabled property in the Scroll View section of the Attributes Inspector.
Once all these elements are checked, the cell height and the UITextView font size should adapt the desired size.
I suggest you take a look at this detailed summary of the WWDC 2017 video that perfectly explains how to build an app with Dynamic Type.
Please check this for a dynamic height of textView in tableView.
// Tableview Delegate Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NotesCell", for: indexPath) as! NotesCell
cell.txtViewNotes.tag = indexPath.row
cell.txtViewNotes.delegate = self
return cell;
}
// Textview Delegate Method
func textViewDidChange(_ textView: UITextView) {
adjustFrames(textView)
}
// Custom Method
func adjustFrames(_ textView: UITextView) {
let indexPath = IndexPath(row: textView.tag, section: 0)
let cell = yourTableView.cellForRow(at: indexPath) as! NotesCell
UIView.setAnimationsEnabled(false);
self.yourTableView.beginUpdates()
cell.constNoteHeight.constant = textView.contentSize.height
textView.beginFloatingCursor(at: CGPoint.zero)
textView.endFloatingCursor()
self.yourTableView.endUpdates()
UIView.setAnimationsEnabled(true);
}
Notes: constNoteHeight is the height constant of textView.
Use GrowingTextView, For UITextView

spacing between UITableViewCells swift3

I am creating a IOS app in swift and want to add spacing between cells like this
I would like to give space of each table view cell same like my attach image.
How I can do that? and Right now all cells are coming without any space.
swift3
you can try this in your class of tableView cell:
class cell: UITableViewCell{
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 2 * 5
super.frame = frame
}
}
}
From Storyboard, your view hierarchy should be like this. View CellContent (as highlighted) will contain all the components.
Give margin to View CellContent of 10px from top, bottom, leading & trailing from its superview.
Now, select the tblCell and change the background color.
Now run your project, make sure delegate and datasource are properly binded.
OUTPUT
NOTE: I just added 1 UILabel in View CellContent for dummy purpose.
Update: UIEdgeInsetsInsetRect method is replaced now you can do it like this
contentView.frame = contentView.frame.inset(by: margins)
Swift 4 answer:
in your custom cell class add this function
override func layoutSubviews() {
super.layoutSubviews()
//set the values for top,left,bottom,right margins
let margins = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, margins)
}
You can change values as per your need
***** Note *****
calling super function
super.layoutSubviews()
is very important otherwise you will get into strange issues
If you are using UITableViewCell to achieve this kind of layout, there is no provision to provide spacing between UITableViewCells.
Here are the options you can choose:
Create a custom UIView within UITableViewCell with clear background, so that it appears like the spacing between cells.
You need to set the background as clear of: cell, content view.
You can use UICollectionView instead of UITableView. It is much more flexible and you can design it the way you want.
Let me know if you want any more details regarding this.
One simple way is collection view instead of table view and give cell spacing to collection view and use
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let widthSize = collectionView.frame.size.width / 1
return CGSize(width: widthSize-2, height: widthSize+20)
}
And if you want tableview only then add background view as container view and set background color white and cell background color clear color set backround view of cell leading, trilling, bottom to 10
backgroundView.layer.cornerRadius = 2.0
backgroundView.layer.masksToBounds = false
backgroundView.layer.shadowColor = UIColor.black.withAlphaComponent(0.2).cgColor
Please try it. It is working fine for me.
You can use section instead of row.
You return array count in numberOfSectionsInTableView method and set 1 in numberOfRowsInSection delegate method
Use [array objectAtIndex:indexPath.section] in cellForRowAtIndexPath method.
Set the heightForHeaderInSection as 40 or according to your requirement.
Thanks,Hope it will helps to you
- Statically Set UITableViewCell Spacing - Swift 4 - Not Fully Tested.
Set your tableView Row height to whatever value you prefer.
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = <Your preferred cell size>
tableView.rowHeight = UITableViewAutomaticDimension
// make sure to set your TableView delegates
tableView.dataSource = self
tableView.delegate = self
}
extension YourClass : UITexFieldDelegate, UITextFieldDataSource {
//Now set your cells.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "yourCell", for: indexPath) as! UITableViewCell
//to help see the spacing.
cell.backgroundColor = .red
cell.textLabel?.text = "Cell"
return cell
}
//display 3 cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
//now lets insert a headerView to create the spacing we want. (This will also work for viewForHeaderInSection)
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//you can create your own custom view here
let view = UIView()
view.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44) //size of a standard tableViewCell
//this will hide the headerView and match the tableView's background color.
view.backgroundColor = UIColor.clear
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
}

UITableViewCell height with dynamic subview swift

I have a UITableView that UITableViewCell has a dynamic UILabel which will be added based on data i get from the database.
I'm using something like
let testing = ["A", "B", "C", "D"] // This array is dynamic data
self.dictionaryTableView.estimatedRowHeight = 44
self.dictionaryTableView.rowHeight = UITableViewAutomaticDimension
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
var y = 0
for var i in 0..<self.testing.count {
let lbl = UILabel(frame: CGRect(x: 0, y: y, width: 50, height: 25))
lbl.text = self.testing[i]
lbl.numberOfLines = 0
cell.addSubview(lbl)
y += 20
}
return cell
}
But UITableViewCell height does not stretch automatically to display all content cell. Please help
Here is the result
EDIT I added constraint for the uilabel in UITableView cellForRowAtIndexPath, the constraints are working (I knew it when I expend the cell height), but cell height not automaticly strech out
var bottomAnchor: NSLayoutYAxisAnchor = NSLayoutYAxisAnchor()
for var i in 0..<self.testing.count {
let lbl = UILabel()
lbl.text = self.testing[i]
lbl.numberOfLines = 0
lbl.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(lbl)
lbl.leftAnchor.constraint(equalTo: cell.leftAnchor, constant: 16).isActive = true
lbl.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
lbl.heightAnchor.constraint(equalToConstant: 25).isActive = true
if i == 0 {
lbl.topAnchor.constraint(equalTo: cell.topAnchor).isActive = true
} else {
lbl.topAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
bottomAnchor = lbl.bottomAnchor
}
return cell
Many thanks
There are two things that you need to fix in your implementation:
Create the layout of your cell before dequeueing it in tableView(_:, cellForRowAt:).
For efficiency reasons, table views reuse the same cells over and over again when the user scrolls. That's why you use this weird "dequeuing" function rather than simply instantiating a new cell.
let cell = tableView.dequeueReusableCell(withIdentifier: "vocabCell", for: indexPath)
will always try to return a used cell that just scrolled out of the view. Only if there are no recycled cells available (for example when dequeueing the first couple of cells) the table view will create new instances of a cell.
The recycling of cells is the very reason why you should never create your cell's layout inside tableView(_:, cellForRowAt:), for example by adding a label. When the cell is being reused, another label will be added on top of the label that you added before and when it's being reused a second time, you'll end up with three overlapping labels etc.
The same applies to constraints. When you add constraints to a cell in tableView(_:, cellForRowAt:) without removing existing ones, more and more constraints will be added while the user is scrolling, most likely resulting in serious constraint conflicts.
Instead, setup your cell's layout before dequeueing it. There are several ways to achieve this:
If you use a UITableViewController inside a storyboard, you can create dynamic prototypes and lay out the cells directly in the storyboard. You could, for example, drag a label to a prototype cell there and create an outlet for it in a custom UITableViewCell subclass.
You can create a XIB file for your cell, open it in Interface Builder and create your layout there. Again, you need to create a UITableViewCell subclass with the appropriate outlets and associate it with your XIB file.
You can create a UITableViewCell subclass and set up your layout purely in code, for example inside the cell's initializer.
You need to use Auto Layout.
If you create your layout in Interface Builder, you just need to add the necessary constraints and you're good to go. If you create your layout in code, you need to set the translatesAutoresizingMaskIntoConstraints property to false for all views that you wish to constrain, for example:
lbl.translatesAutoresizingMaskIntoConstraints = false
In your particular layout, you need to constrain the label at the left, right, top and bottom with the corresponding edges of your cell's contentView.
If you don't know how to do that, please read Apple's Auto Layout Guide. (It's usually a better idea to do this in Interface Builder rather than in code.)
A very detailed description of how to use "Auto Layout in UITableView for dynamic cell layouts & variable row heights" can be found here.
used this code.
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
You can use UITableView section to render the data instead of adding the view programmatically. Here's the example:
class ViewController: UITableViewController {
private var dataSectionOne = ["Data 1", "Data 2"]
// This can be your dynamic data.
// Once the data changed, called tableView.reloadData() to update the view.
private var dataSectionTwo = ["A", "B", "C"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return dataSectionOne.count
} else if section == 1 {
return dataSectionTwo.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.section == 0 {
cell.textLabel?.text = dataSectionOne[indexPath.row]
} else if indexPath.section == 1 {
cell.textLabel?.text = dataSectionTwo[indexPath.row]
}
return cell
}
}
The results:
I found the answers here
I added a constraint to my UITableViewCell bottom with the following code and it's working, but I don't know what exactly this line doing (I don't know the parameters too)
Could anyone help me explain this code
let bottomSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: lbl, attribute: NSLayoutAttribute.bottomMargin, relatedBy: NSLayoutRelation.equal, toItem: cell.contentView, attribute: NSLayoutAttribute.bottomMargin, multiplier: 1, constant: -8)
cell.contentView.addConstraint(bottomSpaceConstraint)

Settings UITableViewCell Heights With Multiline UIButtons in Auto Layout

I have a UITableView with custom UITableViewCells, each has a UIButton inside. I'm setting buttons' titles from an array, so the size of the buttons change according to the title. I need to return correct height based on the inner button's size in heightForRowAtIndexPath event.
Since I'm using auto layout, I've created an outlet for the button's height constraint and I'm updating it in the cell's layoutSubviews() event like this:
class CustomCell: UITableViewCell {
/* ... */
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
}
Then I return the height based on the button height and top-bottom margins like so:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds))
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.myButton!.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + (cell.topMarginConstraint!.constant * 2) /* top-bottom margins */ + 1 /* separator height */
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell") as! CustomCell
cell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
return cell
}
On the first launch, there seems to be no problem. However, after I begin scrolling, then the height of some rows seem to be mistaken. When I get back to the top, I see that previous cell heights get to be broken as well.
When I googled for similar problems, issue seems to be about reusable cells, though I was unable to find another way to calculate the height. What can be done to reuse cells correctly or getting the correct height, perhaps by another method?
More info and source code:
Constraints set by IB like this:
Here's the cells on the first launch:
After some scrolling:
Full code of the project can be found on Github.
According to this
Configure tableView as
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
Call it on your viewDidLoad method
Than configure your uibutton height constraint to be greater then or equal.
Override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat where you can place your estimation height code
First off, it's better if you perform constraint updates in func updateConstraints() method of UIView. So instead of
override func layoutSubviews() {
super.layoutSubviews()
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
}
I would do
override func updateConstraints() {
self.myButton?.layoutIfNeeded()
self.heightConstraint?.constant = self.myButton!.titleLabel!.frame.size.height
super.updateConstraints()
}
Note that you should call the super implementation at the end, not at the start. Then you would call cell.setNeedsUpdateConstraints() to trigger a constraint update pass.
Also you should never directly manipulate the cell bounds the way you are doing in heightForRowAtIndePath: method, and even if you are completely sure that manipulating directly is what you want, you should manipulate cell.contentView's bounds, not the cell's bounds. If you are looking to adjust the cell height dynamically with respect to the dimensions of the content, you should use self sizing cells. If you need to support iOS 7, then this answer tells you how to achieve that behaviour with autolayout only (without touching the bounds etc).
To reiterate the answer, you should do:
func viewDidLoad() {
self.dummyCell = CustomCell.init()
// additional setup
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
self.dummyCell.myButton?.setTitle(self.data[indexPath.row], forState: UIControlState.Normal)
self.dummyCell.layoutIfNeeded() // or self.dummyCell.setNeedsUpdateConstraints() if and only if the button text is changing in the cell
return self.dummyCell.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
Please know that the answer I linked to outlines a strategy to get the cell height via autolayout, so only writing the code changes I proposed won't be enough unless you set your constraints in a way that makes this solution work. Please refer to that answer for more information.
Hope it helps!
First of all, remove the height constraint of button and bind it to top and bottom with cell.
Then, in your cell' height, calculate height of the text based on the width and font of button. This will make the cell's height dynamic and you wont need height constraint anymore.
Refer the link below to get the height of text:
Adjust UILabel height to text
Hope it helps. If you need help further or understanding anything, let me know.. :)

dynamic cell height in ios using constrain programatically in swift

I am working on a project in swift which needs the cell size to be dynamic according to the label content, so i searched for dynamic cell height, i found some solution but all of it included use of storyboard for assigning constrain. Is it possible to do it without storyboard. Is it possible to do it programatically? I mean applying self sizing without storyboard
my code is:
class a: UITableViewDelegate, UITableViewDataSource {
tableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height), style: UITableViewStyle.Plain)
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
self.view.addSubview(tableView)
}
Hope the question is clear.
Thanks in advance.
Edit: None of the answers below is helpful
Try this method.
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
return // get cell text label high and return +10 or +20 height. what is label height..
}
iOS 7 compatible: https://github.com/smileyborg/TableViewCellWithAutoLayout
iOS 8 only: https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8
You can try this method.
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
self.dashBoardTableView.rowHeight = UITableViewAutomaticDimension
if (CellIdentifier == "dashBoardTableCellID")
{
dashBoardTableView.rowHeight = 96
}
else
{
dashBoardTableView.rowHeight = 60
}
return self.dashBoardTableView.rowHeight
}
Yes you can, you can create your constraints in code.
I suggest you look at the class NSLayoutConstraint:
NSLayoutConstraint Apple documentation
iOS 8 suppoorts self sized tableview cells. To enable it first create your cell with correct constraints. End use code below
tableView.estimatedRowHeight = 50
tableView.cellHeight = UITableViewCellAutomaticDimension
estimatedRowHeight is just hint for tableview. When tableview reuses cells it looks its content and adjust sizes acoording to it.

Resources