I am trying to implement city lights animations by having an array of CGRect's with the coordinates of each light. Then creating UIViews around these CGRects. This logic (Thanks to Darren for helping with this logic) is working fine however after the animation is complete, all other elements (UIButtons, Sliders, Other UIImageViews etc) on screen become inaccessible. Even my Swipe gestures are not responding. Before and during the animation, all elements respond well BUT once the animation is complete, they all become inaccessible. I also tried [UIView bringSubviewToFront:] to bring some of the elements in front to see if that helps to make them accessible but it didn't help. I think that's not the issue because even if I try to create the lights view by sending them to background [self.view sendSubviewToBack:light]; everything becomes inaccessible after the animation is complete.
I would appreciate if someone can help/suggest me what am I missing.
Here is my code logic and corresponding scene for city lights animation. rects & lits are ivars.
- (void)viewDidLoad {
pageCount=5;
AVAudioSession * audioSession = [AVAudioSession sharedInstance];
//Setup the audio session
//...
pageNum=1;
//put imageviews in place
//...
//load a page
[self loadPage];
[self loadAudio];
[self cityLightsSetUp];
[super viewDidLoad];
}
-(void)loadPage{
//Logic to load page...
/...
if (pageNum == 3){
[self startCityLights];
}
}
- (void)cityLightsSetUp
{
rects = [[NSMutableArray alloc] init];
lit = [[NSMutableArray alloc] init];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(58, 217, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(71, 217, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(84, 217, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(97, 217, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(58, 231, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(71, 231, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(84, 231, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(97, 231, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(58, 245, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(71, 245, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(84, 245, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(97, 245, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(58, 258, 10, 10)]];
[rects addObject:[NSValue valueWithCGRect:CGRectMake(71, 258, 10, 10)]];
}
- (void)startCityLights
{
[self lightRandomLight];
[self performSelector:#selector(switchOffRandomLight) withObject:nil afterDelay:1.0];
}
- (void)lightRandomLight
{
BOOL escape = NO;
int rand;
while (!escape) {
BOOL alreadyLit = NO;
rand = arc4random() % [rects count];
// Check if already lit
for (UIView *view in lit) {
CGRect litRect = view.frame;
CGRect ranRect = [[rects objectAtIndex:rand] CGRectValue];
if (CGRectContainsRect(litRect, ranRect)) {
alreadyLit = YES;
}
}
if (!alreadyLit) {
UIView *light = [[UIView alloc] initWithFrame:[[rects objectAtIndex:rand] CGRectValue]];
light.backgroundColor = [UIColor orangeColor];
[lit addObject:light];
[self.view addSubview:light];
//[self.view sendSubviewToBack:light];
escape = YES;
}
}
[self performSelector:#selector(lightRandomLight) withObject:nil afterDelay:0.2];
}
- (void)switchOffRandomLight
{
NSLog(#"switchOffRandomLight");
int rand = arc4random() % [lit count];
UIView *light = [lit objectAtIndex:rand];
[lit removeObject:light];
[light removeFromSuperview];
[self performSelector:#selector(switchOffRandomLight) withObject:nil afterDelay:0.5];
}
It appears that once all lights are lit, the while loop runs continuously trying to get a random number that isn't lit. This is what will be blocking the main thread.
All you need is to check if all lights are lit at the beginning of the method and continue if not.
At the bottom of your lightRandomLight method, replace
[self performSelector:#selector(lightRandomLight) withObject:nil afterDelay:0.2];
with
if ([lit count] != [rects count]) {
[self performSelector:#selector(lightRandomLight) withObject:nil afterDelay:0.2];
} else {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
}
and it'll stop when all the lights are lit.
You are blocking the main thread - lightRandomLight calls itself when it is finished, with a 0.2 second delay. Therefore the main thread will never be responsive to user interaction. Look at the animation API provided by UIView - rather than "animating" by adding and removing subviews, you can set the alpha status or background colour of views, and you can set delays and set up loops.
My guess is that while (!escape) {} will never complete (so escape is never YES).
Related
I need to find a generate a random point just outside the screen to spawn an enemy. How can I do this? I looked up other topics on this but they were all confusing.
You could use a method like this :
+(CGPoint)randomPointInRects:(NSArray*)rects
{
// choose one of the rects in the array randomly
int randomIndex = arc4random() % rects.count;
CGRect rect = [(NSValue*)rects[randomIndex] CGRectValue];
// get a random x and y location based on the chosen rect
float randX = arc4random() % (int)rect.size.width + rect.origin.x;
float randY = arc4random() % (int)rect.size.height + rect.origin.y;
// store value in point
CGPoint randomPoint = CGPointMake(randX, randY);
return randomPoint;
}
Then to get that random point on say a 1024x768 screen you might do something like this :
NSMutableArray *rects = [NSMutableArray array];
// define spawn areas with CGRect's
//top
[rects addObject:[NSValue valueWithCGRect:CGRectMake(0, 768, 1024, 50)]];
// bottom
[rects addObject:[NSValue valueWithCGRect:CGRectMake(0, -50, 1024, 50)]];
//left
[rects addObject:[NSValue valueWithCGRect:CGRectMake(-50, 0, 50, 768)]];
//right
[rects addObject:[NSValue valueWithCGRect:CGRectMake(1024, 0, 50, 768)]];
// pass array to method to return a random point in those rects.
CGPoint randomPoint = [self randomPointInRects:rects];
You basically define the areas you want to have nodes spawned, store them in an array, pass them to the method and it will return a random point from those rects.
I added a 50 pixel buffer for each edge.
I did this quickly, so it might have a bug, but the logic of the approach should get you there if so! :)
This solution would also work for any situation where you wanted to have something spawned in multiple areas, defined by CGRects.
Hi how do i create a moving ball on iOS.
When the program starts, I will display the ball on the left of the screen. Subsequently, each time i click a UIButton, how do i move the ball to the right on the same x-axis.
I managed to display the ball but how do i update and redraw its location when the UIButton is pressed? currently each time i press the UIButton, it creates a new ball and the old balls are not cleared.
i understand its because i recreate a new instance of the ball. So how shall i go about fixing this?
Here is my code....
ballView.m
#implementation BallView
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
-(void)drawRect:(CGRect)dirtyRect
{
NSLog(#"in drawRect");
// Get the graphics context and clear it
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Draw a solid ball
CGContextSetRGBFillColor(ctx, 0, 0, 0, 1);
CGContextFillEllipseInRect(ctx, dirtyRect);
[self setNeedsDisplay];
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
MoveBallController.m
-(void)moveBall
{
CGRect viewFrame = CGRectMake(0, 0, 30, 30);
BallView *ball = [[BallView alloc] initWithFrame:viewFrame];
[[self dotView] addSubview:ball];
}
Create one BallView instance and store it in an instance variable. Then when the button is clicked you simply update the ball's frame:
- (void)viewDidLoad {
[super viewDidLoad];
CGRect viewFrame = CGRectMake(0, 0, 30, 30);
_ball = [[BallView alloc] initWithFrame:viewFrame];
[[self dotView] addSubview:_ball];
}
- (void)moveBall {
CGRect frame = _ball.frame;
frame.origin.x += 5; // use some appropriate increment
_ball.frame = frame;
}
where _ball is your new instance variable.
i have a ccsprite which i would like to place inside a CCLayerColor but for some reason its not showing up inside of it. i have the following code and i'm stumped why it isn't showing up.
[self setColor:ccGREEN];
[self setOpacity:255];
[self setPosition:(CGPointMake(0, 60/2+bottomPadding))];
self.inventoryHolder = [[CCSprite alloc] init];
[self.inventoryHolder setTextureRect:CGRectMake(0, 0, 100, 60)];
[self.inventoryHolder setColor:ccc3(255, 0, 0)];
self.inventoryHolder.anchorPoint = ccp(0, 0);
self.inventoryHolder.position = ccp(0, 60/2+50);
[self addChild:self.inventoryHolder z:100];
self being the CCLayerColor and self.inventoryHolder being the CCSprite. Any help would be awesome!
You haven't actually set a texture in the sprite, so there's nothing for it to display.
_ball = [CCSprite spriteWithFile:#"ball.png" rect:CGRectMake(0, 0, 52, 52)];
_ball.position = ccp(300, 300);
[self addChild:_ball];
_ball1 = [CCSprite spriteWithFile:#"ball.png" rect:CGRectMake(0, 0, 52, 52)];
_ball1.position = ccp(300, 300);
[self addChild:_ball1];
if (_ball1.position.x == _ball.position.x && _ball1.position.y == _ball.position.y) {
NSLog(#"Stop");
}
if _ball is touch _ball1 not worrking
in this something wrong?
Use CGRectIntersectsRect to determine if the sprites overlap:
if (CGRectIntersectsRect([_ball1 boundingBox], [_ball2 boundingBox])) {
NSLog(#"Stop");
}
Of course, this is just a rectangular intersection, which is probably unsuitable for circular objects, in which you can use the ideas presented in this webpage.
I am trying to animate the dealing of cards. Currently I have two cards, the dealer card (dCard1) and the player card (pCard1).
The player card should be dealt and as soon as it is dealt, the dealer card should then be dealt.
What is currently happening is the player card will animate properly when I press the deal button, but instead of waiting till the player card is done animating, the dealer card just appears at the dealerTo location. It does not animate. I am new to doing animations any help would be greatly appreciated.
-(void) animateHandsDealt: (Hand*) pHand:(Hand*) dHand{
pCard1= [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
dCard1= [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
CGFloat start = 0;
CGFloat duration = 1;
CGPoint playerTo = CGPointMake(120, 300);
CGPoint dealerTo = CGPointMake(120, 70);
pCard1.image = [UIImage imageNamed:[[pHand.cardArr objectAtIndex:0 ] imagePath]];
[self.view addSubview: pCard1];
[playerImages addObject:pCard1];
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"position" ];
move.fillMode = kCAFillModeBoth;
move.beginTime = start;
[move setToValue:[NSValue valueWithCGPoint:playerTo]];
[move setDuration:duration];
move.removedOnCompletion = FALSE;
[[pCard1 layer] addAnimation:move forKey:#"position"];
dCard1.image = [UIImage imageNamed:#"back-red-75-1.png"];
[self.view addSubview: dCard1];
CABasicAnimation *move2 = [CABasicAnimation animationWithKeyPath:#"position" ];
[move2 setToValue:[NSValue valueWithCGPoint:dealerTo]];
move2.beginTime = start + duration;
[move2 setDuration:duration];
move2.fillMode = kCAFillModeBoth;
move2.removedOnCompletion = FALSE;
[[dCard1 layer] addAnimation:move2 forKey:#"position"];
}
If you're just animating the frame of the view, you can use the much simpler UIView animateWithDuration... methods.
You chain them by using the animateWithDuration:animations:completion: version. You can specify the second animation in the completion: block of the first animation.
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW109
I don't have access to your classes (of course), but I think this is pretty close to "drop-in-ready" code. What do you think?
- (void)deal {
// Of course, you'd replace these NSString objects with actual card objects...
NSArray *array = [NSArray arrayWithObjects:#"pCard1", #"dCard1", #"pCard2", #"dCard2", #"pCard3", #"dCard3", nil];
[self performSelectorOnMainThread:#selector(animateDealWithArray:) withObject:array waitUntilDone:YES];
}
- (void)animateDealWithArray:(NSArray *)array {
// constants that won't change call to call
static CGFloat duration = 1;
static CGSize cardSize = {75, 110};
static CGPoint playerTo = {120, 300};
static CGPoint dealerTo = {120, 70};
// Seems like you want the card to start at the top of the "deck" whether it's a player card or not
UIImageView *card = [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
// Figure out if the first object (the one we're currently using) is a player or dealer card
if ([[array objectAtIndex:0] isKindOfClass:[PlayerCard Class]]) {
// Player card, so show the "face"
[card setImage:[UIImage imageNamed:[(PlayerCard *)[array objectAtIndex:0] imagePath]]];
[UIView animateWithDuration:duration
animations:^{
[card setFrame:CGRectMake(playerTo.x, playerTo.y, cardSize.width, cardSize.height)];
}
completion:^(BOOL finished) {
[self dealNext:array];
}
];
} else {
// Dealer card, so show the back
[card setImage:[UIImage imageNamed:#"back-red-75-1.png"]];
[UIView animateWithDuration:duration
animations:^{
[card setFrame:CGRectMake(dealerTo.x, dealerTo.y, cardSize.width, cardSize.height)];
}
completion:^(BOOL finished) {
[self dealNext:array];
}
];
}
}
- (void)dealNext:(NSArray *)array {
int count = [array count];
if (count > 1) {
NSArray *newArray = [array subarrayWithRange:NSMakeRange(1, count - 1)];
[self animateDealWithArray:newArray];
}
}