I have a collection view with a flow layout that shows multiple cells. Every cell has some async loading happening before it displays an image and some text. What is the best way to update these cells in succession?
I am doing something like this that works but I'm noticing my collection view gets frozen for a second or so if multiple cells get finished at the same time.
dispatch_async(dispatch_get_main_queue(), ^{
[collectionView reloadItemsAtIndexPaths:#[indexPath]];
});
It would be nice to add some more of your code, But it is actually better to do this kinda stuff with performBatchUpdates instead of doing it in main thread.
- (void)performBatchUpdates:(void (^)(void))updates
completion:(void (^)(BOOL finished))completion;
Here is the documentation
Related
Well, the title already says it all. I have a view (a table view in my special case embedded in a non-table view controller) and at some time in code I want to update cells and display the update. The first part works like a charm but the changes won't displayed after the reloadData() - and even when I call setNeedsDisplay() afterwards.
When the controller has to update something the tableview will be also re-drawn and the changes are visible. As soon as I touch the tableview cell the changes are also visible. Without touch the update just isn't displayed.
I've come across this issue and using GCD should solve it
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
I was doing everything in a single thread and have just started moving to asynchronous calls so I'm a bit confused.
What I would like is for the view to load, either with an empty table or with no table at all. The MBProgressHUD should show while the async call gets the data. Then when the data is found the HUD goes away and the table refreshes.
What I have now is everything works except that the HUD is displayed underneath the empty table. Here is the most relevant code.
- (void)awakeFromNib
{
[super awakeFromNib];
[MBProgressHUD showHUDAddedTo:self.tableView animated:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.dataController = [[StudyController alloc] initWithCredentials:email password:password];
dispatch_sync(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.tableView animated:YES];
[self.tableView reloadData];
});
});
}
Basically what is happening, I think, is all my code that was rendering the table correctly runs once while the async call is running, and shows an empty table on top of the HUD, and then again after the call when I tell it to reloadData. How do I get it to not run that first time?
Thanks.
It's hard to gauge without seeing your table view delegate/datasource methods, but your intuition makes sense. In this case, I can see two options:
Add some logic to your delegate/datasource methods to properly show your loading view if the table is empty.
Rather than add the loading view to the table view, add the loading view to the table view's superview. That way, you can ensure that the loading view is always on top of the table view.
I usually opt for the second method, because I prefer to focus my datasource and delegate methods on only the table view.
Hope this helps!
A question regarding reloading data in your UICollectionView.
When using reloadData it only does this once any scrolling has finished. Then there is a small pause where the collection view freezes and cannot be interacted with until the new data is loaded in. How can I get around this?
For example, I would have a view comprised of maybe 10/20/30 items, each with an image and label. I then update my dictionary with new images and strings and then reload the view to add these new items.
Many thanks.
EDIT:
On closer inspection, its not the reloading that causes the pause, its some other code, where I fetch some data. Why might this freeze my view though when i am not even reloading it to make changes to it?
I am fetching this data from -(void)scrollViewDidScroll:(UIScrollView *)scrollView
EDIT 2:
Ok here is some basic code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self myMethodWithCompletionBlock:^(NSMutableDictionary *results, NSString *nextPageToken) {
//No code in completion block is ever called when the method is called in the dispatch block! Regardless of what it is.
}
});
I'm trying to use a custom UiCollectionViewLayout but I have a little problem.
In my project I want to load some JSON information and present it in a UICollectionView. I need to load the information from WEB and then (when everything is loaded) I need to reload my Collection View.
If I use the UICollectionViewFlowLayout everything works great. However, when I try to use my own layout, the reloadData doesn't work properly and nothing happens.
I'm reloading the data with an async task:
dispatch_async(dispatch_get_main_queue(), ^{
[self.newsCollectionView reloadData];
});
Thanks
It sounds like you're looking for invalidateLayout, which causes prepareLayout, collectionViewContentSize, layoutAttributesForElementsInRect:etc to be called.
I have a table view cell , during click it , another table view will open , you can select something as the value of the table view cell. The issue here is the data in the second table view is big and it will take long time to load. So after I click the cell , the screen will froze there which is not user friendly. I want to displaying a progress bar during load the second table view. But I can not find a good place to add that. I am wondering in which method should I add the code to display the progress bar.
What about displaying a UIActivityIndicator (sample image here) in the UITableViewCell accessory view?
You can place it with some code...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
}
You might also do you long-taking calculation in a background thread by calling
[self performSelectorInBackground:#selector(yourLongTakingMethod) withObject:nil];
Two things:
I would seriously consider a different option rather than a progress bar, try for instance something like MBProgressHUD, it looks better and it suits your purpose.
For implementing this, you would have to make sure your UI thread isn't stuck (and therefore the application doesn't "freeze") - this means calling the loading process on a background thread. The method that starts the loading process should start the progress indicator and the method that deals with displaying the loaded data (once it's ready) should remove it. Good luck!
If your app freezes, it means you are blocking the UIThread, like #stavash suggested. The progress bar is just an animated image, it won't solve your problem. What you want to do is to put your "data preparing code" into a thread. The easy way would be to use Grand Central Dispatch. To get started, visit this tutorial. You can skip the first half and focus on the actual thread blocks. The APIs are dispatch_queue_create, dispatch_async and dispatch_release.
This will not make a progress bar. However, it'll unblock your UI. Then you can consider methods to make a progress bar. Use a builtin one, or some custom animation. Or even preload the data at the first tableview via a background task.