Cocos2d-iPhone: "update" called after dealloc - iphone

So I have a subclass of a CCSprite object, and in its init method, I call:
[self scheduleUpdate]
I later release this object from its parent CCNode like so:
[self removeChild:sprite cleanup:YES];
In addition, I call [self unscheduleUpdate] in the sprite's dealloc method.
However, I'm getting a bad memory access, so it appears that the update method is still attempted after the object is released (I've narrowed it down to this, as it works perfectly if I comment out the [self scheduleUpdate] line.
Any ideas?

Found this post in an attempt to ask the same question. I tried unschedule update (within my init method as well) with no luck, but then realized that by moving the [self unscheduleUpdate]; to the actual update method (which is running continuously, unlike the init method) based on a condition it worked!
So, for those looking for copy paste, here's a progress bar example that I'm implementing from http://www.ccsprite.com/cocos2d/using-ccprogresstimer-cocos2d-example.html#HCB_comment_box
-(id) init
{
//initialize progress bar, make sure to add a file named green_health_bar.png to your
//resource folder
timer = [CCProgressTimer progressWithFile:#"green_health_bar.png"];
//set the progress bar type to horizontal from left to right
timer.type = kCCProgressTimerTypeHorizontalBarRL;
//initialize the progress bar to zero
timer.percentage = 0;
//add the CCProgressTimer to our layer and set its position
[self addChild:timer z:1 tag:20];
[timer setPosition:ccp(100, 280)];
[self scheduleUpdate];
}
and in your update method:
-(void)update:(ccTime)dt
{
//get progress bar
CCNode* node = [self getChildByTag:20];
timer.percentage += dt * 10;
if (timer.percentage >= 100)
{
[self gameOver]; //used to stop parallax and show gameover menu
[self unscheduleUpdate];
}
}
I usually don't allow forum reply emails, but feel free to ask questions via #russ152!

Hmm.. try not to use scheduleUpdate? I tried looking for self unscheduleUpdate but there is not such function in a CCNode.. You can try [self unscheduleAllselectors], which stops all selectors of the object , including the update selector, if you are not using the object anymore.. Or use custom selectors instead..

Related

Variables falling out of scope

I have a subclass of AVAudioPlayer and within that subclass I have a method for stopping the current player and (for reasons I won't explain) manually calling audioPlayerDidFinishPlaying like this:
// Handles stopping the player and calling audioPlayerDidFinishPlaying
- (void) stopPlayerForTimedRepeat {
// Stop the player
[self stop];
// Manually call the audio player callback
EditPlayListViewController *playlistController = [[EditPlayListViewController alloc] init];
[playlistController audioPlayerDidFinishPlaying:self successfully:YES];
[playlistController release];
}
However, when I call audioPlayerDidFinishPlaying manually like this, all of my variables in the original EditPlaylistViewController fall out of scope.
How do I avoid this so that I still have access to all of my original variables?
I figured out a better way to do this without manually calling audioPlayerDidFinishPlaying so that all the variables will still be within scope.
// Handles stopping the player and calling audioPlayerDidFinishPlaying
- (void) stopPlayerForTimedRepeat {
// Fast forward the call to the end, which will also call audioPlayerDidFinishPlaying
[self setCurrentTime:[self duration]];
}
The variable scope loses because you are creating a new object EditPlayListViewController *playlistController = [[EditPlayListViewController alloc] init]; in the method after stopping it.
Please declare the player in your .h file (like this EditPlayListViewController *playlistController; and allocate it in your viewDidLoad method (like playlistController = [[EditPlayListViewController alloc] init];.
and change your method like this,
- (void) stopPlayerForTimedRepeat
{
// Stop the player
[self stop];
// Manually call the audio player callback
[playlistController audioPlayerDidFinishPlaying:self successfully:YES];
[playlistController release];
}
Hope,it'll work for you, if not please tell me.

Using Cocos2D scheduleUpdate to delay loading

I have an algorithm that takes a few seconds to load some stuff, and I want to first set the string on a label to say "loading" before the actual loading begins. This is all within the same layer, this is not switching between scenes.
I thought I could simply do this:
-(void)startLoading{
[self unscheduleAllSelectors];//just in case the update is already scheduled
[self.loadingLabel setString:#"Loading...."];
[self scheduleUpdate];
}
Then, I have this:
-(void)update:(ccTime)delta{
[self unscheduleUpdate];
[self beginLoading];//another method that loads all the stuff
}
My understanding is that my method beginLoading should not run until the next frame. Thus my label should get updated correctly. However this is not happening. There is a slight freeze while all my assets get loaded and my label never gets updated before the loading begins.
Am I missing a step?
Nope ure not missing anything. I stopped fighting this and now use this kind of 'delayed' task catapult. It should make certain you will get a draw in the transition from the first to the second tick:
-(void) startLoading{
_loadTicker=0; // an NSUInteger iVar declared in the .h
[self schedule:#selector(tickOffLoading:)];
}
-(void) tickOffLoading:(ccTime) dt{
_loadTicker++;
if(_loadTicker==1) {
[self.loadingLabel setString:#"Loading...."];
} else {
[self unschedule:#selector(tickOffLoading:)];
[self beginLoading];
}
}

Activity Indicator when integrated into Searchbar does not display in iPhone SDK

In my iPhone app, I want to add activity indicator on top of a searchbar.
When it is searching it should display activity indicator.
I have added the activity indicator in XIB and created its outlet.
I am making it hide when the searching finishes, but Activity Indicator does not display.
Problem
I figured out that search function(say A)(where I animate the activity indicator) in turn calls another function(say B) so the main thread is being used in executing the function B. But for activity indicator to animate we require the main thread.
So I tried calling function B using performSelectorInBackGround:withObject method. Now when I click search the activity indicator is shown but the functionality of function B does not execute.
What can be a work-around for this?
There is not quite enough in your question to go on, but to start debugging, I would do the following.
Verify that the activity variably is really wired to the UIActivityIndicator you are creating in IB. (I would set a breakpoint on the setHidden: lines and make sure the variable is not null. Or throw an NSAssert(activity,#"Whoops! actity is null"); in there.)
If the variable is indeed set, I would start checking that it is in the right place in the view hierarchy. (I'd try doing a [self.view addSubview:activity] and see that it appears. You might have to replace it somewhere else.)
You might also want to try having it on by default in IB, until you have everything figured out.
Good Luck. Hope this helps.
Save yourself the hassle of creating a custom activity indicator and use the standard one that's available for you already - in the top bar. Also, IMO users tend to expect that one to spin when something is happening.
UIApplication* app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible = YES;
Obviously, set it to NO when your activity is over.
First of all, make sure you have #synthesize activity at the top of your .m file. Then in the viewDidLoad method, type activity.hidesWhenStopped = TRUE;. Next, in the method that is called when the search starts, type [activity startAnimating]; and [activity stopAnimating]; in the method when the searching stops.
try this:
set hidesWhenStopped = NO, so that is displayed all the time and then hide and show it manually. But the View should be set in IB to hidden first.
- (void)startActivityView {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
activity_view.hidden = NO;
[pool drain];
}
- (void)stopActivityView {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
activity_view.hidden = YES;
[pool drain];
}
- (void)doSomething {
[self performSelectorInBackground:#selector(startActivityView) withObject:nil];
// do some time consuming work
[self performSelectorInBackground:#selector(stopActivityView) withObject:nil];
}
Perhaps you have a view in front of your activity indicator? What if you always bring it to the front....
loadView = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
loadView.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
loadView.center = window.center;
loadView.opaque = NO;
[window addSubview: loadView];
[window bringSubviewToFront:loadView];
[loadView startAnimating];
I suggest that you use DSActivityView for showing your activity indicator. The source code can be found at Dejal blog.
Showing, and hiding, the activity view is a simple line of code.
[DSActivityView activityViewForView:self.view];
start animating the activity indicator and with a delay of 0.1 or 0.2 just call the other method u want.... i tried and it is working for me....
I have got the solution and it is as follows.
I just wrote the below line in Search button click event.
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
And defined the function threadStartAnimating: as follows:
-(void)threadStartAnimating:(id)data
{
[activityIndicator setHidden:NO];
[activityIndicator startAnimating];
}

Run a method after init in cocos2d

I have a loading screen that I initialise with a label to display the loading progression.
I want to call my DataManager AFTER initialising the loading screen, and then call a method to switch scenes. Here is my code:
-(id) init {
if((self=[super init]))
{
loadingLabel = ....
[self addChild:loadingLabel];
/***** This is what I want to call after the init method
//DataManager loads everything needed for the level, and has a reference to the
//loading screen so it can update the label
[[DataManager sharedDataManager] loadLevel:#"level1" screen:self];
//this will switch the scene
[self finishLoading];
*****/
}
return self;
}
-(void) setLoadingPercentage:(int) perc {
//changes the label
}
-(void) finishLoading {
[[CCDirector sharedDirector] replaceScene:[Game node]];
}
So I can't call the datamanager in the init, because the label will not get updated as content gets loaded, and I can't switch scenes in the init method. So How do I run my datamanger and finish loading AFTER the init? My plan was to set a schedule at an interval of 1 second that does this, but it doesn't seem right to have to wait a second.
EDIT: another way I could do it is to schedule at every frame and ask the datamanager where its at... This seems better already, since the datamanager wouldn't need a reference to the loading screen.
Any ideas?
You can use performSelector:withObject:afterDelay: to force the execution of the specified selector during the next iteration of the current threads run loop:
[self performSelector:#selector(finishLoading) withObject:nil afterDelay:0.0f];
The above answer is correct, but you should use the Cocos2d way to schedule methods to run later:
[self schedule:#selector(finishLoading) interval:0.1];
-(void)finishLoading
{
[self unschedule:#selector(finishLoading)];
//Do your stuff
}

connectionDidFinishLoading - how to force update UIView?

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!
As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.
While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.
If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!
Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.
The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!