UITableview reloadRows not working properly in Swift 3 - ios

I have created a UITableView with multiple sections consisting two cells with dynamic cell height. Added a button on second cell. On click of the button increasing the second cell height by changing the constraint height. Action method connected to the button is :
func increaseHeightOnClickOfButton(sender: UIButton) {
let indexPath = IndexPath(item: 1, section: sender.tag)
let cell = myTableView.cellForRow(at: indexPath) as! customCell
cell.viewHeightConstraint.constant = 100
self.myTableView.reloadRows(at: [indexPath], with: .automatic)
}
Issue: On clicking the button, cell height of second section increases rather than the first one.
I have also tried giving fix value of section like this :
let indexPath = IndexPath(item: 1, section: 0)
but it's not working.
It works only when I try this code:
func increaseHeightOnClickOfButton(sender: UIButton) {
let indexPath = IndexPath(item: 1, section: sender.tag)
let cell = myTableView.cellForRow(at: indexPath) as! customCell
cell.viewHeightConstraint.constant = 100
self.myTableView.reloadData()
}
But I don't want to reload the whole table view. Can anyone help me what I am doing wrong or is there any other approach to achieve it ?
Edit :
I did one more change, I removed :
self.myTableView.reloadRows(at: [indexPath], with: .automatic)
After clicking the button when I start scrolling the tableview, coming back to the same cell increases it's height. So I guess there is some issue with reloadRows.

You can use the following code to reload cell's height without reloading the whole data:
tableView.beginUpdates()
tableView.endUpdates()

Edit:
Modified first post to include a DataSource to track each row's height constraint constant.
Assuming your cell looks something like this:
you have added a UIView to the cell (orange view in my example)
added a label and button to the UIView
set leading, trailing, top and bottom constraints for the view
added a Height constraint to the view
connected IBOutlets and an IBAction for the button tap inside the cell class
set your table class for UITableViewAutomaticDimension
Then this should be a working example. Each time you tap a button, the Height constraint in that cell will be increased by 20-pts. No need to call reload anything...
class TypicalCell: UITableViewCell {
#IBOutlet weak var theLabel: UILabel!
#IBOutlet weak var viewHeightConstraint: NSLayoutConstraint!
var btnAction: (() -> ())?
#IBAction func didTap(_ sender: Any) {
btnAction?()
}
}
class TypicalTableViewController: UITableViewController {
// 2-D array of Row Heights
// 4 sections, with 4, 2, 6 and 3 rows
// all initialized to 40.0
var rowHeights: [[CGFloat]] = [
[40.0, 40.0, 40.0, 40.0],
[40.0, 40.0],
[40.0, 40.0, 40.0, 40.0, 40.0, 40.0],
[40.0, 40.0, 40.0]
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 40.0
}
override func numberOfSections(in tableView: UITableView) -> Int {
return rowHeights.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowHeights[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Typical", for: indexPath) as! TypicalCell
// Configure the cell...
cell.theLabel.text = "\(indexPath)"
// cells are reused, so set the cell's Height constraint every time
cell.viewHeightConstraint.constant = rowHeights[indexPath.section][indexPath.row]
// "call back" closure
cell.btnAction = {
// increment the value in our DataSource
self.rowHeights[indexPath.section][indexPath.row] += 20
// tell tableView it's being updated
tableView.beginUpdates()
// set the cell's Height constraint to the incremented value
cell.viewHeightConstraint.constant = self.rowHeights[indexPath.section][indexPath.row]
// tell tableView we're done updating - this will trigger an auto-layout pass
tableView.endUpdates()
}
return cell
}
}

Try reloading the Section:
self.tableView.reloadSections(IndexSet(integer: sender.tag), with: .automatic)

Related

Swift 4 Table View Getting Wrong Cell

I am having problems selecting the right cells within my table view. My code is:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var MyTableView: UITableView
var cellsArray = [ "0" , "1" , "2" , "3" , "4" , "5" , "6" , "7"]
override func viewDidLoad() {
super.viewDidLoad()
MyTableView.layer.borderWidth = 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyTableView.dequeueReusableCell(withIdentifier: "cells", for: indexPath)
cell.textLabel?.text = cellsArray[indexPath.row]
return cell
}
#IBAction func MyButtonClick(_ sender: Any) {
let myIndexPath = IndexPath(row: 4 ,section: 0)
let cell = MyTableView.cellForRow(at: myIndexPath)
cell?.backgroundColor = UIColor.yellow
}
This is the application when it's running
The problem is when I press the button without scrolling the tableview nothing happens, but if I scroll down so the current view includes cells labeled 4/5/6 and press the button both cells labeled "4" and "0" have their background colors set to yellow.
I would ultimately like to know why this is the case since it's been effecting more than just background, like when doing a for loop to sum the cell heights to auto change the height of the tableview, the cells not in view crash the program as it's returning null.
Any help would be greatly appreciated on why is this!
After button press when 4 is in view
Cells are being reused - you can only get reference to the visible cells.
let myIndexPath = IndexPath(row: 4 ,section: 0)
let cell = MyTableView.cellForRow(at: myIndexPath)
cell?.backgroundColor = UIColor.yellow
cell in your case is nil when row number 4 isn't visible. If you wanna change behaviour in the cells you should modify the model and call for example reloadData on your UITableView.

