Here is a piece of code that I'm having problems with:
-(IBAction) nextRegister {
[DSBezelActivityView newActivityViewForView:self.view.superview];
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegistry updateOnServer];
[self loadNewRegisterInfos];
[DSBezelActivityView removeViewAnimated:YES];
}
Line by line, here is what this snippet does (or should do):
Show a DSBezelActivityView (from this: http://www.dejal.com/developer/dsactivityview)
Previous position receives actual position (since this will move to the next)
Next is current + 1;
Updates record in server (this takes ~1s)
Loads new record and renders on screen (this takes 1~3s, tops)
This line removes the DSBezelActivityView.
So, when I tap the button to load next record, it is supposed to show the ActivityView, load the data, then update the screen and release the ActivityView.
But what happens is: It freezes the app for 3s, shows and dismisses the activity view in less than a second and then finishes the routine.
What is wrong?
[Edited]
Resolved! Its a thread issue.
Split among 4 methods:
-(IBAction) nextRegister {
[NSThread detachNewThreadSelector:#selector(openActivityView) toTarget:self withObject:nil];
[self viewNextRegister];
[self removeView];
}
-(void)viewNextRegister{
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegister setScoreOnServer];
// [NSThread detachNewThreadSelector:#selector(loadRegisterInfo) toTarget:self withObject:nil];
[self loadRegisterInfo];
}
-(void)openActivityView{
[DSBezelActivityView newActivityViewForView:self.view.superview];
}
-(void)removeView {
[DSBezelActivityView removeViewAnimated:YES];
}
Rather than a separate thread which can cause issues if you aren't careful you can also do:
-(void) doNextRegister {
previousPosition = (NSInteger *) currentPosition;
currentPosition = (NSInteger *) ((int)currentPosition + (int) 1 );
[currentRegistry updateOnServer];
[self loadNewRegisterInfos];
[DSBezelActivityView removeViewAnimated:YES];
}
-(IBAction) nextRegister {
[DSBezelActivityView newActivityViewForView:self.view.superview];
[self performSelector:#selector(doNextRegister) withObject:nil afterDelay:0.0];
}
Related
I am a little confused about something I was just trying for fun. I have written a little method called animateBars_V1 which uses an array of UIImageViews and alters the height of each UIImageView to show a changing set of coloured bars.
- (void)animateBars_V1 {
srandom(time(NULL));
for(UIImageView *eachImageView in [self barArray]) {
NSUInteger randomAmount = kBarHeightDefault + (random() % 100);
CGRect newRect;
CGRect barRect = [eachImageView frame];
newRect.size.height = randomAmount;
newRect.size.width = barRect.size.width;
newRect.origin.x = barRect.origin.x;
newRect.origin.y = barRect.origin.y - (randomAmount - barRect.size.height);
[eachImageView setFrame:newRect];
}
}
This works fine, I then added a UIButton with a UIAction for when the button is pressed. Each time the button is pressed animateBars_V1 is called and the coloured bars update.
- (IBAction)buttonPressed {
for(int counter = 0; counter<5; counter++) {
[self animateBars_V1];
NSLog(#"COUNTER: %d", counter);
}
}
My question is just for fun I decided that each time the button is pressed I would call animateBars_V1 5 times. What happens is that the bars don't change until after the loop has exited. This results in:
Screen as per storyboard
COUNTER: 0
COUNTER: 1
COUNTER: 2
COUNTER: 3
COUNTER: 4
Screen Updates
Is this the correct behaviour? I don't need a fix or workaround as this was just for fun, I was more curious what was happening for future reference.
If you are calling animateBars_V1 multiple times within a loop, the frames of the bars do get set multiple times, but before they can be rendered, animateBars_V1 gets called again, and the frames are set to a new position/size.
The call to render (drawRect: and related methods) doesn't occur until after the loop is finished - since it is an IBAction, it is by necessity called in the main thread, which means that all rendering is blocked until the code is completed.
There are of course several solutions to this. A simple method to do the multi-animation thing is to use UIView animateWithDuration:animations:completion: in the following manner:
- (IBAction)buttonPressed {
[self animateBarsWithCount:5];
}
- (void)animateBarsWithCount:(int)count
{
[UIView animateWithDuration:.25f animations:^{
[self animateBars_V1];
}completion:^(BOOL finished){
[self animateBarsWithCount:count - 1];
}];
}
//animateBars_V1 not repeated
Of course, if you simply wanted to run the animation one time, (but actually animated) you should do it like this:
- (IBAction)buttonPressed {
[UIView animateWithDuration:.25f animations:^{
[self animateBars_V1];
} completion:nil];
}
CrimsonDiego is right
you can try to delay each call with this:
- (IBAction)buttonPressed {
for(int counter = 0; counter<5; counter++) {
float ii = 1.0 * counter / 10;
[self performSelector:#selector(animateBars_V1) withObject:nil afterDelay:ii];
// [self animateBars_V1];
NSLog(#"COUNTER: %d", counter);
}
}
The problem is here
for(int counter = 0; counter<5; counter++) {
[self animateBars_V1];
NSLog(#"COUNTER: %d", counter);
}
This for loop is executed in nano seconds and your eye is not able to catch up that change as eye can detect only 1/16th of a second.
For testing what can run this code in a timer that runs five time.
Edited
Removed sleep call as it will sleep the main thread and everything will stop. So use Timer here
I am developing a 2d game in which on my game screen I have to implement a reverse timer from 30 sec till 0 sec and if a player does not move his character he will win otherwise he will lose and the game will be over.
This is my init method:
-(id)init
{
if(self==[super init])
{
self.isTouchEnabled=YES;
self.isAccelerometerEnabled=YES;
CGSize size=[[CCDirector sharedDirector] winSize];
screenwidth=size.width;
screenheight=size.height;
west_pic=[CCSprite spriteWithFile:#"west_pic.png"];
west_pic.anchorPoint=ccp(1, 1);
west_pic.scaleX=1.4;
west_pic.position=ccp(size.width, size.height);
[self addChild:west_pic];
label1=[CCLabelTTF labelWithString:#"Level One" fontName:#"Arial" fontSize:20];
label1.position=ccp(size.width/3.8,size.height/1.2);
[self addChild:label1];
label2=[CCLabelTTF labelWithString:#"Lives :" fontName:#"ArcadeClassic" fontSize:18];
label2.position=ccp(size.width/1.5,size.height/8.2);
[self addChild:label2];
player=[CCSprite spriteWithFile:#"player.png"];
player.position=ccp(size.width/1.7, size.height/2);
player.scale=0.2;
player.tag=2;
player.anchorPoint=ccp(1, 0);
[self addChild:player];
[self schedule:#selector(updateplayer:) interval:1.0f/60.0f];
}
return self;
}
for example, you can have an instance with number of seconds, that player must not to move character, then you must have ssome event methods to know when the character begins and stops movement, create updatable label(if you want to show the rest of the time to player) and schedule timer, which will decrease number of seconds. smth like this
- (void) onEnter
{
[super onEnter];
// if player character not move on start
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onExit
{
[self unscheduleAllSelectors];
[super onExit];
}
- (void) onCharacterStop
{
m_restTime = // your time left. in your case, 30 sec
// if you have time label
[self updateRestTimeLabel];
[self schedule:#selector(reduceRestTime) interval:1.f];
}
- (void) onCharacterBeginMovement
{
[self unscheduleSelector:#selector(reduceRestTime)];
}
- (void) reduceRestTime
{
m_restTime--;
// if you have time label
[self updateRestTimeLabel];
if( m_timeLeft == 0 )
{
[self unscheduleSelector:#selector(reduceRestTime)];
// your win code
}
}
- (void) updateRestTimeLabel
{
[m_timeLabel setString:[NSString stringWithFormat:#"Time left: %d", m_timeLeft]];
}
Scenario:
User taps a button asking for some kind of modification on address book.
A method is called to start this modification and an alert view is shown.
In order to show the alert view and keep the UI responsive, I used dispatch_queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
// Show the alert view
});
});
Start the process of address book modification using:
dispatch_async(modifyingAddressBookQueue, ^{});
Now, I want to provide the user with the ability to cancel the process anytime (of course before saving the address book). So when he taps the cancel button in the alert sheet, I want to access the dispatch block, set some certain BOOL to stop the process and revert the address book.
The problem is, you can't do that! you can't access the block and change any variable inside it since all variables are copied only once. Any change of variables inside the block while being executed won't be seen by the block.
To sum up: How to stop a going operation using a UI event?
Update:
The code for the process:
- (void) startFixingModification {
_fixContacts = YES;
__block BOOL cancelled = NO;
dispatch_queue_t modifyingAddressBookQueue;
modifyingAddressBookQueue = dispatch_queue_create(sModifyingAddressBookQueueIdentifier,
NULL);
dispatch_async(modifyingAddressBookQueue, ^{
for (NSMutableDictionary *contactDictionary in _contactArray) {
if (!cancelled) {
break;
}
i = i + 1;
BOOL didFixContact = [self fixNumberInContactDictionary:contactDictionary];
if (!didFixContact) {
_fixedNumbers = _fixedNumbers - 1;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[self setAlertViewProgress:i];
});
});
}
});
cancelledPtr = &cancelled;
}
Code for alertview (my own lib) delegate
- (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib.
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
In interface, I declare
BOOL* cancelledPtr;
Update 2:
It's getting really frustrating! for the following code
for (NSMutableDictionary *contactDictionary in _contactArray) {
NSLog(#"%d", _cancelModification);
if (_cancelModification) {
break;
}
}
if _cancelModification is set to YES, the for loop is broken and that's OK. Once I comment out the NSLog line, the _cancelModification is neglected when it changes to YES!
If you declare your BOOL using __block, then it can be changed outside of the block execution, and the block will see the new value. See the documentation for more details.
An example:
#interface SNViewController ()
{
BOOL* cancelledPtr;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
cancelledPtr = &cancelled;
}
- (IBAction)stop:(id)sender
{
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
#end
Alternatively, use an ivar in your class to store the BOOL. The block will implicitly make a copy of self and will access the ivar via that. No need for __block.
#interface SNViewController ()
{
BOOL cancelled;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
}
- (IBAction)stop:(id)sender
{
NSLog(#"stopping");
cancelled = YES;
}
#end
Approach 1
Create a custom dispatch_async method that returns a "cancelable" block.
// The dispatch_cancel_block_t takes as parameter the "cancel" directive to suspend the block execution or not whenever the block to execute is dispatched.
// The return value is a boolean indicating if the block has already been executed or not.
typedef BOOL (^dispatch_cancel_block_t)(BOOL cancelBlock);
dispatch_cancel_block_t dispatch_async_with_cancel_block(dispatch_queue_t queue, void (^block)())
{
__block BOOL execute = YES;
__block BOOL executed = NO;
dispatch_cancel_block_t cancelBlock = ^BOOL (BOOL cancelled) {
execute = !cancelled;
return executed == NO;
};
dispatch_async(queue, ^{
if (execute)
block();
executed = YES;
});
return cancelBlock;
}
- (void)testCancelableBlock
{
dispatch_cancel_block_t cancelBlock = dispatch_async_with_cancel_block(dispatch_get_main_queue(), ^{
NSLog(#"Block 1 executed");
});
// Canceling the block execution
BOOL success1 = cancelBlock(YES);
NSLog(#"Block is cancelled successfully: %#", success1?#"YES":#"NO");
// Resuming the block execution
// BOOL success2 = cancelBlock(NO);
// NSLog(#"Block is resumed successfully: %#", success2?#"YES":#"NO");
}
Approach 2
Defining a macro for executing a block asynchronously if a condition is validated:
#define dispatch_async_if(queue,condition,block) \
dispatch_async(queue, ^{\
if (condition == YES)\
block();\
});
- (void)testConditionBlock
{
// Creating condition variable
__block BOOL condition = YES;
dispatch_async_if(dispatch_get_main_queue(), condition, ^{
NSLog(#"Block 2 executed");
});
// Canceling the block execution
condition = NO;
// Also, we could use a method to test the condition status
dispatch_async_if(dispatch_get_main_queue(), ![self mustCancelBlockExecution], ^{
NSLog(#"Block 3 executed");
});
}
Try to apply the following code sample to your situation:
__block UIView * tempView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 220, 30)];
[tempView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:tempView];
[tempView release];
__block BOOL cancel = NO;
//点击之后就会开始执行这个方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
int i = 0;
while (i < 1000000000 && cancel == NO) {
i++;
}
NSLog(#"Task end: i = %d", i);
//这个不会执行,因为在之前,gcd task已经结束
[tempView removeFromSuperview];
});
//1s 之后执行这个方法
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"A GCD Task Start");
cancel = YES;
[tempView setBackgroundColor:[UIColor blackColor]];
});
I have my NSTimer embedded in a class that plays image sequences. Basically it loops and changes a UIImageView. Everything goes well if I let the image sequence finish but... on it's own if I try to stop the timer while it is playing I get a sigabrt. Edit: No longer a sigabrt but now a DeAlloc I can't explain.
The "stop" at the end of a frame sequence is the same stop I am calling mid sequence.
So what might cause an NSTimer to break mid function and DeAlloc. More to the point what might I look at to fix it.
Thanks.
I am using some example code from here: http://www.modejong.com/iOS/PNGAnimatorDemo.zip
Edit: I'll add what I believe to be the pertinent code here.
// Invoke this method to start the animation
- (void) startAnimating
{
self.animationTimer = [NSTimer timerWithTimeInterval: animationFrameDuration target: self selector: #selector(animationTimerCallback:) userInfo: NULL repeats: TRUE];
[[NSRunLoop currentRunLoop] addTimer: animationTimer forMode: NSDefaultRunLoopMode];
animationStep = 0;
if (avAudioPlayer != nil)
[avAudioPlayer play];
// Send notification to object(s) that regestered interest in a start action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStartNotification
object:self];
}
- (void) animationTimerCallback: (NSTimer *)timer {
if (![self isAnimating])
return;
NSTimeInterval currentTime;
NSUInteger frameNow;
if (avAudioPlayer == nil) {
self.animationStep += 1;
// currentTime = animationStep * animationFrameDuration;
frameNow = animationStep;
} else {
currentTime = avAudioPlayer.currentTime;
frameNow = (NSInteger) (currentTime / animationFrameDuration);
}
// Limit the range of frameNow to [0, SIZE-1]
if (frameNow < 0) {
frameNow = 0;
} else if (frameNow >= animationNumFrames) {
frameNow = animationNumFrames - 1;
}
[self animationShowFrame: frameNow];
// animationStep = frameNow + 1;
if (animationStep >= animationNumFrames) {
[self stopAnimating];
// Continue to loop animation until loop counter reaches 0
if (animationRepeatCount > 0) {
self.animationRepeatCount = animationRepeatCount - 1;
[self startAnimating];
}
}
}
- (void) stopAnimating
{
if (![self isAnimating])
return;
[animationTimer invalidate];
self.animationTimer = nil;
animationStep = animationNumFrames - 1;
[self animationShowFrame: animationStep];
if (avAudioPlayer != nil) {
[avAudioPlayer stop];
avAudioPlayer.currentTime = 0.0;
self->lastReportedTime = 0.0;
}
// Send notification to object(s) that regestered interest in a stop action
[[NSNotificationCenter defaultCenter]
postNotificationName:ImageAnimatorDidStopNotification
object:self];
}
Edit2: So I commented out an NSAssert in DeAlloc, commenting that out shed a bit more light. Now getting to self.animationTimer = nil; and saying *** -ImageAnimator setAnimationTimer:]: Message sent to deallocated instance.
DeAlloc is being called right when I invalidate the timer... so I'm a bit confused here.
I have a solution which is working but not optimal.
I just added another function to set the frame it's on to the last frame and it ends the sequence as I need.
But it doesn't solve the question of why it's doing it's crashing if I try to stop the sequence mid run.
Well, I wrote this code so I can assure you that it should be working just fine :) You need to invoke stopAnimating before you are done with the view, the only way you would have got the assert in dealloc is if the view was deallocated while it was still animating. That should not happen, which is exactly why there way an assert there. There is also a big comment explaining the assert, what is unclear about that?
- (void)dealloc {
// This object can't be deallocated while animating, this could
// only happen if user code incorrectly dropped the last ref.
NSAssert([self isAnimating] == FALSE, #"dealloc while still animating");
self.animationURLs = nil;
self.imageView = nil;
self.animationData = nil;
self.animationTimer = nil;
[super dealloc];
}
Just call:
[view stopAnimating];
Before you remove the view from its superview and everything will be just fine.
Please, I need help in making this code work. I intend to do an animation with a sprite I added using cocos2d and box2d in xcode. But for some odd reasons I cannot get the sprite to animate repeatedly.
This code builds successfully but animates only once. Can anyone help and tell me what I am not doing right?
The implementation file are as follows:
#import "Mosquito.h"
#import "Box2DHelpers.h"
#implementation Mosquito
#synthesize flyingAnim;
- (void) dealloc{
[flyingAnim release];
[super dealloc];
}
-(void)initAnimations {
flyingAnim = [self loadPlistForAnimationWithName:#"flyingAnim"
andClassName:NSStringFromClass([self class])];
[[CCAnimationCache sharedAnimationCache] addAnimation:flyingAnim
name:#"flyingAnim"];
}
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
id action = nil;
// id flyingAction = nil;
//CGPoint newPosition;
[self setCharacterState:newState];
switch (newState) {
case kStateIdle:
[self setDisplayFrame:
[[CCSpriteFrameCache sharedSpriteFrameCache]
spriteFrameByName:#"Mosquito_anim_1.png"]];
break;
case kStateFlying:
action = [CCAnimate actionWithAnimation:flyingAnim
restoreOriginalFrame:NO];
break;
case kStateTakingDamage:
action = [CCBlink actionWithDuration:1.0 blinks:3.0];
break;
default:
//CCLOG(#"Unhandled state %d in Mosquito", newState);
break;
}
if (action != nil) {
[self runAction:action];
}
}
- (id)initWithWorld:(b2World *)theWorld atLocation:(CGPoint)location {
if ((self = [super init])) {
world = theWorld;
[self setDisplayFrame:[[CCSpriteFrameCache
sharedSpriteFrameCache]
spriteFrameByName:#"Mosquito_anim_1.png"]];
gameObjectType = kMosquitoType;
characterHealth = 100.0f;
[self createBodyAtLocation:location];
[self initAnimations];
}
return self;
}
- (void) updateStateWithDeltaTime:(ccTime)deltaTime
andListOfGameObjects:(CCArray *)listOfGameObjects {
//CGPoint oldPosition = self.position;
if ((characterState == kStateDestroyed) &&
([self numberOfRunningActions] > 0)) {
return;
}
if (characterState != kStateFlying &&
[self numberOfRunningActions] == 0) {
[self changeState:kStateFlying];
}
}
#end
Thanks.
id repeatAnimation = [CCRepeatForever actionWithAction:action];
To repeat forever, you need to do that, otherwise you need to just do:
[self runAction:action];
again.
Also, you might want to consider not reassigning action to CCBlink and make another action and call
[self stopAllActions];
id blinkAction = [CCBlink actionWithDuration:1.0 blinks:3.0];
[self runAction:blinkAction];
This may Help you.
One of the easiest ways for sprite animation.
https://sites.google.com/site/rajanallathambi1/cocos2d-tutorials/sprite-animation-without-plist-file