Dynamically move a view to a new position in auto layout - ios

I have a custom UITableViewCell layout that looks like this. It has three labels.
Label 2 is an optional one. It's not present in every cell. So I want to hide that and move the Label 1 down a little to be center aligned with the Label 3 when that happens.
Here are the constraints I've added for each label.
Label 1
Label 2
Label 3
Notice I have added an extra constraint, Align center to Y with the value of 0 to Label 1 and have set its priority to 750. I figured if I remove the Label 2, that constraint with the lower priority will take its place and move down.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
if indexPath.row == 1 {
cell.label2.removeFromSuperview()
cell.updateConstraints()
}
return cell
}
}
But it doesn't seem to work. Label 2 is removed but Label 1's position is still the same.
How can I accomplish what I'm after?
Attempt #1
As per Mr. T's answer below, I added a top constraint to the Label 1. And then in the cellForRowAtIndexPath method, I changed it's value.
override func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
if indexPath.row == 1 {
cell.label2.removeFromSuperview()
cell.topConstraint.constant = cell.bounds.height / 2
cell.layoutIfNeeded()
}
return cell
}
But this didn't work either.

Try to have an outlet for the top constraint for the label 1. And when you remove the label 2, update the top constraint for the label1 which is the container.height/2 Or Remove the top constraint and give the centerY constraint to the label 1. And do layout if needed, once you updated the constraints.

I am pretty much impressed the way you came so far as that would have been exactly the same steps i would have followed if i had to accomplish it :)
Now my suggestion:
remove an extra "Align center to Y with the value of 0 to Label 1" that you have added that is not serving any purpose :)
I can see you already have a align center to y with some offset i believe -13 to label 1. Create an iboutlet for that :) let's say its name as centerLabel1Constraint :)
whenever you want to bring label 1 to center hide label 2 and set centerLabel1Constraint.constant = 0 and call [cell layoutIfNeeded]
That should do the job :)
Happy coding :)

I figured out a way to do this utilizing the new active property of NSLayoutConstraints as described in this answer.
I made an IBOutlet to the Align center Y constraint with the value -13. Removed the weak keyword from it.
Then in the cellForRowAtIndexPath method, I'm simply toggling the value for the active property.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
if indexPath.row % 2 == 0 {
cell.label2.hidden = true
cell.oldConstraint.active = false
} else {
cell.label2.hidden = false
cell.oldConstraint.active = true
}
return cell
}

Related

How to narrow UITableView cell height if there is dynamic structure in Swift?

I have a tableView and cells. The Cells are loaded from a xib and they have a label with automatic height. I need to narrow one cell if the user taps on it.
I have tried hiding - doesn't work
I have tried removeFromSuperView()- doesn't work
Is there any alternative?
When setting up your tableViewCell store the height anchor you want to update
var yourLabelHeightAnchor: NSLayoutConstraint?
private func setupLayout() {
yourLabelHeightAnchor = yourLabel.heightAnchor.constraint(equalToConstant: 50)
// Deactivate your height anchor as you want first the content to determine the height
yourLabelHeightAnchor?.isActive = false
}
When the user clicks on a cell, notify the tableView that the cell is going to change, and activate the height anchor of your cell.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCellIdentifier") as? YourCell
self.tableView.beginUpdates()
cell?.yourLabelHeightAnchor?.isActive = true
self.tableView.endUpdates()
}
Did you try to do something like this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var result: CGFloat
if (indexPath.row==0) {
result = 50 }
else {result = 130}
return result
}
This is just an example where height is changed for the first row. I tested on my application and it gave result like this.

How to use dynamic height of UITableViewCell in auto-layout and move other views to up when bottom view is hidden?

I have a UITableViewCell in xib and its outlets in corresponding UITableViewCell subclass. I am returning height of cell from
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) ->CGFloat {
return 400
}
I need to hide some views based on the data available in each row of the table and bottom view should shifted to top of the cell. When I am hiding view from cell then there is empty space left in place of hidden view & bottom views are not shifting to top part of the cell.
Here is How I am hiding cell view.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
.....
cell.opetion4.isHidden = true
cell.opetion3.isHidden = true
}
This is my cell.
After hide 2 middle labels it is looking as follows.
But I want to remove this empty space and want to shift bottom label to top as follows.
At first, make the height of UITableViewCell to UITableView.automaticDimension
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Embed all of your questionLabels in a UIStackView (vertical) excluding bottomLabel. Set AutoLayoutConstraint between UIStackView and bottomLabel.
Set the numberOfLines property of UILabels to 0(zero).
Set the Distribution of the UIStackView as Fill
Then, in your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method hide the labels. And it will automatically handle the spaces between UILabels
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
cell.questionLabel1.text = labelOneText[indexPath.row]
cell.questionLabel2.text = labelTwoText[indexPath.row]
cell.questionLabel3.text = labelThreeText[indexPath.row]
if labelOneText[indexPath.row] == "" {
cell.questionLabel1.isHidden = true
}
if labelTwoText[indexPath.row] == "" {
cell.questionLabel2.isHidden = true
}
if labelThreeText[indexPath.row] == "" {
cell.questionLabel3.isHidden = true
}
return cell
}
Final Output:
First I suggest you to set UITableViewCell Height to automatic dimensions . Attach all the children to one another and last child to uiview of xib . Now hiding view does not adjust size of cell so you need to play with height constraint of uiview you are hiding .
Make height constraint as strong in IBOutlet else it will crash since cells are re-using and constraint after setting once will become nil . You need to make sure that height constraint are change according to display cell requirement , thats mean for each cell maintain some datasource that decide to show or hide the view every time when cellforrowatIndexpath method called.
Hope this helps
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) ->CGFloat {
return UITableView.automaticDimension
}
now in cell, put all your views and there siblings which you want to hide/show in UIstackview (horizontal). now if you hide one view, it will be hidden and its apace will be also hidden to no white space will be showing, and no need to handle extra constraints. it will all handled by stackview.