Delete Rows for Dynamically Adjusted Table View?

I am trying to delete a row in a tableview. When the row is deleted, the tableview should adjust, so that there is not a blank row in the tableview. This is what it looks like before any rows are deleted:
This is what it looks like when a row is about to be deleted:
This is what it looks like after a row has been deleted:
There should not be that extra blank row after the fourth row is deleted. How do I get rid of this extra row?
Here is my code currently. As you can see, the size of the tableview is adjusted dynamically. The overall size of the tableview is different based on whether there are 4 items (as shown in the first image) or whether there are 6 items. In both cases, before any deletions occur, there are no extra rows in the table (so there are only 4 rows total in the first case, and only 6 rows total in the second case). In addition, the button is supposed to be 80 pixels below the ending of the tableview. When a row is deleted, the button moves correctly, as you can see that the button has moved up from images 2 to 3. However, it still looks like there is an extra row in the tableview.
#IBOutlet var tableView: UITableView!
#IBOutlet var newButton: UIButton!
var items: [String] = ["Swift", "Is", "So", "Amazing"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "Cell") as! UITableViewCell
// Make sure the table view cell separator spans the whole width
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
override func viewWillAppear(_ animated: Bool) {
// Adjust the height of the tableview
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
// Add a border to the tableView
tableView.layer.borderWidth = 1
tableView.layer.borderColor = UIColor.black.cgColor
}
// This function is used for adjusting the height of the tableview
override func viewDidLayoutSubviews(){
tableView.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: tableView.contentSize.height)
tableView.reloadData()
//Get the current height of the tableview
var tableViewHeight = self.tableView.contentSize.height
var tableViewEnding = 134 + tableViewHeight
var buttonPlacement = tableViewEnding + 80
// The New Button is 80 points below the ending of the tableView
newButton.frame.origin.y = buttonPlacement
print("Table View Height: \(tableViewHeight)")
}
// Allow cell deletion in tableview
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
// delete item at indexPath
self.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
self.viewDidLayoutSubviews()
print(self.items)
print("Number of rows: \(tableView.numberOfRows(inSection: 0))")
}
delete.backgroundColor = UIColor.blue
return [delete]
}
Just add following in view did load method
tableView.tableFooterView = UIView()
Now there will be no extra empty rows.
Next step is
Add Height constraint to tableview and take IBOutlet of it.
After any updates on tableview set constraints's constant value with tableview's contentSize.height
Note: If your cell has some heavy task to do then setting constant next to the reload data may not work properly in that case you may try viewDidLayoutSubviews methdo
Hope it is helpful

TableView Auto Scrolling misbehaving after adding cells

