How to resolve UITableView 'jump' when tapping status bar? - ios

I have a tableViewController with custom cells that each contain a textField. Cells are added to the tableView by clicking an 'Add' button which calls the insertRows(at:with:) method. The cells have a reuseIdentifier and are being reused.
The issue:
Given the user has added a bunch of rows and is editing a textField in one of the bottom rows, when the user tries to scroll to the top by tapping the status bar at the top of the screen, then the tableView jumps / glitches on the first tap, but then scrolls to the top (as expected) after the user taps the status bar a second time.
Note: this jump / glitch doesn't occur when the textField being edited is the first textField to be edited in the tableView, it only occurs from the second edit onwards.
The goal: For the tableView to scroll to the top when the status bar is tapped, without hiding the keyboard.
Things I've tried
Manually setting tableView.scrollsToTop = true
Adding tableView.beginUpdates() before calling insertRows(at:with:), and endUpdates() after
Resigning first responder when textField finished editing
Searching the web for many hours to understand why this is happening, to no avail
#IBAction func addButtonTapped(_ sender: Any) {
let indexPath = IndexPath(row: cells.count, section: 0)
cells.append(cell(text: ""))
tableView.insertRows(at: [indexPath], with: .none)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCellIdentifier", for: indexPath) as! TableViewCell
return cell
}
Attached GIF shows the jump / glitch when the user taps the status bar for the first time, followed by the expected behaviour when the status bar is tapped for the second time.
TableView Jump / Glitch
Any help would be most appreciated. Thanks in advance!

Related

Expandable view within tableview

I currently have a table view controller which consist of not only the cells but also a UIView. Now, within that UIView there's a label which might have more than 1 line of text with a See More button. When I pressed that button, the button itself will disappear and the text.numberOfLines is set to 0 so the View should expand to show all text. Which doesn't seems to work in my case, The button does disappear though and the text just continues to the edge of the screen, truncated instead of extending down.
But when this whole UIView is outside the table view controller the functions above work just as expected, but not after I've moved them to within the table View right above the prototype cells. Any Ideas?
When you are setting up the tableviewcell, you have to specify a fixed height for the row with the delegate function
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) {
if expandedArray[indexPath.row] {
return 60
} else {
return UITableViewAutomaticDimension
}
}
Then keep an array of bools, to specify if the cell is expanded or not. Primarily set the bools to false indicating "not expanded".
Then when the use presses the "see more" button, make the bool in the array with the right index true, indicating that the cell is expanded. and call the below code updating the cell.
tableView.beginUpdates()
tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
tableView.endUpdates()
That should do the trick.
P.S: Don't forget to properly add constraints inside tableview cell.
Use UITableViewAutomaticDimension and reload your cell on click of see more button.

tableviewcell repeating uibuttons

I am having a little trouble with buttons on a tableview.
I have a tableViewCell that I customised with 3 buttons. I set the buttons to hidden in interface builder and when the table loads the buttons are hidden as expected.
I then set the hidden property of the tableview to false when didSelectRow is called and hidden.true when didDeselectRow is called. This works fine as well. The problem is the buttons that are set to visible in the didSelectRow are also visible every seven cells down. they keep repeating themselves.
Below is the code that shows the buttons
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
print("Table selected")
cell.insertEmailButton.hidden = false
cell.insertPhoneButton.hidden = false
cell.insertAllButton.hidden = false
cell.contactTextLabel.alpha = 0.2
cell.contactDetailTextLabel.alpha = 0.2
}
And this hides them when the tableViewCell is deselected
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! ContactsViewCell
cell.insertEmailButton.hidden = true
cell.insertPhoneButton.hidden = true
cell.insertAllButton.hidden = true
cell.contactTextLabel.alpha = 1.0
cell.contactDetailTextLabel.alpha = 1.0
}
I did some research and I learnt it might be the row with the buttons.hidden set to false are being reused by the tableview. But I understand from documentation that the cell being reused is from cellForRowAtIndexPath and not the cell at didSelectRow which is where I am setting the button.hidden to false.
I also tried using the cell.isSelected property in an if else statement in the cellForRowAtIndexPath to hide and show the buttons but this does not show the buttons at all.
Thanks in advance for your help
The tableview reuses the view of the cell when the table is scrolled, to save memory. So, for example, when you set the button to visible (inside didSelectRow) and then scroll down the table, the tableview will take the cells that go out of the visible screen at the top and will reuse them at the bottom, to save the overhead of creating new cells, improving performance.
That is why, your previous properties on the cells are repeating.
To get the desired hidden button on scrolled cells, I recommend setting button.hidden to true/false in
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
This will set the button to hidden whenever a new row is scrolled into the visible view area.
Hope this helps.
I solved the recurring buttons by hiding them when I check if the cell is deselected in cellForRowAtIndexPath. This also means any cell I select will lose its selected status and buttons will disappear when it leaves the view.
I can live with that.
if cell.selected == false{
cell.emailButton.hidden = true
cell.phoneButton.hidden = true
cell.allButton.hidden = true
}
UITableView reuses its cell to improve performance. So, you can not do the way you are trying. What we have to do is, like other tableview cell info e.g. title, description, thumb image etc we also need to save the state for buttons in the array. When you want to hide a button for the cell take object at index from the array and change the button state for the button and reload that table view cell. Still if you face problem or feel difficult to understand, please feel free to ask.

