TableView Auto Scrolling misbehaving after adding cells - ios

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.

Related

Two tableviews in one view controller and height of tableviews should be equal to its content

I am a new in-app ios development. I am trying to get a UI like this: -
Here two types of tables are attached with one table view. This design in andriod.
I tried in IOS and take one table view with custom header and in the header I set another table view. Like this:-
And set code like this : -
override func viewWillLayoutSubviews() {
secondview?.frame.size = CGSize(width: tableViews.frame.width, height: secondtableViews.contentSize.height + 50 + tableViews.contentSize.height)
secondview.layer.masksToBounds = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == tableViews {
debugPrint(" first")
return 50
}else{
debugPrint(" second")
return 50
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == tableViews {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.textLabel?.text = "skdjfhkjfhjkshfkhsdkfhjsd"
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: "gcell")! as UITableViewCell
cell.textLabel?.text = "skdjfhkjfh jkshfkhsdkfhjsd"
return cell
}
return UITableViewCell()
}
I change the height of main table view. It workes on some part height do not work according to the content of table view and the data of first table showing on second and second is showing on first. Can any one help me out from this design issue.
you should go with one tableview with TWO sections and TWO different type of cell...!

UITableView Scrolls to top after insert row

I have an expandable UITableView. When sections tapped, they expand or collapse with animation (Scroll). My problem is that there is a weird animation when expanding or collapsing headers. UITableView scrolls to top and then goes to the tapped cell. In addition, when there is no expanded cell, sometimes, It doesn't scroll to top and there is a big space between top header and top view of UITableView.
My problem is that I need to scroll to expanded section and also get rid of scroll to top bug.
I tried below solution but didn't work for me:
prevent table view to scrolling top after insertRows
It also looks like same problem with below question, but can't figure out how to implement it.
Why does my UITableView "jump" when inserting or removing a row?
How I toggle selection:
func toggleSection(header: DistrictTableViewHeader, section: Int) {
print("Trying to expand and close section...")
// Close the section first by deleting the rows
var indexPaths = [IndexPath]()
for row in self.cities[section].districts.indices {
print(0, row)
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
let isExpanded = self.cities[section].isExpanded
if(isExpanded){
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Collapse Click")
}else{
AnalyticsManager.instance.logPageEvent(screenName: analyticsName!, category: "Button", action: Actions.interaction, label: "\(self.cities[section].name) Expand Click")
}
self.cities[section].isExpanded = !isExpanded
// This call opens CATransaction context
CATransaction.begin()
// This call begins tableView updates (not really needed if you only make one insertion call, or one deletion call, but in this example we do both)
tableView.beginUpdates()
if isExpanded {
tableView.deleteRows(at: indexPaths, with: .automatic)
} else {
tableView.insertRows(at: indexPaths, with: .automatic)
}
// completionBlock will be called after rows insertion/deletion animation is done
CATransaction.setCompletionBlock({
// This call will scroll tableView to the top of the 'section' ('section' should have value of the folded/unfolded section's index)
if !isExpanded{
self.tableView.scrollToRow(at: IndexPath(row: NSNotFound, section: section) /* you can pass NSNotFound to scroll to the top of the section even if that section has 0 rows */, at: .top, animated: true)
}
})
if self.scrollToTop(){
self.tableView.setContentOffset(.zero, animated: true)
}
// End table view updates
tableView.endUpdates()
// Close CATransaction context
CATransaction.commit()
}
private func scrollToTop() -> Bool{
for sec in self.cities{
if(sec.isExpanded){
return false
}
}
return true
}
I'm giving height of cell inside;
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
How I declare headers;
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = DistrictTableViewHeader()
header.isColapsed = !self.cities[section].isExpanded
header.customInit(title: self.cities[section].name, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
EDIT: Solution in this question (Setting estimated height to 0) looks like working when inserting row. However, I still have bug when deleting rows. Bottom header goes to center of tableview and then goes to bottom after collapse header.
iOS 11 Floating TableView Header
You can try using below code. Just get the last content offset of your tableview. Then do the update and reassign the content offset.
let lastOffset = tableView.contentOffset
self.tableView.beginUpdates()
self.tableView.layer.removeAllAnimations()
self.tableView.endUpdates()
tableView.contentOffset = lastOffset
Instead of tableView.beginUpdates() and tableView.endUpdates(), In my Code i'm using tableView.reloadData() for expanding and contracting the particular section, You can call reloadData when you need to provide expansion of section.This results that you don't have the problem of animation scroll to the top. And works fine in my project where I have to show number of rows in particular section on a click of button which includes in that section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
}
// Ignore the If Else statements it's just when do you need expansion of section.
else {
if showMore == true {
return self.userPoints.rewardsData[section - 1].count - 1
}
else {
return 0
}
}
}
Also Don't Forget to increase or decrease the number of rows to that particular section accordingly.Previous line is important to avoid any crash.
Simple Solution swift3 and Above
use below extension as
eg: tableViewOutlet.tableViewScrollToBottom(animated: true)
extension UITableView {
func tableViewScrollToBottom(animated: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
let numberOfSections = self.numberOfSections
let numberOfRows = self.numberOfRows(inSection: numberOfSections-1)
if numberOfRows > 0 {
let indexPath = IndexPath(row: 0, section: 0)
self.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.top, animated: animated)
}
}
}
}
I also facing same issue but after read some tutorials and research & Analysis I got the this issue occurred due to height of cell when you expand the section at that tableview count height of cell from 0 to 120(as per your cell height).
In my case I solved that issue using estimated height of cell.
I hope that will help you,
Thanks

UITableview reloadRows not working properly in Swift 3

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)

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?

Designing tap-to-expand cell in storyboard

I have added a feature to my app that allows users to add notes which will appear in table view cells when they are tapped as they expand to show more content.
My question is - How do I layout the content that is to be shown when the cell is expanded? I have tried adding the textView to the prototype cell, but I just get the textView overlapping on the other cells in the table view rather than being hidden before being tapped.
Here is my code for when a cell is tapped:
var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex.row {
if cellTapped == false {
cellTapped = true
return 141
} else {
cellTapped = false
return 70
}
}
return 70
}
You need to set the cell's clipToBounds property to YES (or select the "Clip Subviews" check box in IB for the cell), so none of its subviews will show outside the bounds of the cell. The text view should have constraints to the sides of the contentView as well as a height constraint. It should also have a constraint to something above it, and that view should have a constraint to the top of the contentView. The constraints should look something like this,

Resources