How does changing the intrinsicContentSize of a UITableViewCell work? - swift

I made a UITableView with cells that expand when you tap on them. It is modeled off the following project: https://github.com/rushisangani/TableViewCellExpand
Basically, the cell's container view has two subviews which act as containers for the expanded/contracted states - "front" and "back", each of which is constrained to the top, bottom, leading, and trailing edges of the cell's main content view. To make the cell expand or contract, I just toggle the isActive property on the bottom constraint of the front and back views. This works, but only if I reload the cell when it is tapped. If I just change the constraints and then try to call invalidateIntrinsicContentSize(), nothing happens.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Expand/contract the cell and invalidate size (doesn't work)
// let cell = tableView.cellForRow(at: indexPath) as! ExpandingCell
// cell.tap()
// cell.invalidateIntrinsicContentSize()
// Keep track of the selected index and configure the expand/contract state when the cell is remade
tableView.deselectRow(at: indexPath, animated: false)
expandedIndexPath = indexPath
if(!expandedIndexPathArray.contains(indexPath)){
expandedIndexPathArray.append(indexPath)
}
else{
expandedIndexPathArray = expandedIndexPathArray.filter({$0 != indexPath})
}
// Whenever a cell's intrinsicContentSize changes, it must be reloaded
tableView.reloadRows(at: [indexPath], with: .none)
}
What's going on behind the scenes? Why can't the cell recalculate its size without being reloaded?

If you want to contract/expand cells based on their intrinsic content size, you need to do the following 3 steps:
Configure the table view:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
For static table view controller, you will need to implement heightForRowAtIndexPath and return UITableViewAutomaticDimension.
Update cell constraints to reflect contracted/expanded state
Ask the table view to reflect changes:
tableView.beginUpdates()
tableView.endUpdates()

Related

Check table view cell fully visible

how are you
I need to know how can i determine table view cell fully visible for playing auto play movie in cell and also detect hiding the table view cell.
I have apply this code below in table view but not give me the correct solution.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cellRect = tableView.rectForRow(at: indexPath)
let isFullyVisible = tableView.bounds.contains(rectInFull)
if isFullyVisible {
// Play video
}
}
So please can you tell me how can i get the correct table view cell visible
cell.frame.origin.y >= tableview.contentOffset.y && cell.frame.origin.y + cell.frame.size.height <= tableview.contentOffset.y + tableview.bounds.size.height
You might want to implement the UIScrollViewDelegate method scrollViewDidScroll(_:) and in there you could do the check for visible cells in your table view:
func scrollViewDidScroll(_ scrollView: UIScollView) {
guard let tv = scrollView as? UITableView else { return }
tv.visibleCells.forEach { $0.playVideo() }
}
Of course assuming that method on the cell (playVideo()) exists and it takes care of being called multiple times when the video is already playing (i.e. must ignore the call if is already playing).
Otherwise if you need the indexPaths of visible cells or any fine tuning use either the property indexPathsForVisibleRows or the method indexPathForRows(in:) on the table view, then those index paths you'll optionally obtain inside an array will point to the model's objects (which might implement the play video logic).

Setting up different layouts for one cell, depending on its content

In an UITableViewCell, I have an UIView container, which contains a UIImageView on top and an UILabel on the bottom. When the UILabel does not contain text, I want to remove the UILabel and resize the UImageView to the bottom of my UIView container.
In my TableViewCell, I have this:
let x = imageViewPostPreview.bottomAnchor.constraint(equalTo: viewPreviewOverlay.topAnchor, constant: 0)
if postText == "" {
x.isActive = true
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
self.layoutSubviews()
self.layoutIfNeeded()
} else if postText != "" {
x.isActive = false
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
self.layoutSubviews()
self.layoutIfNeeded()
}
}
This piece of code works on when the initial UITableViewCell is loaded, but when I scroll my UITableView, the cell loses the initial layout.
How do I setup the layout correctly, so the layout is kept on scroll?
Edit:
Initial cell, which is correct:
After scrolling, incorrect layout:
Add this to prepareToReuse method
override func prepareForReuse() {
super.prepareForReuse()
let x = imageViewPostPreview.bottomAnchor.constraint(equalTo: viewPreviewOverlay.topAnchor, constant: 0)
if postText.isEmpty {
x.isActive = true
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]
} else {
x.isActive = false
imageViewPostPreview.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
}
self.layoutIfNeeded()
}
Note: Dont call layoutSubview directly ... always try to call layoutifNeeded or setNeeedsLayout
A way you can have the cell calculate its size automatically, is to have constraints set inside the cell view for all directions on vertical axis. So, in your case the image view will have top, leading and trailing set to superview, and bottom to the label. The label will have leading and bottom to superview, and the top will be to the image view you set. It's important to set for vertical axis, since for table view we need it to calculate the height. This way the table view will know what to expect and can set height automatically.
After that you set tableView.rowHeight = UITableView.automaticDimension. Or you can also set in the delegate method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
After all this is in place, you will only need to give the right values to the cell subviews(in this case set image and text). You can do this inside cell for row function. A sample code will look something like this.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
let model = self.models[indexPath.row]
cell.imageViewPostPreview.image = model.image ?? nil
cell.postText.text = model.text ?? nil
return cell
}
This should work without any further need to calculate constraints and calling layouts to update. Hope this helps! You can also check this question.

