I would like to wait saveInBackground Parse method before the view disappears. Because the view following is using this data, but that doesn't have the time to refresh...
Here is my code :
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
PFQuery *urlImage = [PFQuery queryWithClassName:#"urlImage"];
[urlImage whereKey:#"objectId" equalTo:#"IcK6mFChL7"];
[urlImage getFirstObjectInBackgroundWithBlock:^(PFObject *urlImageParse, NSError *error) {
if (!error) {
[urlImageParse setObject:self.photoURL.text forKey:#"URL"];
[urlImageParse saveInBackground];
} else {
NSLog(#"Error: %#", error);
}}];
}
Can I make this code in other place that viewWillDisappear:? Or maybe use MBProgressHUD?
viewWillDisappear is a place to execute code, knowing that the view is about to disappear. I believe what you are looking to do is run some action, prior to the view actually going away.
This part right here -> [urlImage getFirstObjectInBackgroundWithBlock:^
means that the code inside that block is going to run on a different thread. So what you are currently saying is, when the view is about to disappear, spin off this other thread and save something in the background, but continue on doing whatever you need to do, like make this view disappear. That's what you're telling the system. So it continues on, doing what it was going to do, not caring about the results of saving that object.
There is a reason that your view is about to disappear. Something happened in your app where the system is thinking it needs to close this view and present another one. It could be that you hit a back button, it could be that you clicked a save button, and at the end of that code, you are asking the system to pop this view off the stack. We really don't know with only the code you posted. What we can assume though, is whatever action was taken to make this view disappear, is where you should be trying to save this object, and you should probably be waiting for a response before you leave this view. If the next view is dependent on that information, then it doesn't make sense to dismiss this view, and present the next one, until you get a successful response that this object was saved. This is just an assumption though, since I don't understand what your app is doing or what is going on when you are trying to save this data, and what the next view looks like. I would say that typically, if you need this stuff to be saved, in order to continue on in your app, you should be saving this data and waiting for a response, to either display an error message to the user, or if successful, then move on to the next screen.
Related
RESOLVED
I was performing the segue from inside a block and it was another thread. When I specifically move the operation back to the main thread it works great.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self performSegueWithIdentifier:#"LoadToDisplay" sender:self];
}];
QUESTION
I want to programmatically present a view controller.
My app downloads some data from the internet, carries out some data handling and then loads the next view.
I use performSegueWithIdentifer successfully throughout the rest of the app however for some reason it is adding a huge delay with this specific transition.
I've used presentViewController with no delay but I can't use this, it was just to test whether I was missing something obvious.
I have an NSLog when the last data handling method completes and one when the next view controller is loaded. Using 'presentViewController' the time between logs is 14ms. When using performSegueWithIdentifer is a staggering 8.5secs!
I literally commented out one line and tested with the other. No other code changes.
Has anyone else experienced this or know what might be going on?
Thanks.
I have an iOS app that uses Core Data as well as a tab bar controller. In the first tab, the user can add items that get saved in Core Data. In the second tab, there's other functionality that relies on read-only access to the Core Data store. When I open the app and switch between the tabs, the data looks the same, however, if I then add an item in the first tab, and switch to the second tab, it's not showing i.e. there hasn't been a refresh. In the second tab, I initially had the fetch done in viewDidLoad but I've moved it into viewDidAppear hoping that the fetch would happen everytime I switched to the second tab (and the corresponding view appeared) but I've still got the same problem.
How do I trigger a fetch/refresh when I click on the second tab after having added an item in the first tab interface?
-(void)viewDidAppear:(BOOL)animated
{
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
You should listen for the notification NSManagedObjectContextObjectsDidChangeNotification
This notification tells you that there has been changes in your database.
For a good explanation look at this post:
How can I track/observe all changes within a subgraph?
Unless you manually change an aspect of the query, you don't need to manually performFetch: more than just once. If the results of the query would change, you'll get notified through the delegate methods. I'd just keep the call on viewDidLoad, as the view will be loaded lazily at the time the controller shows for the first time, which is, when you open it through the UITabBarController button
If you are manually changing the query in between presentations, give us more context so we can help with a proper implementation of it.
Edit: This is a bare minimum implementation that will reload a table view whenever the results of your query would change. The delegate methods allow for more specific behavior like inserting rows or deleting them without reloading the whole table, you can read enough information to implement this from the headers or in the apple docs but this will get you going.
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetch];
}
- (void)fetch
{
if (!self.resultsController) {
//Optionally create resultsController lazily here, I didn't see where you created it
}
[self.resultsController performFetch:&error];
//The manual fetch call doesn't trigger delegate calls, so you refresh manually here after the fetch.
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource
//..
//Implement the table datasource to pull from the NSFetchedResultsController here.
//..
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView reloadData];
}
In my app I'm polling a web service for status updates, using a completionHandler block and making changes to the current view based on returned results when the callback executes.
- (void) tickTimer
{
[MyWebService myWebMethod:param1 completionHandler:^(NSString *result) {
// does view still exist?
[self myUpdateMethod];
// does property still exist?
self.theResult = result;
// does child view still exist?
_txtUpdate.text = result;
}];
}
But in the interim, it's possible that the view may have been unloaded as the user navigates elsewhere.
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to the background? I imagine it gets garbage collected at some point, but how do I tell if it's still safe to access by any of the references above, and what would happen if it's not?
If the view does still exist, how do I tell if it is also still the foreground view?
So, blocks create strong references to all objects pointers that are referred to in their closure. Due to this, your block is going to force [self] to stay in memory until the block is destroyed. If this isn't the behavior you want you should create a weak pointer to self and refer to it inside of the block:
__weak typeof(self) weakSelf = self;
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to
the background? I imagine it gets garbage collected at some point, but
how do I tell if it's still safe to access by any of the references
above, and what would happen if it's not?
If your view stays in the view hierarchy, it will stay in memory. Once there are no more references to the view it will be dealloced.
If you use a weak pointer like outlined above, then [weakSelf] will be nil if the view has been dealloced
If the view does still exist, how do I tell if it is also still the
foreground view?
I'm not sure what you mean by foreground view, but if you want to see if it's still in the view hierarchy then you can check the property -(UIView *)superview. If superview is nil, then it's not on the screen
If you use ARC right, it will not let you use deallocated viewcontroller.
You can use viewDidAppear and viewDidDisappear methods to know visible yours viewcontroller or not.
This is how my application is looking now:
After I perform a database update in my detail controller in view number 7 in the image above as soon as the save button is clicked the details are saved the the database. I'm taken back to tableView number 5 and expect the associated row to show latest updates by calling a special method from the parse.com framework that reloads objects and refreshes the table view e.g. [self loadObjects].
I use an unwind segue. In view 7 I make a connection between the save button and the exit symbol of it's controller window in interface builder and then in tableView number 5 I have my segue method that corresponds to this connect.
Unwind segue method:
-(IBAction)saveDetailsButtonTapped:(UIStoryboardSegue *)segue {
// alert goes here
[self performSelector:#selector(didTapRefreshButton:) withObject:self afterDelay:1.0];
}
This method clears the table and loads the first page of objects:
- (IBAction)didTapRefreshButton:(id)sender {
[self loadObjects];
}
When save is clicked on view number 7 the details are saved to the db and user is bought back to table view number 5 then the method above runs after 1.0 delay. I thought this was ok but didn't feel too right. I tried it on my phone and sometimes the delay wasn't long enough, meaning a failed refresh.
I then decided to try using a UIAlertView delegate method to detect when the ok button of the alertview was pressed and it worked ok most times but then the times I pressed OK to dismiss the alert really quickly upon arriving back on the view and the data wasn't reloaded.
Is there a better solid reliable way to refresh my data?
I need some way of knowing that the database update was successful and only then run the [self loadObjects] method and maybe do that automatically.
I have two methods that detect when objects will load (e.g. like when a button has been tapped) and when they have loaded. I have put some spinner code in there to show a spinner while loading is happening and take it away once it's done.
Isn't there some sort of way to queue methods, like some how in one method make it so one thing doesn't happen until another thing has happened?
If so, I'd really appreciate some insight and examples as I could just mark the app as complete but even though I'm not being paid and it's charity work I still have the urge to do my best.
Thanks for your time.
Kind regards
I have put some spinner code in there to show a spinner while loading is happening and take it away once it's done.
You should do something like that here.
I need some way of knowing that the database update was successful and only then run the [self loadObjects] method and maybe do that automatically.
Because you're saving to parse, it should be the parse SDK that tells you when the save is complete. If you're saving in the background (which you should be) then use the save method when provides you with a callback block that is called when the save has completed. This block being called is your trigger to remove the spinner and segue.
Side note :-
Yes, there are several different kinds of queues, most better than using performSelector:..., but there are also other ways of working with asynchronous activities and you should look at the asynchronous activity for guidance. i.e. can I get a callback when this is done, rather than how long should I wait and hope that it is done.
I have a block completion being called from within a button press message and, depending on state, optionally a UIAlertView being displayed. However, when invoked the UIAlertView appears three (3) times...
With the full information but it disappears itself and shows
Just the title shows and when I click OK
Appears again with full information (as in #1) for which I have to dismiss again
Following is a snippet of the code:
[credential performDataOperation:[credential commandForCreateOnClass:self.className]
withArguments:edits
completionBlock:^(BOOL succeded, id before, id after, NSDictionary *arguments, NSError *error) {
if (succeded) {
self.object = after;
self.objectWasCreated = YES;
[self prepareEditsDictionary];
self.navigationItem.rightBarButtonItem.enabled=NO;
}
else {
errorRecieved = YES;
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Error Message" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
}
}];
You are probably seeing just two alerts. The first appears, but you also have code somewhere that summons the second, so it overrides the first. Then you dismiss the second and the first returns. You need to hunt for your code that presents the second alert, the one without the message, and figure out why that code is running. Just do a global search in your project for UIAlertView! It must be in there somewhere, because all alert views are created and presented in code.
You may have accidentally hooked up your button so that it has multiple action handlers. Of course I could be wrong, but this is a mistake I've sometimes made, and then I've been mystified why my method was being called twice or some unwanted extra thing was happening when I tapped the button. Check your nib/storyboard or code to make sure. The fact that a single button can have many actions for a single UIControlEvent is very surprising and is almost never used intentionally.
(If that's not the right answer, then perhaps the solution lies in your performDataOperation method, whose code you do not show. Maybe it calls the simpler UIAlertView, in addition to calling the block.)