I have a UITableView-based in-game shop.
Every cell has a "BUY" button which is mostly enabled and can be switched to "BOUGHT" if the item is a one-time purchase or can be disabled if there are not enough money.
Right now what I do is calling reloadData every time buy button is being pressed in order to update visible cells and the current cell itself. (I have to update all cells, because after purchase it is possible that there wont be enough money for visible item cells).
But it causes weird animation glitches, like when I click on one cell's buy button and animation finishes on another one.
I think this happens due to reusability of cells. So what I want to know is how to reload data in the whole table view without harming native animation.
The only thing I can think of is not to use reusable cells and cache them all, but I dont think this is a good programming practice.
First, make sure that your view layer and model layer are separate. There should be some non-view object that knows about each item; we'll call it Item.
Now, create an ItemCell (you probably have one already). That's your reusable cell. Hand it the Item. It should configure itself based on the data in there.
Use KVO, delegation, or notifications to let the cell observe its item. When the Item changes its status, the cell should update its own button.
When your cell is reused, you'll pass a new item to it. It should stop observing the previous one, and start observing the new one (and of course reconfigure itself to match the current status).
By separating the views (which are reusable and transitory) from the model (which is stable and long-lived), you get the performance benefits of cell reuse with correct animations and no need to call reloadData.
You could have a reloadCell method in the cell class and loop through the table's visibleCells and update their UI that way. That way they are not recreated (or re-used), they just have their relevant UI pieces that could have changed due to the new data updated.
Related
I have a UITableView with at most a few hundred cells (not all visible at once). Each cell contains a UIButton with a way to indicate progress of an upload. A URLSession performs the uploads in background tasks.
Currently, the session delegate is the UIViewController that manages the cells. As a result, the session calls delegate
.URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: to periodically inform the delegate of the progress of sending content to the server.
In the delegate method, I find the UIButton associated with this task and animate the new progress (I can find the button because I make button.identifier = task.identifier).
This approach forces me to find the button every time the delegate method is called. This seems indirect and I am wondering if there is a better way to do this — there could be 100s of buttons so worried about runtime.
I was thinking to make the button be the session delegate, but that goes against MVC and the button reference may disappear or change in a table view causing undefined behavior (though it sort of makes sense to only update buttons that are actually in memory).
there could be 100s of buttons
No, there couldn't. Cells that do not appear on the screen do not exist at all (because cells are reused in a table view). So you only need to worry about the cells that are actually visible at any one moment. See UITableView visibleCells and indexPathsForVisibleRows. Thus, even though your approach is not extremely efficient, it isn't extremely inefficient either.
However, the correct way to do this is to use the progress object vended by your upload task. When the upload starts, tell the cell or the button or whatever to start observing the progress object's fractionCompleted using key-value observing. Now the cell or the button or whatever is in direct contact with that one task and can update itself every time it hears that the fractionCompleted has changed. When the cell stops being displayed, stop observing. There's a little more to it (i.e. to cope with reused cells that scroll onto the screen when the corresponding task is already in progress) but that's the basic architecture you want.
My sport app keeps track of who is on and off the field, and for how long. I normally have 20-30 people to swap around, during training time. I have a timer to reload the visible cells of the collection view every second. The whole collection view reload can cost between 120-150ms.
The scrolling performance is good, however, sometimes the tapping to swap players does not respond. I think that is when the collection view is trying to reload the cells.
I can see there might a couple of ways:
Use UITableView, with each row to have 4 different elements.
Use UIScrollView, and preload all elements there. We used to use KKGridView library here, but it is way outdated. That is why we move to UICollectionView after 6-7 years
Have anyone had the same issue, and which way should provide the acceptable level of efficiency but not too complex to implement (to reduce the number of bugs)
Keep the record of the cell's index that need to update, then get the Cell from collection view, call method ask it to update itself, push the view update to the cell class, it wont mess with your collection view tap/drag...
Reload whole collectionView is only needed when whole dataSource change, yours is simply cell-by-cell update as I notice
I have another question open where I'm trying to figure out how to reload the collectionView without auto-scrolling. I was also realizing there are a lot of other situations where I will need to change things in the collection view. Also I have some items that I will want to change the .alpha on and change the text of. Is there a way to do all of this in Swift? For example (to be specific) if I have a collection view with a view in each cell and that view has a textField in it, can I change the alpha and text, (change alpha with animation even) without reloading entire table?
Look at the documentation for UICollectionView. There are several "reload" methods:
reloadData()
reloadSections(_:)
reloadItems(at:)
If you just want to reload a single item, update your data source's data and then call reloadItems(at:) passing in the index path for the item.
Another option, if a cell is currently visible, is to use the cellForItem(at:) method to get a reference to an existing cell. Then you can directly access UI components of the cell as needed. You should also update your data model as needed so if the user scrolls and comes back, the cell will be rendered properly.
Most appropriate where you can update your custom view of a particular UIcollectionViewcell is reloadItemsAtIndexPaths.
You would be handling a particular item than whole collectionview with reloadData.
You can handle it via notifications or some call backs in your code where you can make decision when to update which cell.
Hope it will help you.
I have a UICollectionView, and I override setSelected in the UICollectionViewCell subclass to perform a little bounce animation when the user selects the cell.
The problem is that on selection of the cell, the very selection of the cell alters the data for the other cells (for instance, imagine the other cells have a label in each one that has the amount of cells selected displayed). So I call collectionView.reloadData() so that all the other cells update as well to show the new data (for instance, the amount of cells selected in a label).
However, this reloadData() call seems to reset the UICollectionView completely. Any animations taking place are stopped and the cells are simply updated without animation.
How do I have the cell selection animation, and update the other cells at the same time?
I've tried calling reloadData() only after the animation has completed, but this makes it look like there's lag/delay in updating the other cells (as it doesn't update them until a moment after when the animation finishes), which doesn't look very good.
Similarly, I've tried calling reloadItemsAtIndexPaths: (and exclude the selected cell that will animate), but for whatever reason with collection views this method seems really slow. Like there's a noticeable amount of lag after pressing it.
What should I be doing here? What's the standard for reloading data without the collection view destroying in-progress animations?
Reloading throws away the existing cells and creates (or reuses) new ones. So any animation is inherently destroyed. So, don't reload.
Instead, update your data model as usual and then get the currently visible index paths and associated cells and directly update those cells.
I have abit of a problem, I have a coredata object that is used to populate a UITableView. Each UITableViewCell has a couple of buttons that I am using as check boxes, when the user presses one of these text boxes I would like to update coredata and reload the UITableView so all of the arrays I have are updated to reflect the new data.
Thinking about this I have come to the conclusion that its abit redundant or overkill to be reloading the UITableView every time a button is pressed because some of these UITableViews will contain hundreds of rows with two editable UIButtons each.
So I thought that maybe I should update the current array instead then when the view is either exited or the device is put to sleep I could update the coredata object then? the only thing being I don't really know if this is the right thing to do or possible.
The reason this is such a problem is that when I change a button from say a tick to a cross if I scroll away then come back the buttons tick or cross s reverted to its old value.
I would like to know the best way to handle this case as I have never done anything like this before.
You should use an NSFetchedResultsController and its delegate methods to populate the UITableView. Then when the user taps a button, you simply update the corresponding Core Data entity and not the cell. The NSFetchedResultsController will then call its delegate methods, and you can update just that one cell on which the user made a change.
Also, in cellForRowAtIndexPath, you simple fid the corresponding CoreData entity, and use its attributes/properties to adjust the display of the cell.
Remember, that you must always use some data (NSArray usually) from which to read what to do for the cell at the indexPath when the tableView calls cellForRowAtIndexPath:.
This way, when the fetchedResutsController call its delegate methods, you can simply call reloadCellAtIndexPath on the tableView and then the tableView will call cellForRowAtIndexPath again. As the Core Data entity has been updated, your logic for adjusting the display for that cell will cause the cell to look as it should. It's important that you only ever adjust the way a cell looks in cellForRowAtIndexPath, and base the look on a CoreData entity. Change the look of a cell in multiple places, and you will get problems.