I'm fairly new to Objective-C development.
In my first app built from scratch, I'd like to draw a circle that increases its size as time runs.
The problem is that I've been able to move the circle through the screen, but can't seem to update its properties so it gets bigger or smalles.
Thing goes like this:
CCircle class
#interface CCircle : CShapes
#property float radius;
#property float startAngleRadians;
#property float endAngleRadians;
In the first view controller (ShapesViewController.m):
- (void) viewDidLoad
{
(...)
CCircle* circle = [[CCircle alloc] initWithFrame:frame];
circle.radius = 40;
circle.startAngleRadians = M_PI;
circle.endAngleRadians = 2*M_PI;
circle.tag = 1;
[self.view addSubView:circle];
//I also schedule an update method, so that it is called every 1 seconds.
[NSTimer scheduledTimerWithTmieInterval:1.0 target:self selector:#selector(updateCircle) userInfo:nil repeats:YES];
}
Also in ShapesViewController.m, the update method:
- (void) updateCircle
{
CCircle *circle = [self.view viewWithTag:1];
//Now here: if I do this: the circle will "move" through the screen
CGRect frame = circle.frame;
frame.origin.x += 5;
[circle setFrame:frame];
//However, if I try to change the circle properties, I don't know what to do so
//that affects the circle. In this case, the circle will move through the screen,
//but I keep seeing always the same size(radius).
circle.radius += 5;
//I've tried the following (of course, not all at the same time):
//[self.vew addSubview:circle];
//[self.view sendSubviewToBack:circle];
//[self.view sendSubviewToFront:circle];
//[self.view setNeedsDisplay];
//[self.view setNeedsLayout];
}
Any help on what am I doing wrong, and what should I do to achieve what I want?
Thanks!
You updated a property of circle, not self.view, so you need to call setNeedsDisplay on circle.
After drawing the circle, you have to update your view.
[self.view setNeedsDisplay];
I think that'll do it.
Related
Background:
I am using x-code version 3.1.4 and an iphone simulator version 3.1.
Right now I am building a game where a car (portrayed by a UIImageView) moves left and right using two buttons (left and right). That part works fine. However, I am trying to randomly spawn UIImageViews at the top that will go down at a constant rate. I am able to get one UIImageView to move using this:
-(void)moveMeteor{
iv.center=CGPointMake(iv.center.x, iv.center.y + 1);
[self performSelector:#selector(moveMeteor) withObject:iv afterDelay:0.01];
}
where iv is a UIImageView and I call this function when I click a button. However, I want to create randomly spawning UIImageViews. So, I did this using this code:
-(void)addViews: (NSTimer *) aTimer {
UIImageView *iv = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"Meteor.png"]];
CGRect rect = CGRectMake(arc4random() % (350), arc4random() % (5), 35, 35);
[iv setFrame:rect];
[self.view addSubview:iv];
}
I call this function by clicking a start button which does this:
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(addViews:) userInfo:nil repeats:YES];
I thought about using tags by changing the addViews function to :
-(void)addViews: (NSTimer *) aTimer {
UIImageView *iv = [[UIImageView alloc]initWithImage [UIImage imageNamed:#"Meteor.png"]];
CGRect rect = CGRectMake(arc4random() % (350), arc4random() % (5), 35, 35);
[iv setFrame:rect];
[self.view addSubview:iv];
iv.tag=tagStart;
[self moveMeteor:tagStart];
tagStart = tagStart+1;
}
(the initial value of tagStart is 1);
and changing the move function to:
-(void)moveMeteor:(int)tag{
UIImageView *test= (UIImageView *)[self.view viewWithTag:tagStart];
test.center=CGPointMake(test.center.x, test.center.y + 1);
[self performSelector:#selector(moveMeteor) withObject:tagStart afterDelay:0.01];
[test release];
I am very confused on how to resolve this issue. The problem is either that my move function cannot handle so many moving things at a time, my tag system is faulty, my *test is faulty, or that I am calling to many functions at the same time, or something else. Please help and give me a solution to this annoying problem.
A better option than performSelector is NSTimer, and a better option again is CADisplayLink. You limit of performance will probably be using UIImageView and the associated overhead with moving and redrawing them all, so you may want to change to use CALayers instead. But, change one thing at a time.
For the meteors, I'd hold them all in an array and then each time your moveMeteor method is called, iterate through the array and move each one. Your tag method could work but will be less efficient (you're missing a loop to iterate through the tags basically).
-(void)moveMeteors {
for (UIImageView *test in self.meteors) {
test.center = CGPointMake(test.center.x, test.center.y + 1);
}
}
If you have an array called meteors that you add your new meteors to, and a timer calls moveMeteors periodically.
I've made a program that generates an image at a random x-coordinate at the top of the screen. The image then falls down to the bottom. However, I want to keep generating new images (of the same image) every few seconds so that it's as though these duplicates of the same image are continually "raining" from the top. (Note: Eventually, as I continue to develop this app, I will need to recall the location of each image at any moment, so I believe I will need each spawned image to be part of an array. I also believe I must move each image step-by-step, so I cannot rely on animation).
The problem is: How can I make all of this code repeat every 0.5 seconds so that each newly spawned image has its own moveObject timer. It will be like raindrops falling from the top.
#implementation ViewController {
UIImageView *_myImage;
}
- (void)viewDidLoad
{
srand(time(NULL));e
int random_x_coordinate = rand() % 286;
CGRect myImageRect = CGRectMake(random_x_coordinate, 0.0f, 40.0f, 40.0f);
UIImageView *myImage = [[UIImageView alloc] initWithFrame:myImageRect];
[myImage setImage:[UIImage imageNamed:#"flake.png"]];
myImage.opaque = YES;
[self.view addSubview:myImage];
_myImage = myImage;
//FALLING BIRDS TIMER
moveObjectTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:#selector(moveObject) userInfo:nil repeats:YES];
}
//FALLING BIRDS MOVER
-(void) moveObject { // + means down and the number next to it is how many pixels y moves down per each tick of the TIMER above
_myImage.center = CGPointMake(_myImage.center.x, _myImage.center.y +1);
}
You have different solutions:
Just 1 timer in which you update an array of UIImageView (I prefer this one)
Subclass Imageview and put the timer inside the class so each one would have its own timer
Maybe you can use [UIView animateWithDuration... instead of timers, but not sure if you can use many at the same time.
The problem is: How can I make all of this code repeat every 0.5
seconds so that each newly spawned image has its own moveObject timer.
Look into Core Animation's particle effects -- they're not just for smoke, fog, and fire. By setting up a particle emitter and creating particle cells using your image, you can have Core Animation take care of the whole operation. I don't see much official documentation on the topic, but you can read the reference page for CAEmitterLayer to get an idea of how it works, and then take a look at the tutorial on raywenderlich.com.
Use this function for each raindrop object.Just provide point to move to:
-(void)raindropAnimation:(CGPoint)dropPoint
{
raindrop.frame = CGRectMake(dropPoint.x,0,raindrop.frame.size.width,raindrop.frame.size.height) //here y is 0 ie topmost
[UIView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationCurveEaseInOut
animations:^ {
raindrop.frame = CGRectMake(dropPoint.x,dropPoint.y,raindrop.frame.size.width,raindrop.frame.size.height) //will move to point
}
completion:^(BOOL finished) {
[self perfromSelector:#selector(raindropAnimation:CGPointMake(xnewpointhere,ynewpointhere))];
}];
How can I programmatically spawn an unlimited number of UIImageViews on to the screen in a random location on a time increment. The random location would be:CGPoint(arc4random() % (481), arc4random() % (321),20,20) I just don't understand how to constantly create new ones with one image variable and a for loop. Or an NSTimer that on every increment adds a new image to the screen.
I know there are some tutorials out there for this, but the only ones I could find used automatic reference counting. It should be simple to create, and a link is fine.
Other questions:
UI is for user-interface, but since i'm not using a storyboard or xib, would there be a better type of imageView to use.
How about something like this, in your view controller class (substituting your own image file name of course):
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:.5 target:self selector:#selector(addViews:) userInfo:nil repeats:YES];
}
-(void)addViews: (NSTimer *) aTimer {
UIImageView *iv = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"New_PICT0011.jpg"]];
CGRect rect = CGRectMake(arc4random() % (481), arc4random() % (321), 20, 20);
[iv setFrame:rect];
[self.view addSubview:iv];
}
You would have to put some kind of counter in there, and when it got to the total number of views you wanted, call [aTimer invalidate];
here is my code :
-(void) createNewImage {
UIImage * image = [UIImage imageNamed:#"boutonplay_03.png"];
imageView = [[UIImageView alloc] initWithImage:image];
[imageView setCenter:[self randomPointSquare]];
[[self view] addSubview:imageView];
ix=imageView.center.x;
iy=imageView.center.y;
X=(240-ix)/230;
Y=(160-iy)/230;
}
-(void)Animation{
imageView.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
"CreateNewImage" is alled every 2 seconds, and there is a CADisplayLink on "Animation".
My problem is that the animation of "imageView" stops every 2 seconds because a new "imageView" is created and this last start moving and it stops again after 2 seconds, and again and again and again ... What I would like is to make "imageView" continue his animation in spite of the creation of new "imageView".How can I do this please ? sorry for my english I'm french:/
Check to see if imageView == nil or not.
if (imageView == nil) {
imageView = [[UIImageView alloc] initWithImage:image];
}
//rest of code
You are creating a new UIImageView every time you call CreateNewImage
instead, check to see if it has already been created.
-(void) createNewImage {
if(imageView == nil) {
UIImage * image = [UIImage imageNamed:#"boutonplay_03.png"];
imageView = [[UIImageView alloc] initWithImage:image];
}
[imageView setCenter:[self randomPointSquare]];
[[self view] addSubview:imageView];
ix=imageView.center.x;
iy=imageView.center.y;
X=(240-ix)/230;
Y=(160-iy)/230;
}
If I understand correctly your case, what happens is this:
you create a new image in createNewImage;
this image is animated through the method:
-(void)Animation{
imageView.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
which animates your imageView ivar;
when you create a new image, after 2 seconds, you reassign it to the imageView ivar, so that the Animation method can animate it when it is called;
at the same time, you have lost the reference to your previous image that you were animating before, so Animation will not animate it anymore.
Now what I suggest is trying to change Animation in this way:
-(void)Animation{
for (UIView* imgView in self.view.subviews) {
imgView.center = CGPointMake(imgView.center.x + X, imgView.center.y + Y);
}
}
So, this will simply animate any subview of your main view. If your only subviews are the images you want to animate, it should be fine.
EDIT:
From your description in the comments, it seems to me that what happens is that addSubview has the effect of stopping shortly all animations. I don't know if this is related to loading the images from disk, but I remember I also experienced a similar problem time ago. The only approach I see is pre-creating and pre-adding all the UIImageViews you need (or a maximum number of allowed images), make them all hidden and then making them visible again one by one. This would make the logic of your program more complex, but it would work.
Another approach could be using CALayers instead of UIVIews. Best thing would be using a CABasicAnimation to animate your images.
If you want to use UIImageView, you could get [imageView layer] and add this one instead of the view, like this:
[[self.view layer] addSublayer:[imageView layer]];
but I am not sure that this approach would work.
Hope that his helps.
I'm trying to animate a button which moves around the screen. At any point, the user can push the button. But the button doesn't respond to touches. I've tried an animation block but the button simply moves immediately to its end coordinates, while the frame shows the animation (the button is called bubble):
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:5.0];
CGAffineTransform newTransform = CGAffineTransformMakeScale(3, 3);
bubble.transform = CGAffineTransformTranslate(newTransform, 0, -460);
[UIView commitAnimations];
So then I tried Core Animation with the help of some sample code (the path isn't important, it's just an example):
CGMutablePathRef thePath = CGPathCreateMutable();
CGPathMoveToPoint(thePath,NULL,15.0f,15.f);
CGPathAddCurveToPoint(thePath,NULL,
15.f,250.0f,
295.0f,250.0f,
295.0f,15.0f);
CAKeyframeAnimation *theAnimation=[CAKeyframeAnimation animationWithKeyPath:#"position"];
theAnimation.path=thePath;
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
theGroup.animations=[NSArray arrayWithObject:theAnimation];
theGroup.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theGroup.duration=15.0;
CFRelease(thePath);
[bubble.layer addAnimation:theGroup forKey:#"animatePosition"];
But the buttons still don't respond to touches. Btw, I have several of these 'bubble' buttons on screen at once so having several NSTimers concurrently active wouldn't be optimal.
Can anyone suggest another approach? Should I perhaps animate UIImageViews and make them repsond to touches? Or will I have the same problem? This has been puzzling me for a couple of days so any help much appreciated.
Thanks :)
Michael
When animating a GUI element like the UIButton, the actual position only changes when the animation is done. This means any touch events during the animation will only work at the starting point of the button. You can get the current displayed position of the button by accessing it's presentationLayer. You'll probably need to implement your own event handling, look at the position when the user touches the screen, and compare that to the bounds you get from the presentationLayer.
Well, for anyone interested, here's my solution which works perfectly well:
- (void)startMinigame {
makeBubbles = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(generateRandomBubble) userInfo:nil repeats:YES];
floatBubbles = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:#selector(floatBubbles) userInfo:nil repeats:YES];
}
- (void)generateRandomBubble {
//Get a random image for the bubble
int randomIndex = arc4random()%kNumberBubbles;
UIImage *bubbleImage = [bubbleImages objectAtIndex:randomIndex];
//Create a new bubble object and retrieve the UIButton
Bubble *bubble = [[Bubble alloc]initWithImage:bubbleImage andIndex:randomIndex];
UIButton *bubbleButton = bubble.bubbleButton;
//Configure the button
[bubbleButton addTarget:self action:#selector(bubbleBurst:) forControlEvents:UIControlEventTouchDown];
//Add the bubble to the scene
[self.view insertSubview:bubbleButton belowSubview:bathTub];
//Add the bubble to an array of currently active bubbles:
[self.bubbles addObject:bubble];
[bubble release];
}
- (void)floatBubbles {
//Move every active bubble's frame according to its speed.
for (int i = 0; i < [bubbles count]; ++i) {
Bubble *bubble = [bubbles objectAtIndex:i];
int bubbleSpeed = bubble.speed;
CGRect oldFrame = bubble.bubbleButton.frame;
CGRect newFrame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y - bubbleSpeed, oldFrame.size.width+0.1, oldFrame.size.height+0.1);
bubble.bubbleButton.frame = newFrame;
if (bubble.bubbleButton.frame.origin.y < -100.0) {
[bubbles removeObject:bubble];
[bubble.bubbleButton removeFromSuperview];
}
}
}
This sounds a bit too complicated for UIKit and CoreAnimation. It seems like you're developing something like a game. Why not use a global animation timer for let's say ~60 fps and do your drawing in Quartz2D? Then for each touch you could quickly check any hits between Quartz refreshes.
Try animating the uiview's frame property.