Setting the height: TableViewCells with custom Nib that are different sizes

I have a TableViewController that has a custom TableViewCell under the identifier "customCell". Heres an image of the configuration of the cell along with the IBOulets connected to it:
The cell takes information from my backend and presents it. The description text view (just noticed that I accidentally named it descriptionLabel) doesn't allow scrolling so it expands based off of the content that it's holding. The database is being processed correctly from the database, and it's displaying on the app. The only problem is that the cell is not its correct height. On the TableViewControl that's registering this cell through its identifier, I automatically set the height of the cell using UITableViewAutomaticDimension in heightForRow:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
but that's not working either. *The only thing that works is when I set the height of each cell to a standard value such as 200. That doesn't work for me because each cell will be a different height because of the length of the textView.
How do I set the height of a custom nib (tableViewCell) so that it adjusts based off of the content within it instead of setting the height to a specific value?
1-Remove both textViews and replace them with labels
2- Title lbl with theses constraints
top,leading,trailing , .lines = 0
3- Description lbl with theses constraints
bottom ,leading,trailing to contentView,top to bottom of title lbl , .lines = 0
Off course you can leave the 2 textviews , but you have to give each one an initial height and do this in
override func layoutSubviews() {
super.layoutSubviews()
self.titleTvHeight.constant = self.titleTv.contentSize.height
self.desTVheight.constant = self.desTv.contentSize.height
}
//
Don't forget to set this in viewDidLoad of the VC
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableViewAutomaticDimension
Anyway you can remove heightForRowAt

Returning to the same scroll position of UICollectionView after tapping a cell

I have a UICollectionView that is populated with all photos from the devices photo library. After a cell (photo) is tapped, it segues to a view controller that allows for editing. On this view, there is an "Add Photo" button to return the user back to the UICollectionView (to select another photo). I need for the scroll position to "focus" the previous tapped cell in the center of the view without any animations or jumping.
I have tried saving the tapped indexPath as a variable, then on viewDidAppear, scroll to that indexPath with scrollToItemAtIndexPath. The problem is I can't figure out how to update a variable (to save indexPath) on cell tap. I tried this in didSelectItemAtIndexPath, but the value never actually saves.
var cellTappedIndexPath = Int()
Inside didSelectItemAtIndexPath:
cellTappedIndexPath = indexPath.row
The value for cellTappedIndexPath never saves.
Just for testing out scrollToItemAtIndexPath, I have added the following to viewDidAppear:
customViewCollectionView.scrollToItemAtIndexPath(NSIndexPath(forItem: 25, inSection: 0), atScrollPosition: UICollectionViewScrollPosition.CenteredVertically, animated: false)
// 25 is just a number I have set for testing. Ultimately, I would like this to be the saved indexPath of the last tapped cell.
This causes the collectionView to "jump" to cell 25 once it's fully loaded. If I set animated to true, it loads at the top, then scrolls down to cell 25. Not my desired result.
I just want to be able to do 2 things here.
1 - Save the cell tapped as a variable.
2 - use scrollToItemAtIndexPath (with the variable in #1) so the view just loads up instantly with the last cell tapped right into the middle, No animations or anything.
Let me know if further clarification is needed. THANKS!
You could save the selected indexPathreceived when the collectionview cell is tapped and use it when required.
var selectedIndexPath: NSIndexPath?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
selectedIndexPath = indexPath
}
func scrollToCell(){
if let index = selectedIndexPath{
customViewCollectionView.scrollToItemAtIndexPath(index, atScrollPosition: .CenteredVertically, animated: false)
}else{
print("A cell hasnt been selected yet")
}
}

iOS8 Swift - Problems dynamically resizing tableview cells

Here's what my application looks like currently.
You'll see that when the subtitle text label is only one line is resizes correctly, but when there's multiple lines it gets all messed up. I think this has to do with the constraints possibly? Right now I'm using the auto-layout constraints. You can see them in the screenshot. Here's the code that creates my cells.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> DealCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Deal Cell", forIndexPath: indexPath) as DealCell
let currentBar = bars[indexPath.row] as BarAnnotation
cell.barName.text = currentBar.name
cell.deal.text = currentBar.deal
cell.distanceToBar.text = String(format: "%.3f mi", currentBar.distance)
// Set the height of the table view cells
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
return cell
}
Any ideas?
Use tableView:heightForRowAtIndexPath: function in the UITableViewDelegate.
Using this you can individually set the height of each row separately.
For more information : https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/index.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
Or you can use auto layout settings like in this article.
http://useyourloaf.com/blog/2014/02/14/table-view-cells-with-varying-row-heights.html