dismissModalViewControllerAnimated mystery - objective-c

I've seen a number of posts on this subject, but none that leave me with a clear understanding of what is happening.
I've set up a small test involving two UIViewControllers: MainController and ModalController.
MainController has a button on it that presents a modal view controller using the following simple code:
ModalController *myModal = [[ModalController alloc] init];
[self presentModalViewController:myModal animated:YES];
[myModal release];
Now, if I immediately dismiss this modal controller from within the same block of code, as per this next line:
[self dismissModalViewControllerAnimated: YES];
The modal view does not dismiss.
Following some suggestions on this site, I put the dismissModalViewControllerAnimated call in a separate method, which I then called with:
[self performSelector:#selector(delayedDismissal) withObject:nil
afterDelay:0.41];
This works - at least if I make the delay 0.41 or greater. .40 or less and it doesn't work.
At this point, I'm assuming I'm dealing here a run-loop that needs to catch up with itself, for lack of a better description. It's not very stable, unfortunately.
So, for the next test, I make the delayedDismissal do nothing - it only serves to provide a delay - and re-insert the dismissModalViewControllerAnimated call back in the original block, such that my code now looks like this:
ModalController *myModal = [[ModalController alloc] init];
[self presentModalViewController:myModal animated:YES];
[myModal release];
self performSelector:#selector(delayedDismissal) withObject:nil
afterDelay:0.41]; // to create the false delay
[self dismissModalViewControllerAnimated: YES];
...now the dismissModalViewControllerAnimated doesn't work again, no matter how long a delay I use.
So, what is happening here? I realize, like others, I can achieve my goal through assorted workarounds, including the use of a delegate, etc. But I really think it would be good for everyone who encounters this issue to walk away with a thorough understanding of both the problem and the proper solution for this scenario. Incidentally, one use case for this scenario is to present a loading screen modally where the user has no interaction with that screen; it's just being used to present information while blocking the user from taking actions.

The view is animating, thus as long as it is animating calling dismiss won't work.
Also in the second thing you tried, you are calling a "delay" but what you are actually doing is saying the following: "Ok, here is this cute method, can you execute that 0.41 seconds later? thanks, in the mean time, call this method.."
Dismissing a modal view controller should be done through the userinterface, by clicking a button, so why are you trying this in the first place?

Related

How do I notify incoming scene when presentScene:transition: is complete?

