My application is a feed based application (think Instagram). Actually, it is very similar to Instagram - the difference is that instead of a picture, there is a swipeable, paginated view with different objects (link to demo):
https://www.youtube.com/watch?v=EYTjA3Bcv_A&feature=youtu.be
The swipeable functionality originates from this class: https://github.com/nicklockwood/SwipeView
In my UITableViewController class I am trying to remain efficient with my memory by implementing tableView.dequeueReusableCellWithIdentifier. The problem this creates is that as you scroll and cells are being dequeued, the reusable nature of the cells carries over old data. My problem is that I want to preserve which object in the SwipeView a user had swiped to while still implementing tableview.dequeueReusableCellWithIdentifier. The property of the SwipeView that deals with this is currentItemIndex. This manages which item is currently centered in the SwipeView. Naturally, each SwipeView in the feed should remember its own currentItemIndex, however dequeueing cells carries over the old currentItemIndex from the cell that is being reused. This can be reset: cell.swipeView.currentItemIndex = 0 while setting up the new (reused) cell, however then the old cell loses its currentItemIndex when it gets reused. Video of original problem: http://youtu.be/CXTHyvF3adI (see how the old currentItemIndex is transferred between SwipeView cells) Video of problem when currentItemIndex is reset when a cell is reused: http://youtu.be/VBw8Rm-s5Qk (see how the SwipeView cells don't remember which item they were swiped to) Does anyone have an idea on how I can accomplish this? Thanks to all in advance!
That is what PrepareForReuse method is for
Related
I have a UIViewController with a collectionView inside it. The layout may seem confusing but I am making use of horizontal scrolling UICollectionViewCells, presented in 3 sections.
The main collection view is made up of 3 sections. I access 3 different UICollectionViewCell classes for each section becuase it is fetching different data for each one. Once the data is fetched, it dequeues another Cell class, which is the same one accessed for all the sections.
There are 2 buttons in each cell. When I press the button, a value changes within the database. When I re-run the program, the cell in which the button was pressed has moved to its corresponding section that it should be in as per the change. However, I want to achieve this as soon as I press the button. How can I move the cell from one section to another, baring in mind that they the cells presented are inside a collectionview cell making up the sections, which is then inside a greater UICollectionView.?
I've tried researching how to do this but no luck, and everyone seems to be using a drag and drop method which seems too complicated for what I am trying to achieve. Would appreciate any help.
Thanks
The approach I followed to achieve the above situation is using a custom Delegation when cell is removing from collectionviews. A container CollectionView has two sections with 2 different cells containing two different Horizontal UICollectionViews. These two collectionview's cells have UIButtons. When tap on one collectionView's button, that particular cell is removed, custom Delegate method calls and another CollectionView's cell is populated. This is the link of the project I have created for this particular scenario. For better understanding, here is a GIF for demonstration.
I've found some similar questions already on SO, but nothing that seems to address this specific problem.
I'm using a UITableView with around 25 dynamic cells. Each cells contains a hidden UIProgressView. When the user taps on the download button within the cell to download that item, the UIProgressView is displayed and it indicates the progress of the download.
I've achieved this by assigning a tag to each cell which is equivalent to its corresponding downloadItemID. Each instance of MyCell has its own progressBar property.
This works fine as long as the table view is not scrolled during the download. It works also when the user is downloading multiple items at the same time. The code looks like this:
UIProgressView *theProgressBar;
for (MyCell *cell in self.tableView.visibleCells)
{
if (cell.tag == downloadItemID) theProgressBar = cell.progressBar;
}
float progressPercentage = (float)completedResources / expectedResources;
[theProgressBar setProgress:progressPercentage animated:YES];
The problem is that when the user scrolls the table view, the cell and progress view are transferred to another cell. It's simple enough to reset and hide the progress view for the new cell, but when the original/downloading cell is scrolled back into view, no progress is visible.
I've tried caching active progress bars into a dictionary and reallocating them back to the original cell in cellForRowAtIndexPath, but this is giving me the same result: no visible progress after scrolling the cell off and on the screen. Even if I can get them to show up, I'm doubtful I can get this to work seamlessly by rolling my own caching method.
What I need is to keep cells in memory. But can I work around dequeueReusableCellWithIdentifier? This whole problem has arisen because I had to switch to a dynamic system of allocating the cells, but it is too dynamic. Can I create the cells dynamically, but keep them in memory all the time once created, or at least keep the ones that are currently downloading?
(I understand the reasons why cell reuse is the preferred way to go with table views.)
You are working against the framework. As vadian says in the comment, you need to separate the logic and state information from the cells and put them elsewhere.
Here is one way of doing it.
Create a class to hold each download state, like a download ongoing flag, download progress, title, URL, etc.
In your view controller, create and add all your download objects to an array when the view controller is created. The first object corresponds to the first row in the table.
Whenever you dequeue a cell, populate it with data from the array. The NSIndexPath row property serves as the index into the array.
All your updates (download progress) updates the download objects in the array, then update the cell content using the updated object. You can use UITableView cellForRowAtIndexPath to get the cell for a specific array index, if you get nil there is no need to update it.
Hi I am tying to iterate through all the cells of my tableview but My tableview variable only let's me access the visible cells. so is there a way to declare the tableview without using the dequeuereusableCellWithIdentifier? or is there a way to iterate through all the cells?
Thanks,
To efficiently display a table, cells are used and reused depending on which ones are visible onscreen. In fact, this is what dequeuereusableCellWithIdentifier is suggesting - you specify different types of cells so they can be recycled later, as new ones are displayed and the components of offscreen ones are available for reuse.
You should define what needs to be changed or retrieve cells using table view cellForRowAtIndexPath.
You Are Already Accessing All The Cells In The TableView.
Note: I lied, two or more cells may be kept for quick reuse as well by tableView, but are not shown immediately.
At a time, a tableView only shows limited amount to cells to maintain performance and memory usage. So, for this purpose dequeuereusableCellWithIdentifier() method is utilised to let iOS handle the reuse of cells when necessary.
This way, no matter how large the dataSource, from 100,1000 to 1M, for the tableView, it will show the data in its cell smoothly and without any hiccups. That is why, you have limited cells visible and only those are the total cell used and reused by the tableView again and again.
By this definition, the total cell in use are the total visibleCells. So, when you are accessing the visibleCells, you are already accessing all the cells tableView has in use.
If you want to access all the data used by the cell, then please access the dataSource of tableView, not the visibleCells only.
Kind Regards,
Suman Adhikari
I want to access/update UITableView cell (using reusable cells) which is not in the current view . I know the table view cells are reusable, that can be the reason I am unable to fetch them but is there any way of virtually making and updating. OR I have to drop the reusable cell technique. Suppose tableview have total 20 cells but only say 7 are visible in the current view of iPhone. How will i update other 13 cells which are out of view bounds
Accessing and modifying cells (even if you could) would be a bad pattern. UITableViewCells are designed to be created and modified solely in the tableView:cellForRow:atIndexPath datasource method, where framework automatically asks you what to do with cells that are about to be displayed.
The whole idea behind reusability is that the system takes care of the view for you, and all you need to do is take care of your datasource and instruct the system, using the model, how to render cells, in its allocated datasource method.
This paradigm would be defeated if we started accessing cells manually and modifying them.
You can't access those cells because they are not added to the UITableView but are kept in a queue until user scrolls to them, then they are added to the UITableView. Instead update your model, which will reflect changes on the cells.
I'm relatively new to Swift and iOS and I have one issue - I have a custom SwipeCell class that extends UITableViewCell. It's a cell that can be swiped left and right, and has buttons on each side. For some reason if I made the buttons out of the frame of the cell when user swipes the cell they would appear but could not call an action, so my solution was to make cell wider than it is (so buttons can fit in it). Because it was done this way my cell has to have an offset by default for the total width of the buttons of the left (let's say 100), so it's position is X:-100.
And that's fine, and everything works fines with the cells, however there is one huge issue - if I call the deletion of any cell from the tableView like this
tableView.deleteRowsAtIndexPaths([tableView.indexPathForCell(activeCell)!], withRowAnimation: .None)
tableView then deletes the cell, and all of the other cells that are currently visible (doesn't happen with cells above or below the screen bounds) get moved to X:0 instead of staying at X:-100, so I assume that deleteRowsAtIndexPaths calls some function that resets the visible cells positions to 0,0. I'm currently setting swipe cells positions with layoutSubviews() since the number of buttons is dynamic and couldn't be determined upfront, but layoutSubviews is not the function where the bug happens.
So to sum up question - what function does deleteRowsAtIndexPaths call after deleting a cell that resets/redraws the visible cells?
deleteRowsAtIndexPaths deletes a row(s) from the tableView. This is handled internally by iOS. From Apple documentation:
Deletes the rows specified by an array of index paths, with an option
to animate the deletion.
Another interesting thing to note here is that this method does not modify your model (object that holds data used by your table view cells to render on themselves). You have to do that yourself. The cells are deleted, but if you call reloadData without deleting the row from your Model, cell will reappear.
Expect that cells get deleted and created all the time. Cells are very, very temporary objects. Write the code to create one when needed, and don't make any assumptions. Don't assume the cell is there later, don't assume it's in the same row, don't assume it displays the same data (because cells are recycled).
Since I could find out what happens inside of deleteRowAtIndexPaths, and why it changes frames of each Cell, I decided to just do my own function that deletes a row, by obtaining cells with tableView.visibleCells, and then just moving cells bellow the deleted cell up by a height of deleted cell. Thank you all for trying to help me, and especially thanks to Abhinav who told me that it was handled internally, which help me decide to write a custom code for the deletion.