method calls out of order? - iphone

I have the following code:
[self.view bringSubviewToFront:loggingIn];
[self loginWithUsername:user Password:pw];
This is inside an IBAction method for a button press. The button stays highlighted until the user is logged in (this could take several seconds) and THEN the view appears. How come the view isn't appearing before the login method is called?

John's answer would work, but it has to do with the Run Loop than blocking threads. Basically, whenever your app receives an event such as a button getting clicked, your handler gets run in the main thread. Any changes you make to the UI happen after your handler finishes. This lets Cocoa optimize the graphics updates by analyzing/performing them together.
Run Loop:
Event -> Your Handler Code -> System Updates UI
So you need to let your handler code finish in order to let the system start updating the UI - before invoking your loginWithUsername:Password: [sic] method. Scheduling a timer will bump it to the next iteration of the run loop (the timer going off becomes the "event" that triggers the next loop). So,
Run Loop (2 iterations):
Mouse Click -> Your Handler Code (reorder views) -> System reorders views
Timer Fires -> Your Handler Code (trigger login) -> Any addl UI updates
So do something like this:
- (void) buttonHandler: (id)sender {
[self.view bringSubviewToFront:loggingIn];
[self performSelector: #selector(performLogin)
withObject: [NSArray arrayWithObjects:user, pw, nil]
afterDelay: 0];
}
- (void) performLogin: (NSArray *)args {
[self loginWithUsername:[args objectAtIndex:0] Password:[args objectAtIndex:1]];
}
It doesn't have to be an array. You could also pack your username and password in a dictionary and use that as your argument instead.

The first call runs in a different thread which is blocked by the second line. Try delaying the second call by .1 seconds or so by using the:
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
method of NSObject. Since you are using two arguments you will need to either pass both as a single object or use instance variables.

Related

stop and restart a method

