Show only completely visible UITableViewCells - ios

I have a custom tableView, but the way it's designed makes the bottom and top part ugly when only half of the cell is visible. See picture for reference:
I want the bottom part (and the top after crolling) only visible when you can see 100% of the cell.
I tried this to check if the cells were completely visible, but I believe cellForRowAtIndexPath creates the reusableCells when it's partly visible and isn't called again when it's fully visible:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var myCell:ChooseStoryCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ChooseStoryCell
var cellRect = self.tableView.rectForRowAtIndexPath(indexPath)
var completelyVisible = CGRectContainsRect(self.tableView.bounds, cellRect)
if completelyVisible == true {
myCell.hidden = false
myCell.backgroundColor = Color.sharedColors().colorsArray[5]
myCell.storyLabel.text = stories[indexPath.row].name
myCell.circleView.layer.cornerRadius = 15
}
else{
myCell.hidden = true
}
How I would go forward with this?
Any help would be greatly appriciated!

Put your table view inside a parent UIView. A margin from top of tableview to top of parent view should be equal to what height of your cells is, same from bottom, the width of table view should be same as its parent's view, left and right margins equals to zero. So there is just extra space at top / bottom to display a cell.
On tableview set clipsToBounds to NO, on parent view make sure that clipsToBounds is set to YES.
The behavior should be like this, when scrolling the cell will be visible until it reaches the top boundary of that parent view and disappear at once.
Alternatively, you can also just reduce height of your tableview, move it down and set clipsToBounds to NO. It should do it. But I prefer to embed in View to be sure that nothing will be display outside.

Here is a code just came out of my mind. Not tested.
The basic idea is to see the intersection of the cell's frame and the table view's bounds and if the result is the cell's frame, then the cell frame is completely visible.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var myCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
var cellRect = tableView.rectForRowAtIndexPath(indexPath)
var completelyVisible = cellRect.rectByIntersecting(tableView.bounds) == cellRect
if completelyVisible == true {
myCell.contentView.hidden = false
}
else{
myCell.contentView.hidden = true
}
return myCell
}

Related

label having sizeToFits() goes out of bounds

I have a tableview in where each cell has a label. The datasource of this label is from the firebase api. Now, initially the label is loaded in a perfect form. As you scroll through and if any label is of a shorter text width, the rest of the cell, alter their labels to this size.
I even tried applying a stackview around it, but i couldn't help much
Below is the code of cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
if let cell = tableView.dequeueReusableCell(withIdentifier: "FeedCell", for: indexPath) as? FeedCell {
cell.caption.text = ""
cell.configureCell(post: post)
cell.caption.sizeToFit()
cell.delegate = self
return cell
} else {
return FeedCell()
}
}
code for function in TableviewCell
func configureCell(post: Posts, img: UIImage? = nil) {
self.posts = post
self.caption.text = posts.caption
}
I am really unable to fix this. Any help is much appreciated.
How about using auto-layout to set a minimum width in percentage of a screen size IOS.
Auto-Layout constraint has that multiplier parameter that lets you use a fractional relationship between a superview and its subview.
While both the child view (Label) and its superview are selected, add "equal width". Then change the "multiplier" of the constraint you just added to the proportion you need. For example, for 30%

dequeueReusableCell breaks cliptobounds

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.

Variable height UITableViewCell expands after scrolling to end of list and rotating