View Setup:
My TableView has 3 sections with 4 or 9 cell each. Each Cell has a Label and TextField.
On Starting to edit a cell at index 2 of each section, I reload the section which will now consist of 9 cells(update model to dequeueCell so that 5 more cells will be added).
Problem:
The tableView scrolls as expected(brings textfield to visible part of the screen) for the unexpanded state of the section. But after I add cells by beginning to edit the textfield of cell at index 2 of any section, the tableView scrolls such that it hides the textfield. The weird scrolling occurs for any cells in the tableview once any section has expanded numbers of cells. Also, while weird scroll is happening, the tableView is reloaded(which is leading to lose the focus away from textfield). I have included tableView.reloadSection(_:) in the didBeginEditing(:_) custom delegate of the cell.
I have seen this problem in iOS 9 and 10
Sorry for poor explanation. Thanks
Heres the Github Repo
And Problem is here
P.S. I am using Swift 3 and Xcode 8.3.3 with deployment target iOS 10
Please do not suggest answer in Swift 4 and Xcode 9
You can try another approach: change the height of cells instead of insert / delete.
Change number of cells to always return all items:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let sectionEnum = Sections(rawValue: section) else { return 0 }
return sectionEnum.getRows(forExpanded: true).count
}
Set height of 'hidden' items to 0:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
guard let sectionEnum = Sections(rawValue: indexPath.section) else { return 0 }
let isExpanded = expandedSectionData[indexPath.section]
if (!isExpanded) {
let object = sectionEnum.getRows(forExpanded: true)[indexPath.row]
if (!sectionEnum.getRows(forExpanded: false).contains(object)) {
return 0;
}
}
return self.tableView.estimatedRowHeight
}
Set cell to clip subviews to its bounds:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
....
cell.clipsToBounds = true;
return cell
}
And change updating code to (remove tableView.reloadSections, change indexPath):
func didBeginEditing(textField: UITextField, cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell), let section = Sections(rawValue: indexPath.section) else { return }
if indexPath.row == 7 && !expandedSectionData[indexPath.section] {
expandedSectionData[indexPath.section] = true
tableView.beginUpdates()
tableView.endUpdates()
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.none, animated: true)
textField.becomeFirstResponder()
}
}
You need to make textfield as first responder again, after reloading section text field no longer remains first responder.
You might need to change something like -
func didBeginEditing(textField: UITextField, cell: UITableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
if indexPath.row == 2 && !expandedSectionData[indexPath.section] {
tableView.beginUpdates()
expandedSectionData[indexPath.section] = true
tableView.reloadSections(IndexSet(integer: indexPath.section), with: .automatic)
tableView.endUpdates()
// after tableview is reloaded, get cell again
let cell = tableView.cellForRow(at: IndexPath(row: 2, section: indexPath.section)) as? TestCell
cell?.textField.becomeFirstResponder()
}
}
I have tried running this, kind of looks fine to me.
This issue has to do with your use of self-sizing tableview cells. To fix the issue, comment out these two lines in your viewDidLoad and consider defining the height of your cells with tableView:heightForRowAtIndexPath:.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
Since the self-sizing tableview documentation states,
To define the cell’s height, you need an unbroken chain of constraints
and views (with defined heights) to fill the area between the content
view’s top edge and its bottom edge
I also tried changing the bottomMargin = textField.bottom constraint from priority 750 to 1000, but this did not fix the issue.

Tableview row, changing last cell changes the first