I have to stop and restart a simple method that takes from 1 to 2 seconds to execute. How can this be accomplished? I have already tried [NSObject cancelPreviousPerformRequestsWithTarget:self] but it works only with performselector after delay. I have also tried to create a new thread but it doesn't seem to work...
This is my method:
-(IBAction)MyMethod
{
NSLog(#"start");
//Here is the code that takes time to execute. It regards UI intervention,graphic calculation, x and y position etc.
NSLog(#"end");
}
I want this effect: one click on the linked UIButton and the method start (so print start log and end log). If I click the linked UIButton before the NSLog is printed, the method must stop. Is this possible?
You'll want to use a background task. I would suggest subclassing and using a NSOperation and checking for isCancelled within the body of main. See Apple's documentation on using NSOperation and NSOperationQueue.
Well, to do this with threads what I usually do is dividing things into 3 sections:
start.
processing.
finish.
And it looks like this:
-(void)start:(id)sender{
//prepare everything and anything
[NSThread detachNewThreadSelector:#selector(processing:) toTarget:self withObject:nil];
}
-(void)processing:(id)sender{
//Perform all your calculations, you can't modify UI elements here
[self performSelectorOnMainThread:#selector(finish:) withObject:nil waitUntilDone:NO];
}
-(void)finish:(id)sender{
//Wrap everything up and do any modifications to the UI
}
Now, to cancel this you could add maybe use:
Cancels perform requests previously registered with
performSelector:withObject:afterDelay:.
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument

How to use the NSTimer inside of the for loop iphone?

Am very new to iPhone application development. Am adding the events to iCal from my iphone app. The user can add multiple events at a time to iCal from the app. If the user add above 50 events to iCal, the event does not adding in iCal and the app getting crash. So, i want to avoid the app crash. So, i planned to delay the adding event to iCal inside the for loop(While the loop is in running state). How can i pause the For loop running and continue the for loop after 10 sec's delay. Meanwhile the for loop should continue after 10 sec's from where it was paused. How can i do this? Any ideas? Please help to solve my problem? Thanks in advance.
The best way to do what you're after is an NSOperationQueue but it's a bit technical. Something like this might be easier to understand:
Header:
NSMutableArray *events;
When you specify your list of events:
events = /*....*/;
[self nextOp];
The nextOp method:
-(void)nextOp {
// Do 10 events at a time.
int stop = MIN(10,[events count]);
for (int i = 0; i < stop; i++) {
//Add [events objectAtIndex:i] to iCal
}
// Remove these processed events.
[events removeObjectsInRange:NSMakeRange(0,stop)];
if ([events count] > 0) {
// We've still got more events to go, so schedule our next call
[self performSelector:#selector(nextOp) withObject:nil afterDelay:10];
} else {
[events release];
events = nil;
}
}
You should not pause loops on iOS. If you cannot complete your processing promptly and synchronously, you should exit the loop and exit the method containing the loop.
To complete your loop's processing, your app should start another iteration of the loop later, such as in a callback from a completion delegate, a timer, a performselector, or an operation queue. The latter methods can include a delay. You will have to know how to unstructure loops and how to save local state to do this. Exiting a loop with a callback in place is roughly equivalent to a delayed "GOTO" statement.
For an NSTimer call to be useful in loop processing, you need to exit your loop and method, as only then will the timer's callback selector be called.

Asynchronous function execution?

in my iOS app I do the following.
viewDidAppear(){
// Load a spinner in a view on the top
[DSBezelActivityView newActivityViewForView:self.view];
// Execute code that require 3 seconds
...
// Stop the spinner
[DSBezelActivityView removeViewAnimated:YES];
}
The problem is that the spinner doesn't appear, because the the cpu is working hard (something similar). It's like that the code betweek the start and stop has precedence on the rendering of the view.
I would love to find a way to show effectively the start of the spinner, without using a timer to delay the code execution.
Thanks
If you have a method like
-(void) showSpinner:(UIView*)view {
dispatch_async(dispatch_get_main_queue(), ^{
[DSBezelActivityView newActivityViewForView:view];
});
}
there are several ways to call it from a different thread. Choose one from the following:
[NSThread detachNewThreadSelector:#selector(showSpinner:) toTarget:self withObject:self.view];
// or
[self performSelectorInBackground:#selector(showSpinner:) withObject:self.view];
// or
NSInvocationOperation *invOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(showSpinner:) object:self.view];
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue addOperation:invOperation];
// or
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self showSpinner:self.view];
});
Alt + click for details.
Move code between start and stop activity indicator into separate thread because it's blocking main thread. That's why activity indicator is not showing.
Edit: Example
I agree with the 1st answer with a couple of modifications. I just went through this exact same problem. The problem is that anything graphical is going to automatically move to the background updating when you have code that takes time to get through. Throwing the spinner to the background is what it essantially doing anyway. What you want is (sadly) for you main code to run in the background and the spinner to run in the foreground. I know this sounds bad, but in some cases allowing your code to run a bit slower to give indication that the app is doing something useful is beneficial to the user.
In order to get the spinner to work:
1) Take all the code that takes the 3 seconds to run, and put that into a function that is a void function
2) Instantiate your spinner but store it to a variable that is accessible outside your viewDidAppear routine.
3) Startup a new NSTimer with that runs continuously with an increment of about every quarter second or so. I will define what goes into the routine that gets called every cycle later.
4) Call the routine you created in step 1 using the performSelectorInBackground capability. This essentially is now going to run your startup (3 seconds worth) in the background which is really the only way to allow the animated spinner to show up and truly animate.
5) In the routine you created in step 1, add a line of code right at the top that updates a (global to the object) boolean to true, stating that we are in the middle of our main 3 second routine.
6) At the end of the routine defined in step 1 add a line of code setting the same global defined in step 5 to false indicating that our 3 second routine is completed.
7) In the timer routine, we now want to do something that looks like the following:
// If busy that start the spinner
if(YES == busy){
[spinner startAnimating];
}else{
[spinner stopAnimating];
// Here we can also stop and deallocate the timer
}
If you need more aid on this subject, I can indeed provide exact code. Take a look at the example app that I have developed for the Pepperdine News Group. When you press a button, the spinner comes up on the top right of the screen.
http://itunes.apple.com/us/app/pepperdine-graphic-for-iphone/id516343215?mt=8

When are a methods GUI operations actually carried out?

