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!
Related
I'm trying to add check marks to my UItableview cells. I want to checkmarks to display like circle buttons on the left margin like this image:
I am unable to get the checkmarks in the left margin with my code and instead I keep getting checkmarks on the right that display only when tapped. Here is my current code:
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = .None
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
cell.accessoryType = .Checkmark
}
}
Any idea on how i can get the checkmarks to display on the left like the image?
I've made for you whole sample application for this.
just try this.
And this is what it looks like.
I've got a very simple UIScrollView with some content (many subviews). This scroll view is used to show some posts made by users (image + text). One of these views is actually the image of the author and it overflows bottom cell bounds. It is thus overlapped with the cell coming after, and using clipToBounds = false I'm able to obtain the desired result. Everything works great if I scroll down. When I start to scroll back up the view that previously was overlying now gets clipped.
Cell overlapping working fine
Cell overlapping not working (when I scroll up)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = (indexPath.row % 2 == 0) ? "FeedCellLeft" : "FeedCellRight";
let cell = feedScrollView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! FeedCell;
self.setUpCell(cell, atIndexPath: indexPath);
return cell
}
the setUpCell function simply perform some UI related tasks
let row = indexPath.row
cell.postImage.downloadImageFrom(link: rows[row].image, contentMode: .scaleToFill)
cell.postAuthorImage.downloadImageFrom(link: "https://pbs.twimg.com/profile_images/691867591154012160/oaq0n2zy.jpg", contentMode: .scaleToFill)
cell.postAuthorImage.layer.cornerRadius = 22.0;
cell.postAuthorImage.layer.borderColor = UIColor.white.cgColor
cell.postAuthorImage.layer.borderWidth = 2.0;
cell.postAuthorImage.layer.masksToBounds = true;
cell.selectionStyle = .none
cell.postData.layer.cornerRadius = 10.0;
cell.contentView.superview?.clipsToBounds = false;
cell.clipsToBounds = false;
if (indexPath.row % 2 != 0) {
cell.postData.transform = CGAffineTransform.init(rotationAngle: (4 * .pi) / 180);
} else {
cell.postData.transform = CGAffineTransform.init(rotationAngle: (-4 * .pi) / 180);
}
It seems that the deque operation breaks the layout I've made (using autolayout). I've tried many solution like this
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.contentView.superview?.clipsToBounds = false;
cell.clipsToBounds = false;
cell.contentView.clipsToBounds = false;
}
But the results looks always the same. The height of every row is fixed.
I think the issue is with the hierarchy of subviews. When you scroll down, you cells dequeued from top to bottom and added to UITableView in the same order and all looks fine. Because the previous cell is above the following in view hierarchy.
But when you scroll up, cells are dequeued from bottom to top and it means that the cell on top is "behind" the previous cell. You can easily check it with Debugging View Hierarchies feature for Xcode.
You can try to bringSubviewToFront: for example:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.superview.bringSubview(toFront cell)
}
Updated version
I have made small research in Playgrounds and found only one reasonable option to implement overlapping cells without huge performance issues. The solution is based on cell.layer.zPosition property and works fine (at least in my Playground). I updated the code inside willDisplay cell: with the following one:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.layer.zPosition = (CGFloat)(tableView.numberOfRows(inSection: 0) - indexPath.row)
}
According to the documentation for .zPosition (Apple Developer Documentation):
The default value of this property is 0. Changing the value of this property changes the the front-to-back ordering of layers onscreen. Higher values place this layer visually closer to the viewer than layers with lower values. This can affect the visibility of layers whose frame rectangles overlap.
So I use current dataSource counter as minuend and indexPath.row of the current cell as subtrahend to calculate zPosition of the layer for each cell.
You can download full version of my playground here.
I have strange behaviour. My whole view controller looks like this (vertical order):
UILabel,
UITableView,
UIView (that will slide in&out when needed). [see p.s.]
I added constraint between UIView and bottom layout guide and I'm animating it with this method:
func toggleContinueButton(_ toggleOn: Bool) {
if toggleOn {
self.buttonViewConstraint.constant = 0
} else {
self.buttonViewConstraint.constant = -80
}
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
For some strange reason this screws checkmark, which is now "flying" from outside of left margin of screen and right into correct and expected position, on the right side of selected cell. Belowe I have simple code for selecting/deselecting:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)!.accessoryType = .checkmark
self.updateContinueButtonState()
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)!.accessoryType = .none
self.updateContinueButtonState()
}
Has anybody ever met such weirdness?
p.s. tried two versions: first, where sliding UIView is hovering in&out on top of tableView, and second where sliding in UIView is shrinking tableView's height. None worked.
Its hard to say, but you might try calling layoutIfNeeded first, then changing the constraint, then calling it again inside the animation block. That gives other layout changes time to update first.
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
}
I have a UITableView with a custom cell, which has a few labels in it that dynamically decide the height of the cell. When I tap on one cell and segue to a new view controller, upon returning all the formatting for the cells is completely messed up, and I can't figure out what is causing it.
Here is what the cells normally look like:
And I have some pretty basic constraints set on them. The top label is pinned to the top and left margins, and must always be >= 20 from the right. The other labels are aligned to the left of this first label, with vertical spacing set between all of them. The middle label has a right spacing constraint to the margin, and the bottom labels are aligned to the baseline of the first and have horizontal spacing between all of them.
When I segue back to this table view it looks like this however:
I can't figure out what is causing it to layout differently than when I left. If I scroll around it seems to "reset" them back to what they should be, but on initial load they're really messed up. I can attach the project if desired, but there's really not much outside of the Storyboard.
cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTableViewCell
let object = objects[indexPath.row]
cell.title1.text = object.name
cell.title2.text = object.color
cell.title3.text = object.roar
return cell
}
Sample project: http://cl.ly/040L2z0q0V2d
It appears that the table view cells aren't resizing based on the contents when returning from the segue. Using the sample project, I threw a reload data in the viewWillAppear and that seemed to fix the issue.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
}
There are actually couple of issues with your project.
Data Loading and AutoLayout.
The first one is causing a strange behavior at the time of drawing the cells with data. When unwinding from the segue you'll see those additional cells on top of your table caused by ambiguous layout calculation.
Solution: Move the data into override func viewWillAppear(animated: Bool) { and perform a tableView.reloadData() (as correctly suggested by #rFessler).
On the other hand, Autolayout is a kind of fiery beast. Tamable. It's worth investigating the topic further. I wasn't able to make your layout work with autosizing cell height but I'll leave few references and the project for you.
References:
http://www.appcoda.com/self-sizing-cells/
http://captechconsulting.com/blog/tyler-tillage/ios-8-tutorial-series-auto-sizing-table-cells
Project:
http://cl.ly/3z3a2Z3a3U2K
I've had a similar problem myself. I downloaded your project and it seems I've solved it by removing and tweaking some constraints. This is how my constraints look now:
Also I've added this to viewDidLoad:
self.tableView.estimatedRowHeight = 120
self.tableView.rowHeight = UITableViewAutomaticDimension
I also added this to test delete:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete
{
self.objects.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
Now you can even rotate the device and remove rows and it's all working splendid!
However, there's still problem if you push this view on a Navigation Controller (Which is what my problem was about in the beginning). See my storyboard below to get some funky labels:
To solve this, it seems we actually have to do a hack! (Damn you apple, what is going on with this?!)
var firstAppearance=true
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if firstAppearance
{
if let indexPaths = self.tableView.indexPathsForVisibleRows()
{
self.tableView.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
self.firstAppearance = false
}
}
}
At the moment, I think this is as good as it gets.
I played with this and find a simple solution, add this seems to fix the problem.
override func viewWillDisappear(animated:Bool) {
super.viewWillDisappear(animated)
self.tableView.estimatedRowHeight = 166.0
}
Since the the method tableView:estimatedHeightForRowAtIndexPath will be called every time you segue to a new MVC, and change the autolayout, you can just do
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
to reuse the autolayout