I'm having a lot of trouble with cell constraint modifications. I have a tableview with a nested cell inside of it and a button.
The bottom more button has a constraint to share the bottom of the cell. There is also a view hiding behind it with a height constraint, you can see it peeking out the bottom.
Basically if I click the the two constraints are changed and the cell is expanded.
TableViewCell:
#IBAction func expandPress(sender: AnyObject) {
if expandButtonBottomConstraint.active {
self.expandButtonBottomConstraint.active = false
self.descriptionTextHeightConstraint.constant =
self.descriptionTextHeightConstraint.constant * 10
self.tableView.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: buttonIndex, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
TableViewController
override func tableView(tableview: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let eventCell = tableView.dequeueReusableCellWithIdentifier("EventTableViewCell") as! EventTableViewCell
...
eventCell.buttonIndex = indexPath.row
....
self.tableView.layoutIfNeeded()
}
override func viewDidAppear(animated: Bool) {
self.tableView.reloadData()
}
If you click on the first event it expands, but if you click on on the third one, the third and the first expands. The index row is correctly the right row. How can I only expand a particular row consistently?

tableview's Row height is not changing , CollectionView is capturing all the unused space instead of resizing itself in the tableViewCell

I put a UICollectionView into the UITableViewCell by following this tutorial and in my UICollectionViewCell, there's a Image View. So when I run my app, the collection view is not resizing itself at the same time in my cell I put a Text View which is resizing itself according to content, see the below images:
In this first image, I have a text view at the top which have some text in it, and below it with (pink background) is my collection view and inside of that with greenBackground is my image view, as you can see that collection view is taking the extra space instead of reducing itself as Text View Did.
in this second image you can see that my textView haves more content then before so its resized itself now overlapping the CollectionView
this is my TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var txtView: UITextView!
#IBOutlet private weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// collectionView.frame = self.bounds;
// collectionView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollectionViewDataSourceDelegate
<D: protocol<UICollectionViewDataSource, UICollectionViewDelegate>>
(dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.tag = row
collectionView.reloadData()
}
var collectionViewOffset: CGFloat {
get {
return collectionView.contentOffset.x
}
set {
collectionView.contentOffset.x = newValue
}
}
}
this is my collectionViewCell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet var imgView: UIImageView!
override func awakeFromNib() {
self.setNeedsLayout()
//
// self.contentView.frame = self.bounds;
// self.contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
}
}
and this is my TableviewController
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return imageModel.count
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath) as! TableViewCell
cell.txtView.text = txtArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
tableViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
override func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? TableViewCell else { return }
storedOffsets[indexPath.row] = tableViewCell.collectionViewOffset
}
}
extension TableViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return imageModel[collectionView.tag].count
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell",
forIndexPath: indexPath) as! CollectionViewCell
cell.imgView.frame = CGRectMake(0, 0, collectionView.frame.width, collectionView.frame.height)
cell.imgView.contentMode = .ScaleAspectFit
//cell.addSubview(imageView)
cell.imgView.image = ResizeImage(UIImage(named: imageModel[collectionView.tag][indexPath.item])!, targetSize: CGSizeMake( cell.imgView.frame.width , cell.imgView.frame.height))
//imageView.image = UIImage(named: imageModel[collectionView.tag][indexPath.item])
return cell
}
}
How can I make this collection view to AutoLayout itself according to the content in it? I also tried this:
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 100;
but didn't worked (my collection view got disappear) if anybody knows how to do this, then please guide me..
I faced a similar issue when i used collection view inside a table view cell. No matter what i did i couldn't get the table view to resize automatically but the collection view did. Soo instead of autolayout i did it using code.
I ended up having to calculate the size of the label in the collection view numberOfSections in collection view and passing this height using a delegate to the view controller that has the tableView's delegate and dataSource and reloading the appropriate row of the table view.
As it happens, the numberOfSections in collectionview data source gets called everytime and the delegate resizes the table view height.
Some thing like this-
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
[self.delegate setViewHeight:[self getHeightForCellAtIndexPath:[NSIndexPath indexPathForRow:currentSelected inSection:0]]];
return 1;
}
This ought to give you a general idea.
EDIT: Sorry i misunderstood, your question before. Here is something that should work for you:
As per my understanding, you have a table view cell with a label view and collection view inside of it.
Now, inside your table view cell, you should add top, leading and trailing constraints space to the label. Now inside your collection view position your image vertically in the center and add an appropriate top and bottom to the cell superview. Your collection view itself should have a CONSTANT value in leading, trailing, top to label and bottom to superview. Also add a fixed height constraint to your collection View (assuming you want the image sizes to remain the same).
Now lets says View Controller A is the data source for your table view and the table view cell is the data source for your collection view.
In your viewController A, you should write your height for row at indexPath as-
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize stringSize = [yourStringArray[indexPath.row] boundingRectWithSize:CGSizeMake(_yourCollectionView.frame.size.width, FLT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:[UIFont yourFont size:yourFontSize]} context:nil].
return stringSize.height + 35; //magic number 35, play around with it to suit your need. Did this to always have a minimum fixed height.
}
This will allow your tableViewRowForHeight for that particular index to have the height of your label added to it and the constraints ought to do the rest.

Resources