userdata of a CCNode - iphone

I have a CCMenuItemSprite that is instantiate to call a function:
`
menuControl = [CCMenu menuWithItems: nil];
//Create the buttons
for(int i=1;i<17;i++)
{
Button *B_image = [Button buttonWithTexture:Sheet.texture type:i];
CCMenuItemSprite *menuButton = [CCMenuItemSprite itemFromNormalSprite:B_image selectedSprite:B_image target:self selector:#selector(generateEnemy:)];
NSNumber *Btype = [NSNumber numberWithInt:i];
menuButton.userData = Btype;
[menuControl addChild:menuButton];
}
[self addChild:menuControl];`
As you can see in the above, I'm using the menuButton.userData to pass in an NSNumber.
This is to "pass a parameter" to the generateEnemy function.
`-(void) generateEnemy:(CCMenuItemSprite*)sender
{
NSNumber *Btype = (NSNumber*)sender.userData;
int Etype = [Btype intValue];
enemy = [[Enemy alloc]init:ccp(300,0) Type:Etype];
}
It crashes at this: NSNumber *Btype = (NSNumber*)sender.userData;
It works for number 1-12. But 13 and above, it crashes.

I've solve this problem quite a while back.
It crashes due to this concept here.
How I pass data is:
for(int i=1;i<17;i++)
{
Button *B_image = [Button buttonWithTexture:Sheet.texture type:i];
CCMenuItemSprite *menuButton = [CCMenuItemSprite itemFromNormalSprite:B_image selectedSprite:B_image target:self selector:#selector(generateEnemy:)];
menuButton.tag = i;
[menuControl addChild:menuButton];
}
In the generateEnemy function:
-(void) generateEnemy:(id) sender
{
enemy = [[Enemy alloc]init:ccp(300,0) Type:[sender tag]];
}

Related

how to properly use GCD to do long process on back thread and UI process/update on maion thread

i am working on this piece of code as this blocks main thread reported by time profiler.
OrderModule *module = [OrderModule sharedModule];
for(Modifier *modifier in modifierLists)
{
int merge = [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];//long process database interaction
for(ModifierItem *mod_product in modifier.activeModifiers)
{
NSString *modifierProductName = mod_product.productName;
if (merge == 1) {
modifierProductName = [modifierProductName stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%# - ",product.mProductName] withString:#""];
}
numberOfModifiers++;
UILabel *modifierNameLabel = [[UILabel alloc] init];
// [modifierNameLabel setBackgroundColor:[UIColor redColor]];
modifierNameLabel.lineBreakMode = UILineBreakModeWordWrap;
modifierNameLabel.numberOfLines = 0;
modifierNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
modifierNameLabel.numberOfLines = 0;
[modifierNameLabel setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierNameLabel.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierNameLabel.frame = CGRectMake(x, y, 140, 35);
modifierNameLabel.frame = CGRectMake(x, y, 120, 35);
modifierNameLabel.text = modifierProductName;
[orderDetailRow addSubview:modifierNameLabel];
UILabel *modifierAmount = [[UILabel alloc] init];
[modifierAmount setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierAmount.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+30, y, 100, 35);
modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+6, y, 100, 35);
[orderDetailRow addSubview:modifierAmount];
// modifiersCount++;
amountPrice=amountPrice+([mod_product.mExtraCost floatValue]*count);
[modifierAmount setText:[Utils currencyWithSymbol:[NSString stringWithFormat:#"%.02f",(([mod_product.mExtraCost floatValue]*count)+ 0.00001)]]];//simple manipulation
if ([mod_product.mExtraCost floatValue]<=0) { //3.0 changes
[modifierAmount setHidden:YES];
}
y = y + 25;
}
numberOfModifiers++;
}
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
i have tried with this way:
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
{
OrderModule *module = [OrderModule sharedModule];
float __block Y = y;
for(Modifier *modifier in modifierLists)
{
int __block merge;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
merge= [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^
{
for(ModifierItem *mod_product in modifier.activeModifiers)
{
NSString *modifierProductName = mod_product.productName;
if (merge == 1) {
modifierProductName = [modifierProductName stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%# - ",product.mProductName] withString:#""];
}
numberOfModifiers++;
UILabel *modifierNameLabel = [[UILabel alloc] init];
// [modifierNameLabel setBackgroundColor:[UIColor redColor]];
modifierNameLabel.lineBreakMode = UILineBreakModeWordWrap;
modifierNameLabel.numberOfLines = 0;
modifierNameLabel.lineBreakMode = NSLineBreakByWordWrapping;
modifierNameLabel.numberOfLines = 0;
[modifierNameLabel setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierNameLabel.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierNameLabel.frame = CGRectMake(x, y, 140, 35);
modifierNameLabel.frame = CGRectMake(x, y, 120, 35);
modifierNameLabel.text = modifierProductName;
[orderDetailRow addSubview:modifierNameLabel];
UILabel *modifierAmount = [[UILabel alloc] init];
[modifierAmount setFont:DEFAULT_FONT(DEFAULT_FONTSIZE_MODIFIERNAME)];
modifierAmount.textColor = [UIColor colorWithRed:125.0f/255.0f green:127.0f/255.0f blue:131.0f/255.0f alpha:1.0f];
// modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+30, y, 100, 35);
modifierAmount.frame = CGRectMake(CGRectGetMaxX(modifierNameLabel.frame)+6, y, 100, 35);
[orderDetailRow addSubview:modifierAmount];
// modifiersCount++;
amountPrice=amountPrice+([mod_product.mExtraCost floatValue]*count);
[modifierAmount setText:[Utils currencyWithSymbol:[NSString stringWithFormat:#"%.02f",(([mod_product.mExtraCost floatValue]*count)+ 0.00001)]]];
if ([mod_product.mExtraCost floatValue]<=0) { //3.0 changes
[modifierAmount setHidden:YES];
}
Y = Y + 25;
}
numberOfModifiers++;
});
});
}
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
}
but setUpActiveModifierWithX:x is called before the for loop ends execution, how do i call setUpActiveModifierWithX:x method after that for loop.
Thanks for any suggestion
I'd step back from what you are doing, and learn how to use a TableViewController or CollectionViewController.
You seem to be creating an awful lot of subviews, which is an expensive operation. Table views and collection views avoid this by reusing the same views over and over again, just displaying different values. Instead of expensive views you use much cheaper cells. You also automatically only handle things that are actually visible, so if you have thousand items, only the dozen on your screen will take CPU time.
The easiest fix is putting it after the last statement in the block executed on the main thread:
EDIT
This below won't work, and unfortunately, there is no "easy fix" without using a third partly library. I leave the answer here for demonstrating how things quickly get more complex with asynchronous problems.
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
{
OrderModule *module = [OrderModule sharedModule];
float __block Y = y;
for(Modifier *modifier in modifierLists)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int merge= [module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^
{
for(ModifierItem *mod_product in modifier.activeModifiers)
{
...
}
numberOfModifiers++;
[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
});
});
}
}
It turned out that this seemingly easy task isn't actually that easy. I couldn't find a solution that's easier than the following, without resorting to a third party library which makes such tasks much easier (I'll propose a solution using an utility library later).
So, here is an approach which uses a recursive block.
WARNING
Recursive blocks, are tricky - and I wouldn't recommend to use them unless you know why and how you apply this technique.
The code snippet shows the principal approach, but your original problem needs to be adjusted.
Your method
-(void)setUpModifierWithX:(float)x andy:(float)y Row:(OrderDetailRow *)orderDetailRow andModifierList:(NSMutableArray *)modifierLists andProduct:(Product *)product
becomes now an asynchronous method:
-(void) foo
{
NSArray* array = #[#"a", #"b", #"c"]; // your input array, e.g. "modifierLists"
NSMutableArray* marray = [array mutableCopy]; // we need a mutable array here
typedef void (^completion_t)(id result);
typedef void (^block_t)(NSMutableArray*, completion_t);
// setting up block variable suitable for using that block recursively:
__block __weak block_t weak_block;
block_t block;
weak_block = block = ^(NSMutableArray*array, completion_t completion) {
// Check the termination condition for the "loop"
if ([array count] == 0) {
if (completion) {
completion(#"Finished");
}
return;
}
id item = array[0];
[array removeObjectAtIndex:0];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// process "item" on a secondary thread (private queue):
int merge = [item characterAtIndex:0]; //[module getModifierMergeOption:modifier.mModifierId productId:product.mProductId];
dispatch_async(dispatch_get_main_queue(), ^{
// On the main thread, setup the UIKit objects:
NSLog(#"%d", merge);
/*
for(ModifierItem *mod_product in modifier.activeModifiers)
{
...
}
numberOfModifiers++;
*/
// Continue the loop:
weak_block(array, completion); //looks scary!
});
});
};
// Start the loop:
block(marray, ^(id result){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"result: %#", result);
//[self setUpActiveModifierWithX:x andy:y Row:orderDetailRow andModifierList:modifierLists andProduct:product];
});
});
}
Regarding recursive blocks, see this question ARC behavior within a recursive block and this answer on SO: https://stackoverflow.com/a/19626441/465677

Incompatible pointer types sending 'class' to parameter of type CCMenuItem

I am making a 14 year old kid making a game in cocos2d. I am pretty new to cocos2d. I want to display the same coin sprite next to each other to make a pattern.
So I added this in my main gameplay layer:
- (void)coinPatterns {
menu = [CCMenu menuWithItems:[Coins class], [Coins class], self, nil];
[menu alignItemsHorizontally];
[self addChild:menu];
}
And this is how I am initializing menu:
[[GameMechanics sharedGameMechanics] setSpawnRate:50 forMonsterType:menu];
This is what is in my coins class:
- (id)initWithMonsterPicture
{
self = [super initWithFile:#"coin.png"];
if (self)
{
CGRect screenRect = [[CCDirector sharedDirector] screenRect];
CGSize spriteSize = [self contentSize];
posX = screenRect.size.width + spriteSize.width * 0.5f;
posY = 150;
self.initialHitPoints = 1;
self.animationFrames = [NSMutableArray array];
[self scheduleUpdate];
inAppCurrencyDisplayNode.score = [Store availableAmountInAppCurrency];
}
coinValue = 3;
return self;
}
- (void)spawn
{
self.position = CGPointMake(posX, posY);
self.visible = YES;
}
- (void)gotCollected {
self.visible = FALSE;
self.position = ccp(-MAX_INT, 0);
[Store addInAppCurrency:coinValue];
}
I keep getting a Incompatible pointer types sending 'class' to parameter of type 'CCMenuItem'. Can someone please tell me how I should change the code so that this works?
Thanks!
menuWithItems: takes an array of CCMenuItem objects, you are sending a class itself. I don't know what the class Coin does, but if the purpose is to show an image and then do something when it's tapped I suggest you to do this:
CCMenuItem *myCoin1 = [CCMenuItemImage
itemFromNormalImage:#"coin.png" selectedImage:#"coinSelected.png"
target:self selector:#selector(coin1WasTapped:)];
CCMenuItem *myCoin2 ...
menu = [CCMenu menuWithItems: myCoin1, myCoin2, myCoin3, ..., nil];
You should create a method coin1WasTapped: that will be called when the coin was tapped, you can "collect" the coins here. Maybe remove them from the menu or an animation.
If you are going to create many coins I suggest you to use a for loop to create them all in an array. This way it will be easier to manipulate later.
This tutorial is really good, it can help you to understand better what you need to do and how to do it.
Good luck!

How do i toggle UIButton using currentTitle in XCode

To the point, i have 10 button and i write a number in Title inside every button, that is 0 to 9. My problem in here is, how do i toggle a button function, just say when i press a number 0, the value will show in showLabel.text is 0 and the button is in selected state but when i push it again, the number is gone in showLabel.text and the button is change back to normal state. I using this below code but it can't solve my problem. I mean, just say i selected number "1, 5, 7, 8" and i want to deselect number 5, using below code my showLabel will replace all number to none not to be "1, 7, 8". So i think this is not good idea using this code.
-(IBAction)numberBtn:(UIButton *)sender
{
UIButton *button = (UIButton *)sender;
[button setTitle:#"X" forState:UIControlStateSelected];
button.selected = !button.selected;
if (button.selected)
{
number = sender.currentTitle;
showLabel.text = [showLabel.text stringByAppendingFormat:number];
}
else
{
showLabel.text = [NSString stringWithFormat:#""];
}
}
Is there anyway to do that? Happy holiday.
Well. A think it will be good idea to make an array of 10 Boolean values and every time your button has been pressed - you just need to toggle corresponded value in your Boolean array and update your label using "for" loop:
for (int nn=0; nn<10; ++nn) if (boolVals[nn]) [myString appendFormat #"%d ", nn];
Do this instead:
- (IBAction)numberBtn:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected)
{
NSString *number = sender.currentTitle;
showLabel.text = [showLabel.text stringByAppendingString:number];
}
else
{
NSString *newString;
NSString *number = sender.currentTitle;
NSString *string = showLabel.text;
for (int i = 0; i<string.length; ++i) {
NSString *sub = [string substringWithRange:(NSRange){i, 1}];
if ([sub isEqualToString:number]) {
newString = [string substringToIndex:i];
newString = [newString stringByAppendingString:[string substringFromIndex:i+1]];
}
}
showLabel.text = newString;
}
[sender setTitle:#"X" forState:UIControlStateSelected];
}

How to passing class argument to selector in objective C?

I want to set the class tile value by click on the button
if click onPlus it will have a string value = #"+"
if click onMinus it will have a string value = #"-"
here are the code
//Class Tile
#interface Tile : TouchableNode {
NSString *val;
}
-(void) setVal:(NSString *)v
{
val = v;
}
-(NSString *) getVal
{
return val;
}
And the in another class
I have a code like this
for(Tile *tile in player)
{
if (tile.getVal == #"P") {
if (pauseStatus == 0) {
pauseStatus = 1;
[[CCDirector sharedDirector] pause];
CGSize size = [[CCDirector sharedDirector] winSize];
pauseLayer=[[CCLayer alloc] init];
pauseLayer.anchorPoint=ccp(0,0);
pauseLayer = [CCLayerColor layerWithColor: ccc4(0, 0, 255, 125) width: 300 height: 150];
pauseLayer.position = ccp(size.width/2, size.height/2);
pauseLayer.isRelativeAnchorPoint = YES;
[self addChild: pauseLayer z:8];
//Here are 2 two button that when user click it will have #"+" value or #"-"
plusBtn = [CCMenuItemImage itemFromNormalImage:#"plus.png" selectedImage:#"plus.png" target:self selector:#selector(onPlus:)];
minusBTn = [CCMenuItemImage itemFromNormalImage:#"minus.png" selectedImage:#"minus.png" target:self selector:#selector(onMinus:)];
pauseMenu = [CCMenu menuWithItems:plusBtn, minusBTn, nil];
[pauseMenu alignItemsHorizontally];
[self addChild:pauseMenu z:10];
}
}
}
and I have onPlus & onMinus method that I want to send object tile to that method
-(void)onPlus:(Tile *) set
{
NSString *plus = #"+";
[set setVal:plus];
}
-(void)onMinus:(Tile *) set
{
NSString *minus = #"-";
[set setVal:minus];
}
How to pass the object tile to the method??
Or it have another way to make it??
CCMenuItemImage inherits from CCNode, which has a void* userData property. If you assign tile to userData, you could fetch it back from the (id)sender in your onPlus/onMinus methods.
plusBtn = [CCMenuItemImage itemFromNormalImage:#"plus.png" selectedImage:#"plus.png" target:self selector:#selector(onPlus:)];
plusBtn.userData = (void*)tile; // You may need a bridge cast in ARC
minusBTn = [CCMenuItemImage itemFromNormalImage:#"minus.png" selectedImage:#"minus.png" target:self selector:#selector(onMinus:)];
minusBTn.userData = (void*)tile;
-(void)onPlus:(id)senderObj {
CCNode *sender = (CCNode*)senderObj;
Tile *myTile = (Tile*)sender.userData; // Again you may need a bridge cast here
}
If you want to send a Tile object to your onPlus handler, just subclass CCMenuItemImage and make a Tile property:
#interface MyCustomCCMenuItemImage : CustomCCMenuItemImage
#property (nonatomic, retain) Tile* tile;
#end
Here's a rough example of what your code might look like:
- (void)someMethod
{
for(Tile *tile in player)
{
plusBtn = [MyCustomCCMenuItemImage itemFromNormalImage:#"plus.png" selectedImage:#"plus.png" target:self selector:#selector(onPlus:)];
plusBtn.tile = tile ;
}
}
//Your handler
-(void)onPlus:(id)sender
{
Tile *myTile = sender.tile;
}
For string comparisons, you're better off using
[tile.getVal isEqualToString:#"P"]
Since the method you use only works if it's the exact same string (in the same memory location), as opposed to an equivalent string elsewhere .

iPhone Developer's Cookbook: ModalAlert Frozen

I've used a recipe from the iPhone Developer's Cookbook called ModalAlert in order to get some text from a user; however, when the alert is shown, the keyboard and buttons are frozen. Here is the code for the modal alert.
+(NSString *) textQueryWith: (NSString *)question prompt: (NSString *)prompt button1: (NSString *)button1 button2:(NSString *) button2
{
// Create alert
CFRunLoopRef currentLoop = CFRunLoopGetCurrent();
ModalAlertDelegate *madelegate = [[ModalAlertDelegate alloc] initWithRunLoop:currentLoop];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:question message:#"\n" delegate:madelegate cancelButtonTitle:button1 otherButtonTitles:button2, nil];
// Build text field
UITextField *tf = [[UITextField alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 260.0f, 30.0f)];
tf.borderStyle = UITextBorderStyleRoundedRect;
tf.tag = TEXT_FIELD_TAG;
tf.placeholder = prompt;
tf.clearButtonMode = UITextFieldViewModeWhileEditing;
tf.keyboardType = UIKeyboardTypeAlphabet;
tf.keyboardAppearance = UIKeyboardAppearanceAlert;
tf.autocapitalizationType = UITextAutocapitalizationTypeWords;
tf.autocorrectionType = UITextAutocorrectionTypeNo;
tf.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
// Show alert and wait for it to finish displaying
[alertView show];
while (CGRectEqualToRect(alertView.bounds, CGRectZero));
// Find the center for the text field and add it
CGRect bounds = alertView.bounds;
tf.center = CGPointMake(bounds.size.width / 2.0f, bounds.size.height / 2.0f - 10.0f);
[alertView addSubview:tf];
[tf release];
// Set the field to first responder and move it into place
[madelegate performSelector:#selector(moveAlert:) withObject:alertView afterDelay: 0.7f];
// Start the run loop
CFRunLoopRun();
// Retrieve the user choices
NSUInteger index = madelegate.index;
NSString *answer = [[madelegate.text copy] autorelease];
if (index == 0) answer = nil; // assumes cancel in position 0
[alertView release];
[madelegate release];
return answer;
}
Thanks!
You should probably check whether a UITextField's userInteractionEnabled property defaults to YES or NO.
// Put the modal alert inside a new thread. This happened to me before, and this is how i fixed it.
- (void)SomeMethod {
[NSThread detachNewThreadSelector:#selector(CheckCurrentPuzzle) toTarget:self withObject:nil]; }
-(void) CheckCurrentPuzzle {
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];
// code that should be run in the new thread goes here
if ([gameBoard AreAllCellsFilled]) {
if ([gameBoard FilledWithoutWin]) {
//only show this message once per puzzle
if (![currentPuzzle showedRemovalMessage]) {
NSArray *buttons = [NSArray arrayWithObject:#"Yes"];
if ([ModalAlert ask:#"blah blah blah" withTitle:#"Incomplete Puzzle" withCancel:#"No" withButtons:buttons] == 1) {
NSLog(#"Remove The Incorrect Cells");
[gameBoard RemoveIncorrect];
} else {
[gameSounds.bloop2 play];
}
}
} else {
if ([gameBoard IsBoardComplete]) {
[self performSelectorOnMainThread:#selector(WINNER) withObject:nil waitUntilDone:false];
}
}
}
[pool2 release];
}
-(void) WINNER {
//ladies and gentleman we have a winner
}
I had a problem similar to this in my educational game QPlus. It bugged me because I had the "exact" same code in two related apps, and they did not have the bug. It turned out that the bug was because the selector method was not declared in the header file. I am working in Xcode 4.2.
Details below:
In .m:
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(emailLabelPressed)];
tapRecognizer.numberOfTapsRequired = 1;
[aLabel addGestureRecognizer:tapRecognizer];
[aLabel setUserInteractionEnabled:YES];
And later in the .m:
(void)emailLabelPressed {
//details
}
That works just fine in the simulator, but on an actual device the email interface presented modally will not edit. You can send or save as draft but no editing.
Then add this to the .h file:
(void)emailLabelPressed;
And voila, it works on the device. Of course this was the difference with the related apps - they both had the method declared in the header file. I would classify this as an iOS bug, but being such a novice developer I wouldn't presume to know.
Based on this, you may want to verify that your selector method moveAlert: is declared in your header file.
Enjoy,
Damien