Hide unhide Static Table View Cells with Swift - ios

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.

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.

Swift - How to expand UITableViewCell's height, while pushing adjacent cells away from the selected cell?

I am working on a ViewController with a TableView populated with dynamic cells from a prototype nib. I have run into a dead-end trying to recreate certain a cell-expanding animation.
My goal:
When a cell is selected, the selected cell should "expand" (grow in height to twice it's starting size) while at the same time creating space between the cell directly above and below the selected cell.
I've found an example of EXACTLY the asethetic I am looking for in the app "Things". Below are two screenshots showing the table before and and after a cell is selected:
Screenshot of table BEFORE expansion
Screenshot of table AFTER expansion
The best way I can describe the desired animation is the UITableView version of "the parting of the Red Sea". When a cell is selected, the surrounding cells give way lending the selected cell more room.. and the user's focus.
What I've tried:
I found Simon Lee's method answering a similar question and implemented it into my project. And although it animates the row-height change perfectly, it only pushes the adjacent cells on one side of the selected cell. (ie: if the cell at index 4 is selected, all the cells from index 5+ move down but those from index 0-3 stay static. Thereby not achieving the look I'm seeking.)
Using that method, the relevant sections of my code looked something like:
var cellHeight: CGFloat = 72
var selectedCell: IndexPath?
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == selectedCell {
return cellHeight * 2
} else {
return cellHeight
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedCell = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
selectedCell = nil
tableView.beginUpdates()
tableView.endUpdates()
}
Because that didn't result in my desired animation, I tried insertRows:at:with: to insert 2 empty cells (1 above and 1 below the selected row) -- then deleteRows:at:with: to remove them upon deselecting the cell. This ultimately made for a better looking animation, and looked closer to the "Things" example I'm shooting for. However this made the table overly complicated because by adding and removing cells each time a row is selected, it would change the index of the other cells making it frustrating to predict which cells would have what index at any given time.
A possible solution idea?
After working on this for a couple of days the only other way I could think to accomplish what I want is to somehow scroll the table slightly at the same time that the selected cell's height it changed.. so that as the cell expands (moving the following cells downward) it would make the previous cells appear to move upward. I'm hesitant to try this because it feels like a hack, there should be a better way to accomplish this.
ANY help would be thoroughly appreciated! I've been pulling my hair out at an alarming rate. Thank you to anyone who can share their knowledge.

UITableView dynamic cell height scroll to top after calling reloadData

I have dynamic cell height in my tableView
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
I want to do same as whatsapp app (pagination), i want to load 25 by 25 row so when user scroll at top, i want to load new data but i want to keep same position of scroll, here is what i did
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isMore && scrollView.contentOffset.y == 0 {
loadMore() // function that add new data in my model
self.tableView.reloadData()
}
}
The problem that i have is after i reach top of my tableview it call reloadData but the tableView scroll to UP
I tester some other solution like this:
var cellHeights: [IndexPath : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
guard let height = cellHeights[indexPath] else { return 70.0 }
return height
}
But still not working
If you are reloading the table to add more data , then after reload call below function to retain the scroll position.
tableview.scrollToRow(at: indexPath, at: .middle, animated: true)
here indexPath is the indexPath of the last cell before reload.
In tableView:cellForRowAt when you detect indexPath is getting close to the last element (when indexPath.row is getting close to yourArray.count), query for more items. When you have them, add them to your data source (the array, or whatever it is, where you have all the objects represented in the table) and reload the table view. The table view should display the new items and the user can continue scrolling without doing anything special.
You shouldn't wait until tableView:cellForRowAt is called for the last cell, as the user will have to wait if you need to call a server to fetch more items. Whatsapp's scroll is fast because they download the items well before you scroll to the last elements (some time ago it did stop at the end, but now they are probably fetching new items way earlier).
Keep in mind that 25 items might be too few, it the network is slow the user might scroll through the items you have before more items to display and will have to wait, which is not a good user experience. Simulate a slow network connection and see if 25 items are enough to scroll through while your app contacts the server (depends on network speed, request and response sizes, how fast the server answers your request, etc.).
I faced the same problem in a messaging App. Here's how I solved it:
Assigned an unique messageId to every message in datasource array.
In cellForRow:, I save that messageId of very first row in a variable called messageIdFirst.
if(indexPath.row == 0){
messageIdFirst = message.id!
}
In loadMore() method, I check which current indexpath row have the same messageId in now updated datasource array.
if(message.id == messageIdFirst){
previousTopIndexPath = i
}
,where i being that index in datasource array which have the same messageId.
Now, I scroll to that previousTopIndexPath like this:
tableview.scrollToRow(at: previousTopIndexPath, at: .middle, animated: true)
Note 1: As this was not working efficiently in UITableView, I end up using UICollectionView with the same solution.
Note 2: While this does work smoothly, I know there must exist a cleaner way to do this.

UITableViewCell styling resetting when scrolled off and back onto view

I currently have a TableView in my project, which is set up to turn a cell green when it is pressed, and back to clear if it is pressed a second time. However, if I scroll down to the bottom of the table view, and scroll back up, all my cells have been reset to their default clear colour.
I'm not sure how to go about fixing this issue, as anything I can find referring to it is in Objective-C rather than Swift. Any help and advice as to how to go about this would be great, thanks.
Everytime a UITableViewCell goes out of the screen, any function that you've written in the tableViewController/ViewController runs again.
for example in cellForRowAtIndexPath if you have a cell.setUpCell() or something similar, it will rerun and reset your values to the original values.
if you have a
var name = testName in your MainVC
and you update something in your cell, you should change the name in your mainVc too.
Every time you scroll or call tableView.reloadData() UITableView cells will reload. So, every time you select UITableViewCell, add selected index (indexPath.row) to an array(ex: selectedIndexArray) in your didSelectRowAt indexPath: delegate. If the cell you selected is already selected one, then remove the cell from selectedIndexArray.
And in your cellForRowAt indexPath: manage the cells using selectedIndexArray.
var selectedIndexArray:[Int] = [] //to save selected tableViewCells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let isSelected = false
for each in selectedIndexArray
{
if each == indexPath.row
{
isSelected = true
}
}
if isSelected == true
{
//set selected cell color
}
else
{
//set default cell color
}
}
You need to write the logic of adding and removing cell indexes in your didSelectRowAt indexPath:.

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)
}
}

Resources