Let's say I'm transitioning from an old SKScene of an SKView to a new one:
//In some controller somewhere...
-(void)swapScenes{
MySceneSubclass *newScene = [[MySceneSubclass alloc] initWithSize:CGSizeMake(1024, 768)];
SKTransition *transition = [SKTransition crossFadeWithDuration:REALLY_LONG_DURATION];
[[self mySKView] presentScene:newScene transition:transition];
}
Let's additionally say I want my new scene to perform some action or animation once the transition is completed, but not before. What would I use to trigger that?
At first I thought I'd be able to override didMoveToView: to do this, but it turns out this is called at the very begining of the transition (in hindsight, this makes sense. In a crossfade, the incoming scene is composited at the very beginning of the animation, even if its opacity is very low).
Next, as a hail mary, I tried inserting a call to the new scene right after presentScene:
-(void)swapScenes{
MySceneSubclass *newScene = [[MySceneSubclass alloc] initWithSize:CGSizeMake(1024, 768)];
SKTransition *transition = [SKTransition crossFadeWithDuration:REALLY_LONG_DURATION];
[[self mySKView] presentScene:newScene transition:transition];
[newScene doSomethingAfterTransition]; //<----
}
But presentScene: predictably returned immediately causing this method to be called long before the transition had completed.
As a last resort, I'm considering something like:
[newScene performSelector:#selector(doSomethingAfterTransition) afterDelay:REALLY_LONG_DURATION];
But I'd really like to avoid that if at all possible. It seems like there ought to be an delegate action or notification or something that knows when the transition is over, right?
The answer to this was staring me in the face. As I mentioned above, in a transition both scenes need to be present throughout the animation. Thus the incoming scene's didMoveToView: is called immediately at the beginning of the transition instead of at the end as I expected.
Of course, by this same logic, the outgoing scene's willMoveFromView: won't get called until the end of the transition. Which is what I was looking for in the first place.
So, you can override -willMoveFromView: of the outgoing scene (or, more likely, some shared superclass) to send a notification or call a delegate or whatever you like when transition completes. In my case, I have it call a block so I can keep everything local to my -swapScenes method, but YMMV.
Perform selector after delay with the same delay as the transition is perfectly reasonable.
This is a commonly desired need and (ideally) we shouldn't be programming around the tools. The two workaround provided by #jlemmons and #LearnCocos2D are both functional, but each have their fallbacks.
I would highly suggest going to the Apple Bug Reporter and requesting the addition of
- (void)[SKScene didFinishTransitionToView:(SKView *)view]
The more people request, the sooner it may appear.

Calling -setNeedsDisplay:YES from within -drawRect?

I am customizing my drawRect: method, which serves to draw a NSImage if it has been "loaded" (loading taking a few seconds worth of time because I'm grabbing it from a WebView), and putting off drawing the image till later if the image has not yet been loaded.
- (void)drawRect:(NSRect)dirtyRect
{
NSImage *imageToDraw = [self cachedImage];
if (imageToDraw != nil) {
[imageToDraw drawInRect:dirtyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:YES hints:nil];
} else {
//I need help here
[self setNeedsDisplay:YES];
}
}
My question is how to do the latter. [self cachedImage] returns nil if the image is unavailable, but anytime within the next few seconds it may become available and at that time I want to draw it because the custom view is already on screen.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn), but that doesn't work.
Any pointers as to where I can go from here?
EDIT:
I am very much aware of the delegate methods for WebView that fire when the loadRequest has been completely processed. Using these, however, will be very difficult due to the structure of the rest of the application, but I think I will try to somehow use them now given the current answers. (also note that my drawRect: method is relatively light weight, there being nothing except the code I already have above.)
I currently have about 10+ custom views each with custom data asking the same WebView to generate images for each of them. At the same time, I am grabbing the image from an NSCache (using an identifier corresponding to each custom view) and creating it if it doesn't exist or needs to be updated, and returning nil if it is not yet available. Hence, it's not as easy as calling [view setNeedsDisplay:YES] from - (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame or another method.
My initial instinct was to try calling [self setNeedsDisplay:YES]; if the image wasn't available, in hopes that it would tell Cocoa to call drawRect again the next time around (and again and again and again until the image is drawn)
This would be incredibly inefficient, even if it worked.
anytime within the next few seconds it may become available and at that time I want to draw it
So, when that happens, call [view setNeedsDisplay:YES].
If you have no means of directly determining when the image becomes available, you'll have to poll. Set up a repeating NSTimer with an interval of something reasonable -- say 0.25 second or so. (This is also pretty inefficient, but at least it's running only 4 times per second instead of 60 or worse. It's a tradeoff between two factors: how much CPU and battery power you want to use, and how long the delay is between the time the image becomes available and the time you show it.)
my drawRect: method is relatively light weight, there being nothing except the code I already have above.
Even if you do nothing at all in -drawRect:, Cocoa still needs to do a lot of work behind the scenes -- it needs to manage dirty rects, clear the appropriate area of the window's backing store, flush it to the screen, etc. None of that is free.
Well, usually there is some delegate method that is called, when a download of something finishes. You should implement that method and call setNeedsDisplay:YES there.
The documentation for webkit:
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/ResourceLoading.html#//apple_ref/doc/uid/20002028-CJBEHAAG
You have to implement the following method in your webview delegate:
- webView:resource:didFinishLoadingFromDataSource:
There you can call [view setNeedsDisplay:Yes]

Is setNeedsDisplay *always* repainting?

I wrote a little custom-view-application using cocoa. And later (yes, i know it's bad) I just asked myself: Would this work for cocoa touch as well? Of course id did not work instantly, I had to change the class names and so on. Well, i refreshed the View, whenever it was needed, using a NSTimer and the setNeedsDisplay: method. Worked pretty well under cocoa, but absolutely not under cocoa touch.
I can't explain it to myself an I actually don't know what lines of code could help someone to solve the problem. Maybe here is the Timer:
[self setMyTimer: [NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:#selector(myTarget:) userInfo:nil repeats:YES]];
And it's target:
- (void) myTarget:(NSTimer *)timer {
[self setNeedsDisplay];
}
The timer is invoked every 30 ms, I checked that with an NSLog.
In the drawRect: method I did actually just draw some shapes and did nothing else. Just in case it would be necessary to call some kind of clearRect: method. As I said, under cocoa it worked.
I would first verify whether drawRect: is running by using a breakpoint or log statement.
Then, make sure that your view is actually on the screen. What is the value of [self superview]? You should also do something like self.backgroundColor = [UIColor redColor]; so that you can see where your view is.
Just because you're marking the view dirty every 30ms doesn't mean it will draw every 30ms. It generally should (that's about 30fps), but there isn't a guarantee. drawRect: shouldn't rely on how often it's called. From your question, I assume you mean that it's never drawing, rather than just not drawing as often as expected.
Here's the discussion about setNeedsDisplay (note the LACK of arguments) from the documentation of UIView:
You can use this method to notify the system that your view’s contents
need to be redrawn. This method makes a note of the request and
returns control back to your code immediately. The view is not
actually redrawn until the next drawing cycle, at which point all
invalidated views are updated.
You should use this method to request that a view be redrawn only when
the content or appearance of the view change. If you simply change the
geometry of the view, the view is typically not redrawn. Instead, its
existing content is adjusted based on the value in the view’s
contentMode property. Redisplaying the existing content improves
performance by avoiding the need to redraw content that has not
changed.
In contrast, here's the discussion about setNeedsDisplay: (note the argument) from the documentation of NSView:
Whenever the data or state used for drawing a view object changes, the
view should be sent a setNeedsDisplay: message. NSView objects marked
as needing display are automatically redisplayed on each pass through
the application’s event loop. (View objects that need to redisplay
before the event loop comes around can of course immediately be sent
the appropriate display... method.)

Update screen in Cocoa/Objective C following button press

Newbie Objective C/Cocoa question: I have an application with some data entry fields and a "do it" button. When the button is pressed, some computation takes place and output data is displayed in a table view and some text fields in the same window. What I'd like is that when the button is pressed that the text fields and the table view are both cleared while the computation takes place.
I've tried making the appropriate calls as the first few statements of the action routine for the button press, but that doesn't work. I would imagine that the runtimes don't get called to do the screen update until after my action routine is finished.
Is there a simple way to do what I want to do? Thanks.
You imagine correctly.
The usual way to do this sort of thing is to use NSObject's performSelectorInBackground:withObject: to start the heavy calculation in the background. Then once the background code finishes doing its work, use performSelectorOnMainThread:withObject:waitUntilDone: to call another selector on the main thread to update the UI (remember, UI calls may only be done from the main thread).
You're correct about the screen updates not taking place until after your routine finishes. Most drawing to the screen is queued to improve performance.
When you change the value in an NSTextField, it knows to call [self setNeedsDisplay:YES] in order to queue its need for redrawing. If you want to force it to display, you can call [textField display]. (Note that calling [textField setNeedsDisplay:YES] will not cause immediate display). Things get a bit more difficult with an NSTableView, as this -display method is unlikely to work for it.
While you could create a secondary thread to do your processing, that would create a lot of complexity that may not be worth it. You might consider using -performSelector:withObject:afterDelay: to begin your processing routine rather than calling it directly.
- (IBAction)buttonClicked:(id)sender {
[textField setStringValue:#""];
[tableView reloadData];
// instead of doing the following:
// [self processData:nil];
// do
[self performSelector:#selector(processData:) withObject:nil afterDelay:0.0];
}
- (void)processData:(id)sender {
// process the data
[textField setStringValue:#"the results"];
[tableView reloadData];
}
Using -performSelector:withObject:afterDelay: is different than calling the method directly, as it causes the method to be called not immediately, but scheduled to be called "ASAP". In many cases, your app will be able to squeeze in the updates to the UI before it can get to performing that computation method. If testing reveals this to be the case, then you can avoid having to go to the trouble of creating a secondary thread to do the processing.
If you want to force updating screen then call setNeedsDisplay from your UIView.
I would imagine that the runtimes
don't get called to do the screen
update until after my action routine
is finished.
Bingo. Your button's action method is called on the main thread, which is the same thread that is responsible for updating the user-interface. So the interface will not update until after your action method returns.
To get around this, you can split your action method into two parts. The first part makes the calls to clear your previous view and set whatever new state you want to use for rendering. The second part does the new calculations, and is moved to its own method. Then, at the end of the first part, add something roughly like:
[self performSelectorInBackground:#selector(myActionSecondPart) withObject:nil];
...to run the computation part in the background. Then your UI will update while the computation runs.

Lazy UIPickerView - (it won't move twice)

And thank you in advance for your attention.
I am trying to achieve a little animation effect in my simple application, using this time an UIPickerView object.
Basically, when a certain event occurs, I wish my picker view to animate itself selecting its last row (with animation, of course) and immediately after (without any further user interaction) its first row (with animation, of course). This, to give an 'application got crazy' effect which is supposed to be really really witty, I guess.
Therefore my code was:
[myPicker selectRow:[myPicker numberOfRowsInComponent:0]-1 inComponent:0 animated:YES];
[myPicker selectRow: 0 inComponent:0 animated:YES];
And what I got is that the picker will execute only the second instruction no matter the row specified within it. It seems to ignore the first one.
I thought it could be for some 'clever' function of the compiler which notes that two following instructions perform an assignment to the same variable, therefore is better to ignore the first one (not considering the 'ANIMATED' factor).
I don't know if it is the case but I tried to move these two instructions within two if statements based on value of some parameter, trying this way to fool the compiler (which cannot know at compile time the value of that parameters, therefore it is not supposed to perform such an optimization).
It didn't work, either because I am completely out of trace or because the compiler is far more clever than me.
I even thought it could be a problem of delays and correct sequence of events therefore I tried to rewrite the code as follow
-(void) setPickerRowToLastRow;
{
[myPicker selectRow:[myPicker numberOfRowsInComponent:0]-1 inComponent:0 animated:YES];
}
-(void) setPickerRowToFirstRow;
{
[myPicker selectRow:0 inComponent:0 animated:YES];
}
.....
[self performSelector: #selector(setPickerRowToLastRow)
withObject: nil
afterDelay: 1];
[self performSelector: #selector(setPickerRowToFirstRow)
withObject: nil
afterDelay: 1];
And it didn't work: the picker still performs just the second action and it will move just once.
And now the question(s):
Why does the picker move just once?
How can I reach my goal (even if maybe a bit trivial)?
This has nothing to do with the compiler. It's not that clever.
The reason only one animation is performed is that the animation doesn't start "at once" but on the next run loop (more about which here). At that point the animation will start, using the latest values sent to the picker.
Using performSelector:withObject:afterDelay: should work, and the reason it doesn't is probably that you put 1 as the delay for both invocations. Set it to 0 on the first and 2 (for example) on the second, and it should work.