Updating data for UITableView in background breaks animations - objective-c

I have a UITableViewController which is supposed to fetch data in the background and then refresh the UITableView. However when I run the update method in the background it breaks all transition animations in the entire app (Slides when pushing view controllers on to the navigation stack). Weirdly, the exact same model works in other classes very similar to this one.
Here is the call I'm using for background updating:
[self performSelectorInBackground:#selector(updateData) withObject:nil];
However this works, but is of course not done in the background:
[self updateData];
And finally the method being run:
- (void)updateData{
updating = YES;
[progress show:YES];
dataSource = [[NetworkHandler sharedInstance:self] getRaces];
[progress hide:YES];
updating = NO;
[self.tableView reloadData];
}
The updating flag is not in any way an attempt at a semaphore, merely a way to ensure that the view doesn't get updated twice in case the user switches back and forth between views. ;)

[self.tableView reloadData]; looks like it could cause some kind of threading issue. All updates to the UI should be done in the main thread. So this should work:
[self.tableView performSelectorOnMainThread:#selector(reloadData)];

As of February 2015, the method mentioned in the answer above is deprecated, and replaced by:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];

Related

EXC_BAD_ACCESS error at viewwillappear using scrollToRowAtIndexPath

I'm getting an EXC_BAD_ACCESS error when using scrollToRowAtIndexPath in the viewWillAppear method. I searched for solutions and saw some old posts recommending to set delegate and table to nil (see code below), however when I set that I simply dont get anything loaded in my tableview.
I should say that this is part of a chat application where I want to show the last message entered first. Many thanks for any assistance with this.
Here's my viewWillAppear:
-(void)viewWillAppear:(BOOL)animated {
[self.table reloadData];
int lastRowNumber = [self.table numberOfRowsInSection:0] - 1;
NSIndexPath* ip = [NSIndexPath indexPathForRow:lastRowNumber inSection:0];
//self.table.delegate = nil;
//self.table = nil;
[self.table scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
I should add that this code seems to work fine in other parts of my program, the only time I get the error is in the viewWillAppear method.
viewWillAppear: is too early to do any animation on view. Per Apple Documentation:
This method is called before the view controller's view is about to
be added to a view hierarchy and before any animations are configured
for showing the view. You can override this method to perform custom
tasks associated with displaying the view. For example, you might use
this method to change the orientation or style of the status bar to
coordinate with the orientation or style of the view being presented.
If you override this method, you must call super at some point in your
implementation.
So, you cannot add animations when even the view hierarchy is not set completely.
It may help you
NSIndexPath * lastIndex =[NSIndexPath indexPathForRow:yourContenetArray.count-1 inSection:0];
[self.yourTableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionNone animated:YES];
Write your code in viewDidAppear method.As your table view is loaded
then after you should call the method scrollToRowAtIndexPath . Then
your animation will be performed.And if you want to use in
viewWillAppear , then You can try to reload your tableView and then
write code for scrollToRowAtIndexPath
You should probably use the viewDidLoad method for scrolling, and if your array count is 0 (empty array) it will throw an error. It is not possible to scroll to cell at index -1.

Cocoa Touch: How can I update the table view everytime I tap on a tab of tabbar?

I'm using iOS 8 and Objective c.
If I do a change in the values in one view, I can't see the reflection on my other tabviews. But if I re-run the app, I can see the reflection on the other tabs. Sometimes it works, sometimes it doesn't. How can I make sure that when I tap on a tabview, it will be reload according to the database?
I'm not sure what you are trying in your code, but you can try to implement the viewDidAppear method with a reloadData in your tableView:
- (void)viewDidAppear:(BOOL)animated{
[self.tableView reloadData];
}
[tableView reloadData] is the one you are looking for , this UITableView method refreshes the table view by calling its delegate and data source methods again to get fresh data.
I solved it!
Here is my code:
"TheMaster" is the main tabview. Here selectedIndex can be 0,1,2.... according to the placement of the tab. The leftmost tab has the index 0, and after that 1,2, and so on.
UITabBarController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"TheMaster"];
controller.selectedIndex=0;
[self presentViewController:controller animated:YES completion:nil ];

Reloading a UICollectionView using reloadData method returns immediately before reloading data

I need to know when reloading a UICollectionView has completed in order to configure cells afterwards (because I am not the data source for the cells - other wise would have done it already...)
I've tried code such as
[self.collectionView reloadData];
[self configure cells]; // BOOM! cells are nil
I've also tried using
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadData];
} completion:^(BOOL finished) {
// notify that completed and do the configuration now
}];
but when I reload the data I am getting crashes.
How can I reload the data into the collection, and only when it has finished reloading - do a particular completion handler
This is caused by cells being added during layoutSubviews not at reloadData. Since layoutSubviews is performed during next run loop pass after reloadData your cells are empty.
Try doing this:
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[self configure cells];
I had similar issue and resolved it this way.
If you'd like to perform some code after your collectionView has completed it's reloadData() method, then try this (Swift):
self.collectionView.reloadData()
self.collectionView.layoutIfNeeded()
dispatch_async(dispatch_get_main_queue()) { () -> Void in
// Put the code you want to execute when reloadData is complete in here
}
The reason this works is because the code within the dispatch block gets put to the back of line (also known as a queue). This means that it is waiting in line for all the main thread operations to finish, including reloadData()'s methods, before it becomes it's turn on the main thread.
Collection view is not supported to be reloaded animatedly with help of reloadData. All animations must be performed with methods, such as
[collectionView deleteItemsAtIndexPaths:indexesToDelete];
[collectionView insertSections:sectionsToInsert];
[collectionView reloadItemsAtIndexPaths:fooPaths];
inside of performBatchUpdates: block. That reloadData method can only be used for rough refresh, when all items are removed and laid out again without animation.