I am working on a web-services data processing app and I am trying to make the app run as quickly as possible. When a certain 3 finger pan gesture is performed, I call a method that sends updated information off to the server to get a new batch of images to update the existing ones with.
So lets say there are 15 images in an array, I filter through them with a 2 finger gesture, and then if I want to change something about them, I can do the 3 finger gesture, and I get that same set back, just tweaked a bit (contrast/brightness, etc.).
Is what I want though is to be able to update the imageView that is displaying the images after the first image has been retrieved, so as to give the user a feel for what the rest in the series are going to look like. But no matter what I try, and no matter how many different threads I try and implement, I can't get the imageView to update before the entire download is complete. Once the batch download is done (which is handled on a separate thread) the imageView updates with the new images and everything is great.
The first step in the process is this:
if(UIGestureRecognizerStateEnded == [recognize state]){
[self preDownload:windowCounter Level:levelCounter ForPane:tagNumber];// Where this method is what gets the first image, and tries to set it to the imageView
[self downloadAllImagesWithWL:windowCounter Level:levelCounter ForPane:tagNumber]; //And this method goes and gets all the rest of the images
}
This is my preDownload method:
-(void)preDownload:(int)window Level:(int)level ForPane:(int) pane{
int guidIndex = [[globalGuids objectAtIndex:pane] intValue];
UIImage *img = [DATA_CONNECTION getImageWithSeriesGUID:[guids objectAtIndex:guidIndex] ImageID:counter Window:window Level:level];
if(pane==0){
NSLog(#"0");
[imageView3 setImage:img];
}else if(pane==1){
NSLog(#"1");
[imageView31 setImage:img];
}else if(pane==2){
NSLog(#"2");
[imageView32 setImage:img];
}else if(pane==3){
NSLog(#"3");
[imageView33 setImage:img];
}
}
So by separating this out into two different methods (there are no threads being implemented at this point, these methods are being called before all that) I was thinking that after the preDownload method completed, that the imageView would update, and then control would continue on down into the downloadAllImagesWithWL method, but that doesn't appear to be the case.
Am I missing something simple here? What can I do to update my GUI elements before that second method is through running?
You are right. However the viewn won't refresh until your code reaches runloop. You can do 2 things:
Make your downloadAllImagesWithWL method async, so it will return after you called it, your main thread reaches runloop, gui updates, and the download method will tell your logic through a callback when its done.
OR
A simplier hackier (and bad) solution would be to run runloop for some time before you call your download method. Something like this: [[NSRunloop currentRunLoop] runUnitlDate: [Date dateWithTimeIntervalSinceNow: 0.1]]; It will run runloop for 0.1 second.
When the image is set, the image view will mark itself as needing display. The actual display won't occur until the beginning of the next run loop. In OS X, you can use -display to draw the view immediately, but I don't think Apple created a public method to do this on iOS. However, if the next method simply creates the background thread, then it will return quickly and the display update will probably occur before the thread finishes.

Nested undo group with CoreData

I want to add a undo manager to a coredata backed iphone application. When the user tries to add a new object (by tapping on + button) I load a new modal viewcontroller and start a new undo group in viewDidLoad.
When the user presses Cancel button, I want to rollback the changes in cancelAction callback.
Queries:
Is it possible to start a nested Undo group and persist it through event loop and collate all the changes done in one single undo group? Right now, when I call beginUndoGrouping in cancelAction, I get a missing beginUndoGrouping exception.
What is the significance of groupsByEvent API for NSUndoManager? Do I require to set it to NO to persist an undo group through event loop?
What needs to be done in saveAction callback to make changes permanent (apart from calling save on mangedObjectContext)? How do I tell Undo Manager to stop tracking the changes?
Error Message:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '_endUndoGroupRemovingIfEmpty:: NSUndoManager 0x75415f0 is in invalid state, endUndoGrouping called with no matching begin
Sample Code:
// RootViewController.m
- (void) addAction {
// Load Modal View Controller to add new object
}
// AddViewController.m
- (void) viewDidLoad {
// Start nested undo group
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager beginUndoGrouping];
[self createModel];
}
- (void) cancelAction {
// Revert all changes
[self.managedObjectContext processPendingChanges];
[self.managedObjectContext.undoManager endUndoGrouping];
[self.managedObjectContext.undoManager undoNestedGroup];
...
}
- (void) saveAction {
// Save changes
}
Beginning with your specific questions - Yes, you can manually define the bounds of an undo operation using beginUndoGrouping and endUndoGrouping.
In this case, the undo operation should work whether or not groupsByEvent is set. This is because all the undo groups that are generated by the event loop are nested under your main open undo grouping started with beginUndoGrouping, and as long as you call undoNestedGroup directly after you call endUndoGrouping, it should work. If you are not using the event loop undo groupings, don't worry about it and set it to NO.
To make your changes permanent, close the undo group with endUndoGrouping and call save on your context. The processPendingChanges calls are not needed, and may cause issues in nested groups. If you want clear the undo operations, call removeAllActions on your undomanager after endUndoGrouping - this guarantees the changes will never be un-did.
Use breakpoints/nslog to make sure your begin/end calls are one for one.
If you want your cancel action to be like an 'undo button', you'll have to do the following:
Move beginUndoGrouping to viewWillAppear
Call endUndoGrouping in viewWillDisappear
re-open undo grouping at the end of your cancel action
re-open undo grouping at the end of your save action
Otherwise, if you leave it as is, make sure you close the dialog in your save and cancel actions, to avoid possibility of endUndoGrouping being called multiple times.
If you have any questions, please comment and I'll update.
Good luck!