I'm producing a "brick" every second. When you tap it, it goes away. My problem is, when a second, or more, appear on the screen, tapping the previous one eliminates the most recent one, and can't be removed from the screen at all. The code I have is:
- (NSTimer *)getTimer{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector: #selector(produceBricks) userInfo:nil repeats:YES];
return timer;
}
-(IBAction) tapBrick {
//remove last brick
[bricks[count] removeFromSuperview];
//add to score
count++;
NSString *scoreString = [NSString stringWithFormat:#"%d", count];
score.text = scoreString;
}
-(void) produceBricks {
//determine x y coordinates
int xPos, yPos;
xPos = arc4random() % 250;
yPos = arc4random() % 370;
//create brick
bricks[count] = [[UIButton alloc] initWithFrame:CGRectMake(xPos,yPos + 60,70,30)];
[bricks[count] setBackgroundColor:[UIColor blackColor]];
[bricks[count] addTarget:self action:#selector(tapBrick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:bricks[count]];
}
I know it has to do with the bricks[count] removeFromSuperview line being count is always increasing. How would I reference to the brick in the array that's being clicked instead of the current one?
If you add a sender argument, it'll automatically get set to the control which sent the action, so you can do something like the following:
-(IBAction) tapBrick:(id)sender {
[sender removeFromSuperview];
}
Make the method tapBrick:(UIButton *)brick and you'll get a reference to the brick that was tapped as the argument.
Also, I strongly advise against using a C array for this. It's just asking for memory management trouble. Better to use an NSMutableArray and make sure to follow the memory management rules. With the code you've posted, your brick objects will never be released, and instead they'll just keep eating memory until your app exhausts its supply.
Views have a tag property for a reason — So you can identify views of the same type in the same view controller. Consider setting a unique tag for each of these bricks, and then removing the particular one you want to.
Related
I'm working on a project for school that includes a set of buttons that create NStimers used as a countdown timer. Ideally, the time would display as the title of the UIButton that started the timer.
So far I have only been able make this work if when setting the display of the time I refer to a specific instance of a UIButton rather than a somehow passing a UIButton argument.
I also have declared "time" NSString that holds the current displayed value calculated by the NSTimer. This is not how the final implementation should be, and I only have it set up like this just to have it partially working.
Code for one of the buttons:
b1 = [[UIButton alloc]
initWithFrame:CGRectMake(neutral.frame.size.width * 0.56, neutral.frame.size.height * 0.18, neutral.frame.size.height * 0.64, neutral.frame.size.height * 0.64)];
[b1.titleLabel setFont:[UIFont fontWithName:#"Helvetica-Bold" size:35.0]];
[b1 setBackgroundImage: forState:UIControlStateNormal];
Method called from button press action:
-(void)buttonClicked:(id)sender
{
//do stuff
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerFired) userInfo:[NSNumber numberWithInt:t] repeats:YES]; }
NSTimer timer fired method:
-(void)timerFired
{
if((curMin>0 || curSec>=0) && curMin>=0)
{
if(curSec==0)
{
curMin-=1;
curSec=59;
}
else if(curSec>0)
{
curSec-=1;
}
if(curMin>-1)
{
time = [NSString stringWithFormat: #"%d%#%02d",curMin,#":",curSec];
[b1 setTitle:time forState:UIControlStateNormal];
}
}
else
{
[b1 setTitle: nil forState:UIControlStateNormal];
[timer invalidate];
}
}
I hope that was clear enough as to what I am trying to accomplish. I am somewhat surprised that I haven't been able to find anything to walk me through this. I am flexible to changing how exactly this works. All that really matters is that each button can start a unique countdown timer.
I'm sure that this code is sloppy, but Objective C is relatively new to me so any help at all would be appreciated.
Don't store time as minutes/seconds, just use seconds, as all that greater than 60 logic is really tedious isn't it.
I've chosen to call the instance variable _secondsLeft so I will know what it means when I look at the code in 6 months time:
-(void)timerFired
{
NSString *formatted = #"";
if (secondsLeft > 0)
{
secondsLeft--;
formatted = [NSString stringWithFormat:#"%d:%02d", (secondsLeft / 60), (secondsLeft % 60)];
} else {
[timer invalidate];
}
[b1 setTitle:formatted forState:UIControlStateNormal];
}
This question already exists:
iphone-non-global uiimageview detection
Closed 8 years ago.
Background:I am using XCode 3.1. Please do not comment about this.
Problem: I have two buttons, fire and make meteor, which make UIImageViews when clicked. I add these UIImageviews to two seperate NSMutable arrays, bullets and meteors. How would I check for collisions between any element in the bullet array and an element in the meteor array. Also, if there were a collision, how would I then remove the bullet and the meteor in question from the view. Thank you. Here is my code so far:
-(IBAction)createBullets:(id)sender{
UIImageView *two = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"lazerBeam.png"]];
CGRect rectTwo = CGRectMake((image.center.x), (image.center.y - 45), 7, 20);
[two setFrame:rectTwo];
[self.view addSubview:two];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(moveBulletOne:) userInfo:two repeats:YES];
[bulletImageViews addObject:two];
}
-(IBAction)createMeteors:(id)sender{
UIImageView *one = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"Meteor.png"]];
CGRect rectOne = CGRectMake(arc4random() % (310), arc4random() % (1), 35, 35);
[one setFrame:rectOne];
[self.view addSubview:one];
[NSTimer scheduledTimerWithTimeInterval:0.4 target:self selector:#selector(moveMeteorOne:) userInfo:one repeats:YES];
[meteorImageViews addObject:one];
}
I also move the meteors and bullets using a NSTimer to a move function, but that is irrelevant. Basically, I want to check when a bullet collides with a meteor, and when that happens, to remove the meteor and bullet in question from the view.
Basic O(n^2) search, check every bullet against every meteor. This is probably fine for a small app, but it can be optimized using spatial partitioning methods to reduce the amount of checks required (quadtrees etc). You want to create a "toRemove" array rather than removing in place so that the array is not modified while enumerating.
NSMutableArray* bulletsToRemove = [NSMutableArray array];
NSMutableArray* meteorsToRemove = [NSMutableArray array];
for (UIImageView* bullet in bulletImageViews)
{
for (UIImageView* meteor in meteorImageViews)
{
if (CGRectIntersectsRect(bullet.frame, meteor.frame)
{
[bulletsToRemove addObject:bullet];
[meteorsToRemove addObject:meteor];
break;
}
}
}
[bulletsToRemove makeObjectsPerformSelector:#selector(removeFromSuperview)];
[meteorsToRemove makeObjectsPerformSelector:#selector(removeFromSuperview)];
[bulletImageViews removeObjectsInArray:bulletsToRemove];
[meteorImageViews removeObjectsInArray:meteorsToRemove];
To get a sense of what I'm doing without posting pages of code... I have an NSOperation that I'm using to process files as they are added to a folder. In that NSOperation I'm using the NSNotificationCenter to send notifications to an NSView whenever a new job is started. The idea is, that I want to add a new subview to give me some information about the job that just started. The problem is I can't seem to get new subviews to draw. Here is what I have right now.
- (void)drawRect:(NSRect)dirtyRect
{
NSLog(#"Draw Count %i", [jobViewArray count]);
int i = 0;
while (i < [jobViewArray count]) {
[self addSubview:[jobViewArray objectAtIndex:i]];
}
}
and then further down:
-(void) newJobNotification: (NSNotification *) notification
{
if (!jobViewArray)
jobViewArray = [[NSMutableArray alloc] init];
++jobCount;
NSRect rect;
rect.size.width = 832;
rect.size.height = 120;
NSPoint point = { 0, ((jobCount * 120) - 120) };
rect.origin = point;
ProgressView *newJob = [[ProgressView alloc] initWithFrame:rect];
[jobViewArray addObject:newJob];
NSLog(#"Notice Count %i", [jobViewArray count]);
}
}
When I use my app to add a job, the notification is properly received by my NSView, the subview is properly added to the jobViewArray, but then when drawRect: gets called again my jobViewArray is empty. It's the first time I've tried to do something like this so I'm probably doing something completely wrong here... I guess that goes with out saying since it doesn't work huh?
You shouldn't be adding the subview to the view in drawRect:. When you receive the notification you should add the subviews there because the second time the notification comes around, you're going to add 2 subviews, then the next time 3 subviews and so one.
If you add the subview in the notification then you'll not need to mess around with the array.
I'm trying to get multiple Ball objects to form on screen and bounce around. I have it working fine for one, but I am having problems adding more. Ideally, the amount of balls will vary, but for now I am trying to just get two into an array to change the rest of the code.
My problem is that everything compiles and runs fine, but I still only have one ball. This is where I suspect the problem arises.
I also feel like doing this in viewDidLoad is incorrect, but I'm not sure where it actually belongs.
viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
balls = [[NSMutableArray alloc] init];
CGPoint newPos = CGPointMake(5,5);
for(int i = 0; i < 2; i++) {
[balls addObject:[[[Ball alloc] init] autorelease]];
}
[[balls objectAtIndex:1] setPosition:newPos];
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
- (void)onTimer
{
for(int i = 0; i < [balls count]; i++) {
Ball *b = [balls objectAtIndex:i];
[b update];
[(BallView *)self.view refresh:b];
}
}
- (void)refresh:(Ball *)aBall {
ball = aBall;
[self setNeedsDisplay];
}
I've added my onTimer, I thought adding the delay in creating the balls would be enough to make this a nonissue. All update does is change the velocity/direction based on the accelerometer/collisions with the edge of the screen.
Indeed both balls should be displayed. Check their coordinates to see if they overlap. On another note, it appears you are leaking the balls array, as well as the ball1 and ball2 objects. You are alloc init'ing them so you have to release them at the end of viewDidLoad, or, if you are using retain properties, just autorelease them when you set them so that they get retained by the properties.
As for where the code should be located, viewDidLoad should be ok, but consider putting the balls array into the init method you call to create your ViewController, since there's probably no way your ViewController could work without that array being set up.
i have an array of uibezierpath's.
how could I draw each path in sequence, but with a delay so it appears animated?
I am currently drawing the whole array like this (and technically, it is sequential i think, but so fast it appears all at once):
for (UIBezierPath * path in myArrayOfBezierPaths){
path.lineWidth=3;
[path stroke];
[path release];
}
lastly, how could I make each path a different color?
You need to redraw the view multiple times, each time drawing more of your paths. To accomplish this, you need use a timer of some kind and have a way to keep track of which paths you're drawing.
To keep track of the paths you could use an NSInteger instance variable and increment it each time another path is drawn, then in -drawRect: check the value of that variable and draw the appropriate number of paths.
Then you would implement a method that updates the ivar and redraws the view. Here is a very simplistic version:
//assume pathsToDraw is an NSInteger ivar
//and that myArrayOfBezierPaths is an NSArray ivar
//containing UIBezierPath objects
- (void)startDrawingPaths
{
//draw the first path
pathsToDraw = 1;
[view setNeedsDisplay];
//schedule redraws once per second
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateView:) userInfo:nil repeats:YES];
}
- (void)updateView:(NSTimer*)timer
{
//increment the path counter
pathsDrawn++;
//tell the view to update
[view setNeedsDisplay];
//if we've drawn all our paths, stop the timer
if(pathsDrawn >= [myArrayOfBezierPaths count])
{
[timer invalidate];
}
}
Your drawing method would look something like this:
- (void)drawRect:(CGRect)rect
{
NSInteger i = 0;
for (i = 0; i < pathsDrawn; i++)
{
UIBezierPath * path = [myArrayOfBezierPaths objectAtIndex:i];
path.lineWidth=3;
[path stroke];
}
}
As far as color goes, you're creating the paths, so you can make them whatever color you like.