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.
Related
My AppDelegate maintains a list of active window controllers to avoid ARC deallocating them too early. So I have a notification handler like this:
- (void) windowWillClose: (NSNotification*) notification {
[self performSelectorOnMainThread: #selector(removeWindowControllerInMainThread:)
withObject: windowController
waitUntilDone: NO];
}
- (void) removeWindowControllerInMainThread: (id) windowController {
[windowControllers removeObject: windowController];
}
I use the main thread because doing the handling on the notification thread risks deallocating the controller before it's ready.
Now, this works pretty well — except when there are animators currently running. I use animators in some places, through NSAnimationContext. I have looked at this QA, and the answer just isn't acceptable. Waiting for a while, just to get animation done, is really shoddy and not guaranteed to work; indeed it doesn't. I tried using performSelector:withObject:afterDelay, even with a larger delay than the current animation duration, and it still results in the animator running against nil objects.
What is the preferred way of doing controller cleanup like this? Not use NSAnimationContext but using NSAnimation instead, which has a stopAnimation method?
First, if some of your animations run indefinitely -- or for a very long time -- you're going to have to have a way to stop them.
But for things like implicit animations on views, you could simply use a completion method.
self.animating=YES;
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context){
[[v animator] setAlphaValue: 1];
} completionHandler:^{
self.animating=NO;
}];
Now, you only need to poll whether your animation is running and, if it's not running, proceed to close your window.
One nice way to do the polling is to set a timer with a fixed delay. If the animation is still running, just reset the timer and wait another interval.
Alternatively, you could send a notificaton from the completion handler.
I haven't used NSAnimationContext (always did this with NSAnimation, but mostly for historical reasons). But the typical way I like to managed things similar to this is to create short-lived retain loops.
Mark's answer is exactly the right kind of idea, but the polling is not required. The fact that you reference self in the completion handler means that self cannot deallocate prior to the completion handler running. It doesn't actually matter whether you ever read animating. ARC has to keep you around until the completion block runs because the block made a reference to you.
Another similar technique is to attach yourself to the animation context using objc_setAssociatedObject. This will retain you until the completion block runs. In the completion block, remove self as an associated object, and then you'll be free to deallocate. The nice thing about that approach is that it doesn't require a bogus extra property like animating.
And of course the final, desperate measure that is occasionally appropriate is to create short-lived self-references. For instance:
- (void)setImmortal:(BOOL)imortal {
if (immortal) {
_immortalReference = self;
}
else {
_immortalReference = nil;
}
}
I'm not advocating this last option. But it's good to know that it exists, and more importantly to know why it works.
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]
Purely by accident I discovered that calling [bar.view addSubview:[foo view]] doesn't work, but [bar.view addSubview:foo.view] does in the following code.
foo=[fooViewController alloc] initWithNibName:#"fooViewController" andBundle:nil];
[self.view addSubview:foo.view];//here's where I swap the two commands out
[foo aFunctionThatSendsAMessageToOneOfFoosSubViews];
(That last line is because foo has some sub-views that need to be set up prior to running -- notably, a UIWebView. If they haven't been instantiated before the message is sent, the message winds up going to nil. With foo.)
I thought these two were functionally identical -- that foo.view calls the same getter that [foo view] does, but in practice that's not the case; the dot syntax gets the desired results, while using the brackets winds up sending the message to nil.
If you'd asked me ten minutes ago, I would have told you the difference between the two expressions was 'syntax, and nothing else'. Given that I'm clearly wrong, I need to understand HOW I'm wrong or I'm going to stumble over it again.
They are functionally equivalent. I think this is a race condition. When you first call foo.view in that code, the view is not loaded yet, and a call is sent to [foo loadView]. You can't be sure that the view is loaded until [foo viewDidLoad] is called or foo.isViewLoaded == YES.
You need to wait make sure the view is loaded before performing any actions that rely on it, such as [foo aFunctionThatSendsAMessageToOneOfFoosSubViews].
In your current case, sometimes it is loading in time and sometimes it isn't.
if i'm not mistaken the problem is that [foo view] tries to call a method named view (and if you don't have it the return is nil)
on the other hand in the case of foo.view, view is a property of the class
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?
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.