How to delay the appearance of sprites? - objective-c

I am almost done with the app I'm currently making but I'm having a hard time trying to figure out one feature I need for it. I have 5 different sprites that drop on the screen on random intervals and positions. My problem is I need to tell my code that for a certain amount of time, for example 20 seconds, only 1 of the sprites would appear at a time. Then after 20 seconds, more would start dropping at the same time. Here's how my method looks like for dropping the sprites:
//The init method
-(id) init
{
if((self=[super init])) {
//Enable touch
self.isTouchEnabled = YES;
//Allocate and initialise
sprites = [[NSMutableArray alloc]init];
CGSize winSize = [[CCDirector sharedDirector]winSize];
//Add the sprites
[self schedule:#selector(scheduleDrop:)interval:2.0];
}
return self;
}
//Method to drop sprites
-(void)spriteDrop
{
CGSize winSize = [[CCDirector sharedDirector]winSize];
int RandomX = (arc4random() % 200);
NSString *strSprite = #"1.png";
switch(arc4random() % 5){
case 1:
strSprite = #"1.png";
break;
case 2:
strSprite = #"2.png";
break;
case 3:
strSprite = #"3.png";
break;
case 4:
strSprite = #"4.png";
break;
case 5:
strSprite = #"5.png";
break;
}
sprite = [CCSprite spriteWithFile:strSprite];
sprite.position = ccp(RandomX, 500);
sprite.scaleX = 40 / sprite.contentSize.width;
sprite.scaleY = 150 / winSize.height;
int posMinY = sprite.contentSize.width / 2;
int posMaxY = winSize.height - sprite.contentSize.height / 2;
int range = posMaxY - posMinY;
int actual = (arc4random()%range);
currentPos = actual;
[self addChild:baby];
int minDur = 2.0;
int maxDur = 5.0;
int rangeDur = maxDur - minDur;
int actualDur = (arc4random()%rangeDur) + minDur;
id drop = [CCMoveTo actionWithDuration:actualDur position:ccp(actual, -sprite.contentSize.height/2)];
id dropDone = [CCCallFuncN actionWithTarget:self selector:#selector(spriteDropDone:)];
[sprite runAction:[CCSequence actions:drop, dropDone, nil]];
sprite.tag = 1;
[sprites addObject: sprites];
}
//This is the method that schedules the drop
-(void)scheduleDrop:(ccTime)dt
{
[self spriteDrop];}
I hope there's somebody who would be able to help me with this.

Not sure if you want the number of sprites being dropped to change or the type (visually) to change over time.
One solution would be to have an ivar _numberOfSpritesToBeDropped. This could be increased in a method controlled by a scheduler at the requested time interval (e.g. 20 seconds). You'd then simply use this ivar in the method controlling the dispersing of the sprites...
If you simply want to add different kind of sprites every 20th. seconds you would simply change the random statement to
switch(arc4random() % _numberOfSpritesToBeDropped)...
Side note: your spriteDrop method seems to be doing more than simply dropping the sprites and it would certainly warrant to be split into two different methods.
Based on our discussion below, something like this perhaps:
NSInteger _maximumCurrentNumberOfSpritesAllowed;
-(id) init{
self = [super init];
if(self) {
[self setup];
}
return self;
}
-(void) setup{
self.isTouchEnabled = YES;
sprites = [[NSMutableArray alloc]init];
_maximumCurrentNumberOfSpritesAllowed = 1;
[self schedule:#selector(scheduleDrop:)interval:2.0];
}
-(void)spriteDrop{
if (sprites.count < _maximumCurrentNumberOfSpritesAllowed) {
CGSize winSize = [[CCDirector sharedDirector]winSize];
NSString *spriteName = [self randomSpriteName]; // I'd consider doing something similar with the position etc. as well
sprite = [CCSprite spriteWithFile:spriteName];
int RandomX = (arc4random() % 200);
sprite.position = ccp(RandomX, 500);
sprite.scaleX = 40 / sprite.contentSize.width;
sprite.scaleY = 150 / winSize.height;
int posMinY = sprite.contentSize.width / 2;
int posMaxY = winSize.height - sprite.contentSize.height / 2;
int range = posMaxY - posMinY;
int actual = (arc4random()%range);
currentPos = actual;
[self addChild:baby];
int minDur = 2.0;
int maxDur = 5.0;
int rangeDur = maxDur - minDur;
int actualDur = (arc4random()%rangeDur) + minDur;
id drop = [CCMoveTo actionWithDuration:actualDur position:ccp(actual, -sprite.contentSize.height/2)];
id dropDone = [CCCallFuncN actionWithTarget:self selector:#selector(spriteDropDone:)];
[sprite runAction:[CCSequence actions:drop, dropDone, nil]];
sprite.tag = 1;
[sprites addObject: sprites];
}
}
-(NSString *)randomSpriteName{
NSString *strSprite;
switch(arc4random() % 5){
case 1:
strSprite = #"1.png";
break;
case 2:
strSprite = #"2.png";
break;
case 3:
strSprite = #"3.png";
break;
case 4:
strSprite = #"4.png";
break;
case 5:
strSprite = #"5.png";
break;
default:
strSprite = #"1.png";
break;
}
return strSprite;
}
// Call this method with a scheduler at whatever interval you'd like
-(void) increaseMaximumNumberOfSpritesAllowed{
_maximumCurrentNumberOfSpritesAllowed++;
}
//This is the method that schedules the drop
-(void)scheduleDrop:(ccTime)dt{
[self spriteDrop];
}

Related

Objective-C append image thumbnails onto the end of a view

I'm trying to append images onto a view and have them display a thumbnail in a nice neat row. The issue I'm having is that it appears all images simply get added on top on each other in the corner as only the last image in the array is dispayed.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.notesField.delegate = self;
NSString *userName = ((ontracAppDelegate*)[UIApplication sharedApplication].delegate).userName;
self.textField.text = [NSString stringWithFormat:#"Date Created: %# \nCreated By: %#",[NSDate date], userName];
if (self.noteText != nil) {
self.notesField.text = self.noteText;
self.notesField.editable = NO;
self.textLabel.hidden = TRUE;
}
[self.view resignFirstResponder];
if ([self.imageArray count] != 0) {
for (UIImage *image in imageArray) {
NSLog(#"Image %#", image);
[self addImageToScreen:image];
}
}
NSLog(#"imageArray %#", imageArray);
NSLog(#"self.textField.text %#", self.textField.text );
NSLog(#"self.notesField.text %#", self.notesField.text);
}
-(void) addImageToScreen:(UIImage*) image;
{
int adjustHeight = 0;
int adjustWidth = 10;
int imagesInARow = 7;
int imageHeight = 75;
int imageWidth = 75;
int count = [self.imageArray count];
self.numberOfPhotosLabel.text = [NSString stringWithFormat:#"%i",count];
if (count > 1 && count < imagesInARow) {
adjustHeight = 0;
adjustWidth = (20 * [self.imageArray indexOfObject:image]) + ([self.imageArray indexOfObject:image] * imageWidth);
}
UIButton* container = [[UIButton alloc] init ];
CGRect frame = CGRectMake(adjustWidth, 5, imageWidth, imageHeight);
[container setTag:[self.imageArray indexOfObject:image]];
[container setImage:image forState:UIControlStateNormal];
[container setFrame:frame];
[container addTarget:self action:#selector(displayFullScreenImage:) forControlEvents:UIControlEventAllTouchEvents];
UIStackView *photosView = [[UIStackView alloc] initWithFrame:frame];
container.frame = photosView.bounds;
[photosView addSubview:container];
[self.view addSubview:photosView];
}
Use addArrangedSubview of UIStackView instead of addSubview.
And you should add all the Buttons to the same UIStackView. So, add a property for the UIStackView to your class, initialize it in viewDidLoad (or in the storyboard) and add the buttons to that stack view in addImageToScreen.

SpriteKit Add node in empty location with duration

I need some help.
I am developing a game and I have two questions.
Quick explanation of game :
There are three different nodes (shark (player), water ball and brick )
When the game starts there are one shark, four water balls and eight bricks.
when shark contacts with water ball, player earns point, water ball disappears and game adds another water ball in random location,
When shark contacts with brick shark dies and disappear from the screen.
My questions are;
First : When we hit the water ball how do we add the water ball in empty location. How do I make sure new water ball will be in different location then bricks and other water balls?
Second:
I want to wait a few seconds before I add the new water ball. How do I do that?
Here is some part of my code.
Thank you,
-(void) addWaterBallTopLeft {
WaterBall *waterBall = [WaterBall waterBallAtPosition:CGPointMake
([Util randomWithMin:(self.frame.size.width/10) + self.waterBall.size.width max:self.frame.size.width/2-self.brickT.size.width],
[Util randomWithMin:(self.frame.size.height- (self.frame.size.height/4)) + self.brickW.size.height max:self.frame.size.height-20])];
waterBall.name = #"WATERBALLTL";
[self addChild:waterBall ];
}
-(void) addWaterBallTopRight {
WaterBall *waterBall = [WaterBall waterBallAtPosition:CGPointMake([Util randomWithMin:self.frame.size.width/2+20 max:self.frame.size.width-20], [Util randomWithMin:(self.frame.size.height- (self.frame.size.height/4)-20) max:self.frame.size.height-20])];
waterBall.name = #"WATERBALLTR";
[self addChild:waterBall ];
}
-(void) addWaterBallBottomLeft {
WaterBall *waterBall = [WaterBall waterBallAtPosition:CGPointMake([Util randomWithMin:20 max:self.frame.size.width/2-20], [Util randomWithMin:(self.frame.size.height/2)+20 max:(self.frame.size.height- (self.frame.size.height/4)-20)])];
waterBall.name = #"WATERBALLBL";
[self addChild:waterBall];
}
-(void) addWaterBallBottomRight {
WaterBall *waterBall = [WaterBall waterBallAtPosition:CGPointMake([Util randomWithMin:self.frame.size.width/2+20 max:self.frame.size.width-20], [Util randomWithMin:(self.frame.size.height/2)+20 max:(self.frame.size.height- (self.frame.size.height/4)-20)])];
waterBall.name = #"WATERBALLBR";
[self addChild:waterBall];
}
-(void) addBrick {
BrickW *brickW1 = [BrickW BrickWAtPosition:CGPointMake( (self.frame.size.width/12)* 1 , self.frame.size.height - self.frame.size.height * 0.25) ];
[self addChild:brickW1];
BrickW *brickW2 = [BrickW BrickWAtPosition:CGPointMake( (self.frame.size.width/12)* 3 , self.frame.size.height - self.frame.size.height * 0.25) ];
[self addChild:brickW2];
BrickW *brickW3 = [BrickW BrickWAtPosition:CGPointMake( (self.frame.size.width/12)* 9, self.frame.size.height - self.frame.size.height * 0.25)];
[self addChild:brickW3];
BrickW *brickW4 = [BrickW BrickWAtPosition:CGPointMake( (self.frame.size.width/12)* 11, self.frame.size.height - self.frame.size.height * 0.25)];
[self addChild:brickW4];
BrickT *brickT1 = [BrickT BrickTAtPosition:CGPointMake( self.frame.size.width/2 , (self.frame.size.height / 24)*23 )];
[self addChild:brickT1];
BrickT *brickT2 = [BrickT BrickTAtPosition:CGPointMake( self.frame.size.width/2 , (self.frame.size.height / 24)*21 ) ];
[self addChild:brickT2];
BrickT *brickT3 = [BrickT BrickTAtPosition:CGPointMake( self.frame.size.width/2 , (self.frame.size.height / 24)*13 )];
[self addChild:brickT3];
BrickT *brickT4 = [BrickT BrickTAtPosition:CGPointMake( self.frame.size.width/2 , (self.frame.size.height / 24)*15)];
[self addChild:brickT4];
}
- (void) didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ( firstBody.categoryBitMask == CollisionCategoryBrick && secondBody.categoryBitMask == CollisionCategoryShark ) {
NSLog(#"BRICK");
[contact.bodyB.node removeFromParent];
} else if ( firstBody.categoryBitMask == CollisionCategoryWaterBall && secondBody.categoryBitMask == CollisionCategoryShark ) {
NSLog(#"BALL");
self.addSeconds =YES;
[contact.bodyA.node removeFromParent];
[self addPoints:PointsPerHit];
if (![ self childNodeWithName:#"WATERBALLTL"]) {
[self addWaterBallTopLeft];
}
if (![ self childNodeWithName:#"WATERBALLTR"]) {
[self addWaterBallTopRight];
}
if (![ self childNodeWithName:#"WATERBALLBL"]) {
[self addWaterBallBottomLeft];
}
if (![ self childNodeWithName:#"WATERBALLBR"]) {
[self addWaterBallBottomRight];
} }
NSLog(#"%lu", (unsigned long)[self.children count]);
}
-(void)update:(CFTimeInterval)currentTime {
if (startGamePlay){
startTime = currentTime;
startGamePlay = NO;
}
timeSinceStart = (currentTime -startTime);
countDownInt = 1000 - (int)(currentTime-startTime);
if (countDownInt > 0 && self.addSeconds == NO) {
countDown.text = [NSString stringWithFormat:#"%i", countDownInt];
} else if(countDownInt > 0 && self.addSeconds == YES){
startTime +=2;
countDown.text = [NSString stringWithFormat:#"%i", countDownInt];
self.addSeconds = NO;
}
if (countDownInt == 0 ){
countDown.text =#"0";
Level2 *level2 = [Level2 sceneWithSize:self.frame.size];
SKTransition *transition = [SKTransition fadeWithDuration:0.5];
[self.view presentScene:level2 transition:transition];
}
}
//answer of your second question use a skaction sequence which would fire a function after 1 second
SKAction *Timetofire= [SKAction sequence:#[
//time after you want to fire a function
[SKAction waitForDuration:1],
[SKAction performSelector:#selector(urFunction)
onTarget:self]
]];
[self runAction:Timetofire withKey:#"key"];
//if you want your function eld fire x number of time
[self runAction:[SKAction repeatAction:Timetofire count:10]];
where x in number of times you wan't your function to fire
//if you want your function fire forever
[self runAction:[SKAction repeatActionForever:Timetofire ]];
1)now the answer of your first question used predefined NSMutableArray to store multiple CGPoint. where each CGPoint define the position of your shark,ball and other item be sure that the value of each CGPoint is different from each other which would give your item a random occurrence.

Accessing an object's local variable from a method Objective-c

I'm trying to access an object's local variable from didBeginContact method. How's it possible ?
Let's say I've got a ball object and whenever it bounces from the player, that ball's bouncedFrom variable get's an identifier from the player.
This is my initBall method, which initialises a ball. It is called every 3-5(random) seconds.
-(void)initBall {
NSString *bouncedFrom;
bouncedFrom = #"";
ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball"];
ball.name = ballCategoryName;
ball.zPosition = 0;
int cannonPos = (arc4random() % 3 ) + 1;
SKSpriteNode *cannon = (SKSpriteNode *)[self childNodeWithName:[NSString stringWithFormat:#"%i",cannonPos]];
ball.zPosition = 3;
ball.position = cannon.position;
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = wallCategory;
ball.physicsBody.collisionBitMask = wallCategory;
ball.physicsBody.usesPreciseCollisionDetection = YES;
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.friction = 0.0f;
//Bounce back
ball.physicsBody.restitution = 1.0f;
//
ball.physicsBody.linearDamping = 0.0f;
ball.physicsBody.allowsRotation = NO;
switch (cannonPos) {
case 1:
ball.position = CGPointMake(ball.position.x+20, ball.position.y+20);
[ball.physicsBody applyImpulse:CGVectorMake(-1.0,1.0)];
break;
case 2:
ball.position = CGPointMake(ball.position.x-20, ball.position.y+15);
[ball.physicsBody applyImpulse:CGVectorMake(-1.0,1.0)];
break;
case 3:
ball.position = CGPointMake(ball.position.x-20, ball.position.y-15);
[ball.physicsBody applyImpulse:CGVectorMake(-1.0,-1.0)];
break;
case 4:
ball.position = CGPointMake(ball.position.x+20, ball.position.y-20);
[ball.physicsBody applyImpulse:CGVectorMake(-1.0,-1.0)];
default:
break;
}
What I want is something like this in the didBeginContact:
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & ballCategory) !=0) {
SKNode *ball = contact.bodyB.node;
SKNode *player = contact.bodyA.node;
ball.bouncedFrom = PlayerCategory;
}
if ((firstBody.categoryBitMask & wallCategory) !=0) {
SKNode *ball = contact.bodyB.node;
SKAction *addScore = [SKAction runBlock:^{
if (ball.position.y < 0) playerScore--;
if (ball.position.x < 0) cpu2Score--;
if (ball.position.y > self.frame.size.height) cpu1Score--;
if (ball.position.x > self.frame.size.width) cpu3Score--;
}];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:#[addScore, removeNode]];
[ball runAction:sequence];
}
}
Both ball and xCategories are declared as static variables at the top of my code.
Is something like this possible ?
My first thought is that you should declare bouncedFrom as a class variable in the .h rather than declaring it in your init: method.
so in the .h should look like
#interface Ball: NSObject{<br>
NSString *bouncedFrom;<br>
}
#end

If Else satement working but not updating cocos2d

i have tested my code by changing the starting lives vaule, the problem is it doesn't remove them as the statement becomes valid, How do i fix this? I have tried placing it in my .m file but it doesn't seem to work properly anywhere, any ideas on where it would go? I would post the .m but it is about 500 lines so it is a bit big so i just pasted the relevant bit of it. also i am a 15 year old, and i am fairly new to cocos2d development
The Code
- (void) addMonster {
CCSprite * monster = [CCSprite spriteWithFile:#"startH.png"];
// Determine where to spawn the monster along the Y axis
CGSize winSize = [CCDirector sharedDirector].winSize;
int minY = monster.contentSize.height / 2;
int maxY = winSize.height - monster.contentSize.height/2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
// Create the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
monster.position = ccp(winSize.width + monster.contentSize.width/2, actualY);
[self addChild:monster];
// Determine speed of the monster}
if (Strategyscore < 10) {
int minDuration = 5.0;
int maxDuration = 10.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
eate the actions
CCMoveTo * actionMove = [CCMoveTo actionWithDuration:actualDuration
position:ccp(-monster.contentSize.width/2, actualY)];
CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
[node removeFromParentAndCleanup:YES];
[_monsters removeObject:node];
Life--;
CCSprite *Life3 = [CCSprite spriteWithFile:#"heart.png"];
Life3.position = ccp(210,200);
CCSprite *Life2 = [CCSprite spriteWithFile:#"heart.png"];
Life2.position = ccp(220,200);
CCSprite *Life1 = [CCSprite spriteWithFile:#"heart.png"];
Life1.position = ccp(230,200);
[self addChild:Life3];
[self addChild:Life2];
[self addChild:Life1];
if(Life == 2) {
[self removeChild:Life3];
}
else if(Life == 1) {
[self removeChild:Life2];
[self removeChild:Life3];
}
else if(Life <= 0) {
[self removeChild:Life1];
[self removeChild:Life2];
[self removeChild:Life3];
// Cr [[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[MainMenu scene]]];
}
}];
[monster runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];
//collision stuff
monster.tag = 1;
[_monsters addObject:monster];
}
Also the .h file
int StrategyBullet;
int Strategyscore;
int high;
int Life;
CCLabelTTF *highlabel;
CCLabelTTF *StrategyBulletLabel;
CCLabelTTF *StrategyscoreLabel;
#interface Strategy: CCLayer
{
NSMutableArray * _monsters;
NSMutableArray * _projectiles;
int _monstersDestroyed;
}
+(CCScene *) scene;
#end
Every time you add a new monster, you add a new set of sprites Life1,Life2, and Life3, superimposed on the previous ones. You probably want to have a single set of life hearts.
in .h
CCSprite *Life1,*Life2,*Life3;
in .m, init method
Life3 = [CCSprite spriteWithFile:#"heart.png"];
Life3.position = ccp(210,200);
Life2 = [CCSprite spriteWithFile:#"heart.png"];
Life2.position = ccp(220,200);
Life1 = [CCSprite spriteWithFile:#"heart.png"];
Life1.position = ccp(230,200);
[self addChild:Life1];
[self addChild:Life2];
[self addChild:Life3];
and in your actionMoveDone call block, dont remove them, just make them not visible
CCCallBlockN * actionMoveDone = [CCCallBlockN actionWithBlock:^(CCNode *node) {
[node removeFromParentAndCleanup:YES];
[_monsters removeObject:node];
Life--;
if(Life == 2) {
Life3.visible=NO;
}
else if(Life == 1) {
Life3.visible=NO;
Life2.visible=NO;
}
else if(Life <= 0) {
Life3.visible=NO;
Life2.visible=NO;
Life1.visible=NO;
}
}];
for starters. I just made this as 'like your coding style' as possible, but eventually you will find different patterns to do this as you game becomes more complex. Read about normal iOS code and naming conventions, it will help you and also make your code samples more palatable for the people trying to help you here.
Where are you montering the lives value ? In a tick method ?
if(Life == 2) {
[self removeChild:Life3];
}
else if(Life == 1) {
[self removeChild:Life2];
[self removeChild:Life3];
}
else if(Life <= 0) {
[self removeChild:Life1];
[self removeChild:Life2];
[self removeChild:Life3];
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:1.0 scene:[MainMenu scene]]];
}

NSMatrix with multiple toggle buttons?

I am trying to create an NSMatrix of NSButtonCells where between zero and four buttons can be selected (toggled on). I have tried the following (test) code, but am not sure how I can provide the functionality I require. Perhaps it's not possible with NSMatrix and I need to look at an alternative control, or create my own?
#interface MatrixView : NSView
{
NSScrollView *_scrollView;
NSMatrix *_matrixView;
}
#end
#implementation MatrixView
- (id)initWithFrame:(NSRect)frameRect
{
NSLog(#"initWithFrame. frameRect=%#", NSStringFromRect(frameRect));
self = [super initWithFrame:frameRect];
if (self != nil)
{
_scrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, frameRect.size.width, frameRect.size.height)];
[_scrollView setBorderType:NSNoBorder];
[_scrollView setHasVerticalScroller:YES];
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
NSSize contentSize = [_scrollView contentSize];
contentSize.height = 300;
// Make it 3 x however-many-buttons-will-fit-the-height
CGFloat gap = 8.0;
CGFloat width = (contentSize.width / 3.0) - (gap * 2.0);
NSUInteger rows = (contentSize.height / (width + gap));
NSLog(#"width=%f, rows=%lu", width, rows);
NSButtonCell *prototype = [[NSButtonCell alloc] init];
[prototype setTitle:#"Hello"];
[prototype setButtonType:NSToggleButton];
[prototype setShowsStateBy:NSChangeGrayCellMask];
_matrixView = [[NSMatrix alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)
mode:NSListModeMatrix
prototype:prototype
numberOfRows:rows
numberOfColumns:3];
[_matrixView setCellSize:NSMakeSize(width, width)];
[_matrixView setIntercellSpacing:NSMakeSize(gap, gap)];
[_matrixView setAllowsEmptySelection:YES];
[_matrixView sizeToCells];
[_scrollView setDocumentView:_matrixView];
[self addSubview:_scrollView];
[self setAutoresizesSubviews:YES];
[prototype release];
}
return self;
}
...
I got this to work with the following subclass of NSMatrix. I added one property, onCount, to keep track of how many buttons were in the on state:
#implementation RDMatrix
#synthesize onCount;
-(id) initWithParentView:(NSView *) cv {
NSButtonCell *theCell = [[NSButtonCell alloc ]init];
theCell.bezelStyle = NSSmallSquareBezelStyle;
theCell.buttonType = NSPushOnPushOffButton;
theCell.title = #"";
if (self = [super initWithFrame:NSMakeRect(200,150,1,1) mode:2 prototype:theCell numberOfRows:4 numberOfColumns:4]){
[self setSelectionByRect:FALSE];
[self setCellSize:NSMakeSize(40,40)];
[self sizeToCells];
self.target = self;
self.action = #selector(buttonClick:);
self.drawsBackground = FALSE;
self.autoresizingMask = 8;
self.allowsEmptySelection = TRUE;
self.mode = NSHighlightModeMatrix;
self.onCount = 0;
[cv addSubview:self];
return self;
}
return nil;
}
-(IBAction)buttonClick:(NSMatrix *)sender {
NSUInteger onOrOff =[sender.selectedCells.lastObject state];
if (onOrOff) {
self.onCount += 1;
}else{
self.onCount -= 1;
}
NSLog(#"%ld",self.onCount);
if (self.onCount == 5) {
[sender.selectedCells.lastObject setState:0];
self.onCount -= 1;
}
}
When you try to select the 5th button it will flash on, but then go off. This could be a problem depending on how you are using the state of these buttons. I just logged them with this method:
-(IBAction)checkMatrix:(id)sender {
NSIndexSet *indxs = [self.mat.cells indexesOfObjectsPassingTest:^BOOL(NSButtonCell *cell, NSUInteger idx, BOOL *stop) {
return cell.state == NSOnState;
}];
NSLog(#"%#",indxs);
}
After Edit: I didn't like the way my first method flashed the button on briefly before turning it off again when you try to click the 5th button. I found what I think is a better solution that involves overriding mouseDown in the matrix subclass (if you want to try this, you should delete the setAction and setTarget statements and delete the buttonClick method):
-(void)mouseDown:(NSEvent *) event {
NSPoint matPoint = [self convertPoint:event.locationInWindow fromView:nil];
NSInteger row;
NSInteger column;
[self getRow:&row column:&column forPoint:matPoint];
NSButtonCell *cell = [self cellAtRow:row column:column];
if (self.onCount < 4 && cell.state == NSOffState) {
cell.state = NSOnState;
self.onCount += 1;
}else if (cell.state == NSOnState) {
cell.state = NSOffState;
self.onCount -= 1;
}
}