Auto layout issue in TableView

I use dynamic height cells for my UITableView with this code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionsCell", for: indexPath) as! QuestionsCell
cell.label1.text = ""
cell.label2.text = ""
cell.label1.text = some text from array
cell.label2.text = some text from array
cell.label1.lineBreakMode = .byWordWrapping
cell.label1.numberOfLines = 0
cell.label1.sizeToFit()
return cell
}
Then I've pinned all constraints in Storyboard for each element (top, bottom, leading and trailing). On iPhone everything works fine, but on iPad when I scroll the UITableView this is happening (dynamic label height):
sizeToFit() only makes cell1 as big as it needs to be for the text.
You also need to use automatic height sizing for the table view cells.
In viewDidLoad,
self.tableView.rowHeight = UITableViewAutomaticDimension
You will also want to make sure you have a vertical constraint from your bold label to the "12 days ago" label, to ensure there is always vertical space between them, so they don't overlap.
you need to implement this delegate method:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
if this does not work then check if there is vertical spacing constraints between top textView and bottom textView.

Dynamic table view cell, change of subview height programmatically

I have a UITableViewCell with a custom view cell.
In this view cell, I have a simple UIView called imgWrapper where I added constraints as follows:
width = 50
height = 50
leading to superview = 20
top to superview = 20
bottom to superview = 20
Those are the only constraints in there. And I left the hugging and compression to the default ones.
In my code I've set this:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 90
}
Then in in my rowAtIndex...I have this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: LogCustomCell = tableView.dequeueReusableCellWithIdentifier("logCustomCell", forIndexPath: indexPath) as! LogCustomCell
var imgWrapperHeight: CGFloat = log.big ? 100 : 50
cell.imgWrapperHeight.frame.size.height = imgWrapperHeight
return cell
}
Once I compile and run it. All the cells are the same size.
Notes:
I checked if log.big was true/false and it does change.
I've also tried to do CGRect(x,y,width,height) but also didn't work.
I know I can do heightForRowAtIndexPath but I want to do animations and I know we can do something like this for labels (see printscreen) which makes the tableView know the height without defining it:
Any ideas what I'm doing wrong?
You need to put your height logic into the UITableViewDelegate method called tableView:heightForRowAtIndexPath, something like the following:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (true ? 100 : 50) + 2 * 20
}
PS. I've written this in Swift 2, thus the overridekeyword.
Delegate method "heightForRowAtIndexPath" will do it for you. You can know cell index for which you are returning height from indexPath.row and hence return height accordingly.
i.e
if indexPath.row == 0
{
return 70
}
else if indexPath.row == 1
{
return 100
}
add
self.automaticallyAdjustsScrollViewInsets = false
before these two lines
tableView.estimatedRowHeight = 50
tableView.cellHeight = UITableViewCellAutomaticDimension
Solutions 1:
As mentioned by the users. You can set the row height in your UITableViewController like so:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (true ? 100 : 50) + 2 * 20
}
Solution 2:
Set the height constraint on the element that will determine the height of cell. Then create an outlet in your VC for NSLayoutConstraint
While you are setting the content of the cell, you can do:
Ex:
#IBOutlet var imgWrapperHeightConstraint: NSLayoutConstraint!
...
override func tableView(tableView: UITableView, cellAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
imgWrapperHeightConstraint.constant = 50 // Or whatever value you want
}

Custom cell in UITableView shifts right after swipe to edit

I just want a simple UITableView with the ability to slide left to delete. Everything works okay except the right constraint on the textview in my prototype cell seems to get shifted after swiping to delete. Here's my code for the table:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//Uses prototype cell from Interface Builder called "CommentTableCell"
let tableCell = tableView.dequeueReusableCellWithIdentifier("CommentTableCell", forIndexPath: indexPath) as! CommentTableCell
tableCell.userInteractionEnabled = true
tableCell.selectionStyle = .None
//Sets the text for the cells in the comment table
tableCell.commentText.text = comments[indexPath.row]
tableCell.timeLabel.text = commentTimes[indexPath.row]
return tableCell
}
//As many rows in the table as there are comments
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
//Allows the user to delete comments
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
comments.removeAtIndex(indexPath.row)
commentsTable.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.layer.borderWidth = 0.75
self.view.layer.borderColor = borderColor.CGColor
self.view.layer.cornerRadius = 5.0
//Gets rid of the line between comment cells
commentsTable.separatorStyle = UITableViewCellSeparatorStyle.None
commentsTable.backgroundView = nil
//Sets the height of the row to fit text boxes
self.commentsTable.estimatedRowHeight = self.commentsTable.rowHeight
self.commentsTable.rowHeight = UITableViewAutomaticDimension
}
}
This is what it looks like after I've swiped left to edit and then swiped back right on the top cell (stackoverflow won't let me use pictures yet). Note the right sides of the gray text boxes and labels in each cell are no longer aligned. The gray text box in the cells has a right constraint of -8 so I'm also confused why there's any margin on the other cell's text boxes at all.
Thanks for any help you can give me, I'm still fairly new to Swift! I've tried to find anything like this question on stack overflow and I've come up empty.
Okay so I found a way to fix this and thought I'd post here in case anyone else runs into the same problem.
It still seems like a bug in XCode to me as I can't think of any time you might want the behavior described above. Basically, if the text box constraints in the prototype cell are set to "constrain to margins" in the Pin auto layout menu then the right horizontal constraint will be reset (as far as I can tell) randomly after you slide to delete and then slide back.
Just uncheck constrain to margins when you add those constraints and it should fix this problem!

Resources