UPDATE
I know what is causing this strange breaking of layout. It is the setting of an Accessory (UITableViewCellAccessory). If I stop specifying an accessory the layout does not break. I have not added this as the answer because an answer would need a solution that gives me an accessory without breaking layout
Most of the issues I see people having with custom cells of dynamic height are that they do not have the correct height until they are rotated. However I see the opposite: All cells are the height valid for their dynamic content. Scrolling up and down does not break this. However if I scroll to the bottom of the list, then rotate the device, then rotate back one row will become between 0.5 and 1.5 times the height of the screen.
A further rotation or a further scroll will put the rows back to the expect height. I have included a couple of before and after screenshots
The UITableView is defines as follows
this.rootChildrenTable = new UITableView()
{
TranslatesAutoresizingMaskIntoConstraints = false,
AccessibilityIdentifier = "rootChildrenTable",
RowHeight = UITableView.AutomaticDimension,
EstimatedRowHeight = 44.0f,
BackgroundColor = UIColor.GroupTableViewBackgroundColor,
TableFooterView = new UIView(),
TableHeaderView = this.searchBar,
KeyboardDismissMode = UIScrollViewKeyboardDismissMode.OnDrag
};
Note the usual suspects are set RowHeight and EstimatedRowHeight. As soon as I remove the Lines = 0 from the label, making the rows all the same height, the issue goes away.
Any idea what else I should be looking at?
I had facing the same issue in my project. I had overcome that by the following way to set the height for the row.
//calculate the height by this way
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightForBasicCell(at: indexPath)
}
func heightForBasicCell(at indexPath: IndexPath) -> CGFloat {
var sizingCell: YourCell? = nil
var onceToken: dispatch_once_t
dispatch_once(onceToken, {() -> Void in
sizingCell = tableView.dequeueReusableCell(withIdentifier: YourCell.cellIdentifier())!
})
self.configureBasicCell(sizingCell, at: indexPath)
return self.calculateHeight(forConfiguredSizingCell: sizingCell)
}
func calculateHeight(forConfiguredSizingCell sizingCell: YourCell) -> CGFloat {
sizingCell!.bounds = CGRect(x: 0.0, y: 0.0, width: CGRectGetWidth(tableView.frame), height: CGRectGetHeight(sizingCell!.bounds))
sizingCell!.setNeedsLayout()
sizingCell!.layoutIfNeeded()
var size = sizingCell!.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
return size.height + 1.0
// Add 1.0f for the cell separator height
}
func configureBasicCell(_ cell: YourCell, at indexPath: IndexPath) {
//set your text here
cell(“text”)
}
Change code according to your need. I just converted objective C code to swift.

Change label height of custom cell within didSelectRowAtIndexPath

I'm trying to resize a label's height when my custom cell is selected (to allow it to expand so more text is visible).
I'm obviously missing something fundamental though because the frame is the exact same after I try to draw the new CGRect.
Here's the relevant code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow()
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! BasicCell
UIView.animateWithDuration(0.3) {
tableView.beginUpdates()
currentCell.subtitleLabel!.frame = CGRectMake(currentCell.subtitleLabel!.frame.origin.x, currentCell.subtitleLabel!.frame.origin.y, currentCell.subtitleLabel!.frame.size.width, 100)
currentCell.subtitleLabel!.numberOfLines = 0
tableView.endUpdates()
}
}
Any help would be appreciated.
You should use
-reloadRowsAtIndexPaths:withRowAnimation:
inside beginUpdate and endUpdate pair. Simply setting the frame is not enough for table view to know which cell needs to be updated.
Also there's no need to put your update code inside animation block. It's animated inherently.

Only set the value once when two table view cells have the mostly the same components

I have two prototype cells. Most of the UI are the same except one has a image view and the other one don't. The situation is that in cellForRowAtIndexPath, when I set the value for these cell's labels. I have to set them each time in two cells. Can I just set them once and only set the image for the cell only has a image view?
For example:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let place = places[indexPath.row] as Place
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("ImageCell", forIndexPath: indexPath) as ImageCell
cell.nameLabel = names[indexPath.row]
cell.screeNameLabel = screenNames[indexPath.row]
cell.createAtLable = place.createdAt.shortTimeAgoSinceNow()
cell.profileImageView.hnk_setImageFromURL(imageURL)
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("BaseCell", forIndexPath: indexPath) as BaseCell
cell.nameLabel = names[indexPath.row]
cell.screeNameLabel = screenNames[indexPath.row]
cell.createAtLable = place.createdAt.shortTimeAgoSinceNow()
return cell
}
}
As you can see, both cells have the same nameLabel, screeNameLabel, createAtLable. How do I only have to set them once? The above code is only a example. My current project, I have a lot of ui in the cells and they are basically the same except one has a image and one don't.
And by the way, what's the best way for this situation. When a cell contains most the same components, only a few components are different. Are using multiple prototype cells the best way?
You can solve it by auto layout. You can use the same cell prototype. What you need to do is set the height of image view to "greater than or equal to 0" if you want to let it expand according to different image height or "less than or equal to some constant if you want the image view height to be fixed. Since imageView has intrinsic content height and width it will expand or shrink according to what you load. The key is to make image view height 0 when there is no content.

Resources