How to pause time in iOS Programming

In my iOS program, I want a function to pause for 1 second. What code do I have to write to do this?
You can use one of these: sleep(1); or wait(1);
Or you can use [NSThread sleepForTimeInterval:1.0]
And there is also performSelector:withObject:afterDelay
Another way to do this is:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//Place code here});
sleep() and wait() are sometimes looked down upon but as long as you understand whats happening use it at your free will
Newer to iOS are completion blocks. If you need to perform some action after a view controller is popped, for example, you could now use blocks. I recently used this method to dismiss a message compose view controller and then pop the current view controller if successful.
Old way:
[controller dismissModalViewControllerAnimated:YES];
// Use an NSThread or performSelector:withObject:afterDelay
New Way:
[controller dismissViewControllerAnimated:YES completion:^{
if (result != MessageComposeResultCancelled){
// If cancelled remain on current screen, else pop to parent view controller
[self.navigationController popViewControllerAnimated:YES];
}
}];

UINavigationController stops pushing view

We use a navigation controller and a view controller to display a question to the user. Everything has been working fine but we made some UI adjustments so we can port the application to iPad, the only changes were to make the frame of the table view dynamic to be either on iphone or ipad. However now when we get to the 187 question out of 335 it doesn't push the new question anymore... it pushes a blank screen and the "viewDidLoad" method of the pushed view controller is never called, as it has been the past 187 times. We have setup break points to make sure the navigation controller and view controller are still be allocated in memory and they are.
Here is the viewDidLoad of the view controller that gets called every new push...
- (void)viewDidLoad {
_tableView = [[QuestionTableView alloc] initWithFrame:self.view.frame style:UITableViewStyleGrouped];
_tableView.center = CGPointMake(self.view.center.x, self.view.frame.size.height/2);
[_tableView setDataSource:self];
[_tableView setQuestionDelegate:self];
[_tableView setDelegate:self];
_tableView.scrollEnabled = YES;
[_tableView setBackgroundView:[[[UIView alloc] init] autorelease]];
_tableView.directionalLockEnabled = YES;
_tableView.delaysContentTouches = NO;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.opaque = NO;
[self.view addSubview:_tableView];
}
We push the the view controller by...
[questionsNavigationController pushViewController:viewController animated:YES];
Thanks in advanced! :)
If you make the tableview smaller you can get through the entire set of 335 questions ? Are you creating a ViewController per question ?
You could run the project with instruments to check for a memory leak.
Do you really need all questions on the stack? How about a popViewControllerAnimated:NO before pushing the next one, also with animated:NO?
It works on the simulator because it's memory is the PC's memory. Put an NSLog into your -didReceiveMemoryWarning method to see it running out of memory.
Probably you shouldn't use NavigationController this way. It's.. ugly.
I would do one of the following:
"pop" ViewControllers that are 5-10 views behind (using setViewController). In other words - maintain 5-10 views behind, the others will be freed (and the result saved). Once the user decides to get back (and there are 3-4 views in the stack), reconstruct few more.
implement the NavigationController behaviour yourself - just replace the views instead of stacking them. Once the user gets back, reload the view with the needed data.
If you realy think that your implementation is ok - try to free as much possible memory from your view, once things get hot.