Handling touch events to one UITableCell

I have some UITableViewCells in which I can perform click actions.
When a click action starts the cell will expand and a UIPickerView, UITextView or other data will be displayed. (See example images)
When you click the TableCell again the cell will collapse and the original state will be displayed.
Currently every cell expands when I click in a UITableViewCell with an expand action. When I click on a cell I want every other cell to be collapsed and only expand the clicked one.
Question:
How can I give my table cell, which is expanded, a state in which it will receive all touch events. (Become the first responder for the whole screen) and close it first and after the close action send the click event to the corresponding UITableViewCell.
I have made the UITextView first responder and that will close the keyboard after it's poped up, but I want the table cell to be the handler of the click events.
Example code
func togglePicker() {
//This function is called when the UITableCell is clicked.
canBecomeFirstResponder()
// Some code here which adds UIPickerView, UITextView or other data.
setNeedsLayout()
}
I tried this code, but this cell only receives touch events which are triggered in this cell and not outside its boundaries.
Example images
Orginal cell state
First cell is expanded
I implemented the following solution:
class CustomCell: UITableViewCell {
var delegate: CustomCellDelegate?
var focussed: Bool = false
function becomeFocussed() {
//Put some code here to change the design
setNeedsLayout()
focussed = true
delegate?.isFocussed(self)
}
function becomeNormaleState() {
//Put some code here to change the design to the original state.
focussed = false
setNeedsLayout()
}
}
Every Cell has two functions: becomeFocussed() and becomeNormaleState(). When the cell is clicked the function becomeFocussed() should be called.
This functions tells the delegate that the cell is selected.
In the TableViewController the function isFocussed(cell : UITableViewCell), loops through al cells of the current tableView en calls "becomeNormaleState()" for every cells which is focussed.
okay, by looking at the images u are changing the frame of the cell, one solution for When I click on a cell I want every other cell to be collapsed and only expand the clicked one. as u asked in the question, u can store the index path of the clicked cell and use this collapse other cell if that cell's picker view is showing for example,
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if showIndexPath?.row == indexPath.row
{
return 330 //expanded height for example
}
else {
return 100 //normal state height for example
}
}
//in this method u can decide which one to expand or collapse
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//if user selected the same cell again close it
if showIndexPath?.row == indexPath.row
{
//already expanded, collapse it
showIndexPath = nil
tableView .reloadRowsAtIndexPaths([showIndexPath!], withRowAnimation: .Fade)
}
else
{
//expand this cell's index path
showIndexPath = indexPath
tableView .reloadRowsAtIndexPaths([showIndexPath!], withRowAnimation: .Fade)
}
}

Hide unhide Static Table View Cells with Swift

I am trying to make a '''Static Table''' with some cells that are expandable and collapsing . I override the ''': WithAnimation:)''' which should provide me some control on the animation transition. Unfortunately, the cells returned are blank, I don't mean empty, but the cells have disappeared and their location is left blanc in the table. When I try using the '''reloadSections( )''' rather, the whole section is also returned blank. After some recherche , I start to suspect that '''reloadRowAtIndexPath(​: WithAnimation:)''' and '''reloadSections( )''' works with table associated with a data source.
Am I right? If yes, how can I control animation of the cell I want to hide/unhide so that the transition is smooth?
Thanks (edited)
To get a smooth animation, simply reload the tableView with
tableView.beginUpdates()
tableView.endUpdates()
this will call the heightForRowAtIndexPath automatically and render a smooth transition.
To expand on irkinosor's answer. In this example I have 1 section with 3 static rows. When tableView is initially loaded, my UISwitch is off and I only want the first row (row 0) be visible (hide rows 1,2). When the UISwitch is on, I want to show (rows 1,2). Also, I want smooth animation having the rows accordion to hide or show.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row > 0 && toggleSwitch.isOn == false {
return 0.0 // collapsed
}
// expanded with row height of parent
return super.tableView(tableView, heightForRowAt: indexPath)
}
In my switch action I initiate the tableView refresh
#IBAction func reminderSwitch(_ sender: UISwitch) {
// to initiate smooth animation
tableView.beginUpdates()
tableView.endUpdates()
}
You can obviously add more logic to the heightForRowAt function if you have multiple sections and more interesting row requirements.

UITableView selected row center like UIPickerView in Swift

What I want to do: Make UITableView selected row animate like a UIPickerView on selection.
I have a UITableViewController in which I am displaying UIImages in rows from Parse backend. The cell size is almost half of the users screen. I have used tapGestureRecognizer "Single Tap" to increase/decrease the cell height to show selected cell images and "Double Tap" to segue data to next view. If anyone is interested to know, here is the Link to it.
Now the problem is, when I tap on any specific cell image the cell increases in height as needed but if the selected cell is not centered when tapped (i.e: I have bottom half of the cell Row-1 and top half of cell Row-2 on users screen) the cell which has its height increased is partially visible.
So I was wondering if there is way with which we can have the selected cell automatically centered on tap? Similar to UIPickerView?
Here is a solution. Use the didSelectRowAtIndexPath delegate method and then scroll to the provided indexpath.row of the tableview accordingly.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
yourTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top , animated: true)
}

Resources