Is it possible to snap UIViews using UISnapBehavior consecutively - ios

I am working on CS193P and I would like to create an effect where cards fly in from the 0,0 one after another snapping into place. I tried to chain the animation but the views flying together also I am trying to use UIDynamicAnimator and same thing happens. All the views are snapping together. Here is the code that i have to snap the views.
-(void)snapCardsForNewGame
{
for (PlayingCardView *cardView in self.cards){
NSUInteger cardViewIndex = [self.cards indexOfObject:cardView];
int cardColumn = (int) cardViewIndex / self.gameCardsGrid.rowCount;
int cardRow = (int) cardViewIndex % self.gameCardsGrid.rowCount;
UISnapBehavior *snapCard = [[UISnapBehavior alloc]initWithItem:cardView snapToPoint:[self.gameCardsGrid centerOfCellAtRow:cardRow inColumn:cardColumn]];
snapCard.damping = 1.0;
[self.animator addBehavior:snapCard];
}
}
-(void)newGame
{
NSUInteger numberOfCardsInPlay = [self.game numberOfCardsInPlay];
for (int i=0; i<numberOfCardsInPlay; i++) {
PlayingCardView *playingCard = [[PlayingCardView alloc]initWithFrame:CGRectMake(0, 0, 50, 75)];
playingCard.faceUp = YES;
[playingCard addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(flipCard:)]];
[self.cards addObject:playingCard];
//NSUInteger cardViewIndex = [self.cards indexOfObject:playingCard];
//int cardColumn = (int) cardViewIndex / self.gameCardsGrid.rowCount;
//int cardRow = (int) cardViewIndex % self.gameCardsGrid.rowCount;
// playingCard.frame = [self.gameCardsGrid frameOfCellAtRow:cardRow inColumn:cardColumn];
playingCard.center = CGPointMake(0, 0);
[self.gameView addSubview:playingCard];
[self snapCardsForNewGame];
}
}
Does it even make sense to use it in this situation? I tried few different things to get the cards fly in one by one but wasn't able to.
Thanks in advance!

Since you're adding all the UISnapBehaviors at the same time, the animator runs them all together. Delay adding them to the animator and they'll be animated on their own.
-(void)snapCardsForNewGame
{
for (PlayingCardView *cardView in self.cards){
NSUInteger cardViewIndex = [self.cards indexOfObject:cardView];
int cardColumn = (int) cardViewIndex / self.gameCardsGrid.rowCount;
int cardRow = (int) cardViewIndex % self.gameCardsGrid.rowCount;
UISnapBehavior *snapCard = [[UISnapBehavior alloc]initWithItem:cardView snapToPoint:[self.gameCardsGrid centerOfCellAtRow:cardRow inColumn:cardColumn]];
snapCard.damping = 1.0;
NSTimeInterval delayTime = 0.01 * cardViewIndex;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.animator addBehavior:snapCard];
});
}
}

Related

How to get UIButton position from UIScrollView

I have a scrollView with many buttons on it. I'm trying to get the Button position form the scroll so I can scroll it to that position. My issue is I always get the same value in _btnV.frame.origin.x once I click on each button. So please how I can resolve this issue?
- (void)scrollView{
segmentWidth = screenWidth / 5;
NSArray *images = #[#"Smiley-1.png",#"Smiley-2.png",#"Smiley-3.png",#"Smiley-4.png",#"Smiley-5.png",#"Smiley-5.png",#"Smiley-5.png",#"Smiley-4.png",#"Smiley-1.png",#"Smiley-2.png",#"Smiley-3.png",#"Smiley-4.png"];
long ScrollContentSize = ((images.count -1)*segmentWidth)+screenWidth;
_scrlView.contentSize = CGSizeMake(ScrollContentSize, _scrlView.frame.size.height);
float screenX2 = screenHeight/2 - segmentWidth/2;
__block float screenX = (screenWidth-segmentWidth)/2;
NSMutableArray *arr = [[NSMutableArray alloc] init];
for(int i=0; i<images.count; i++)
{
_btnV = [[UIButton alloc]init];
[_btnV setFrame:CGRectMake(screenX, screenX2, segmentWidth, segmentWidth)];
[_btnV setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%#",[images objectAtIndex:i]]] forState:UIControlStateNormal];
[_btnV setBackgroundColor:[UIColor redColor]];
[_btnV addTarget:self action:#selector(scrollToMe:) forControlEvents:UIControlEventTouchUpInside];
[_btnV setTag:i];
[arr addObject:_btnV];
[_scrlView addSubview:_btnV];
screenX = screenX+segmentWidth;
}
_menuItems = arr;
[self.view addSubview:_scrlView];
}
-(void) scrollToMe:(id)sender{
int scrollX = _scrlView.contentOffset.x;
int mod = scrollX % segmentWidth;
int position = scrollX/segmentWidth;
float positionOffset = (float) mod / segmentWidth;
scrollX = position * segmentWidth;
NSLog(#"position:%f",_btnV.frame.origin.x);
//scrollX = ...
[_scrlView setContentOffset:CGPointMake(scrollX, scrollX) animated:YES];
}
Find the button using its tag value
-(void) scrollTo:(id)sender{
UIButton * button = (UIButton *)sender;
int position = button.tag;
scrollX = position * segmentWidth;
NSLog(#"position:%f",_btnV.frame.origin.x);
//scrollX = ...
[_scrlView setContentOffset:CGPointMake(scrollX, scrollX) animated:YES];
}
You are calling selector method of UIButton. so in this method, you can directly get UIButton position with respect to UIScrollView. like
-(void) scrollTo:(id)sender{
UIButton* btnClick = (UIButton*)sender;
NSLog(#"X position : %f",btnClick.frame.origin.x);
}
Hope this help you.
try this , it will log position of all your button correctly
-(void) scrollTo:(id)sender{
int scrollX = _scrlView.contentOffset.x;
int mod = scrollX % segmentWidth;
int position = scrollX/segmentWidth;
float positionOffset = (float) mod / segmentWidth;
scrollX = position * segmentWidth;
for(int i=0; i<images.count; i++)
{ if([[[_scrlView subviews] objectAtIndex:i] isKindOfClass:[UIButton class]]) {
Button btn = [[_scrlView subviews] objectAtIndex:i];
NSLog(#"position:%f",btn.frame.origin.x);
}
}
//scrollX = ...
[_scrlView setContentOffset:CGPointMake(scrollX, scrollX) animated:YES];
}

UIImageView animationDuration with fade effects

I'am searching for a (very) simple way to crossfade images within animationDuration time. I tried using NSTimer and some other stuff I found here, but no solution satisfy or helps me.
I create this stuff in a method (without comments) ...
- (UIImageView *)playSequence{
NSArray *structureArray = self.contenManager.ImageViewControllerSize;
NSMutableArray *structuresToPlay = [[NSMutableArray alloc] init];
// walk thru current collection
for(NSInteger i = 0; i < [self.currentCollection count]; i++)
{
NSInteger index = [[self.currentCollection objectAtIndex:i] integerValue];
[structuresToPlay addObject:[UIImage imageNamed:[structureArray objectAtIndex:index]]];
}
_playSequence = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
_playSequence.animationImages = structuresToPlay;
_playSequence.animationDuration = 5 * structuresToPlay.count;
[_playSequence startAnimating];
return _playSequence;
}
Here is the part where the method is called ...
- (void)justPlaySelection
{
UIView *justPlaySelection = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
// some background music code ...
// add play sequence
[justPlaySelection addSubview:self.playSequence];
// add close button
// { ... }
[self presentSemiView:justPlaySelection];
}
- (UIImageView *)playSequence
{
if (_playSequence == nil)
{
NSArray *structureArray = self.contenManager.ImageViewControllerSize;
NSMutableArray *structuresToPlay = [[NSMutableArray alloc] init];
// walk thru current collection
for(NSInteger i = 0; i < [self.currentCollection count]; i++)
{
NSInteger index = [[self.currentCollection objectAtIndex:i] integerValue];
[structuresToPlay addObject:[UIImage imageNamed:[structureArray objectAtIndex:index]]];
}
_playSequence = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)];
}
_playSequence.animationImages = structuresToPlay;
_playSequence.animationDuration = 5 * structuresToPlay.count;
[_playSequence startAnimating];
return _playSequence;
}
Here is another way to switch between to UIImageViews, it wouldn't be hard to add in the fade in and out.
- (void) performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = indexNum.intValue;
BOOL completedSequece = index >= [self.frames count];
if (completedSequece) return;
UIImageView *imgIn;
UIImageView *imgOut;
if (index % 2 == 0) {
imgIn = self.animationImageViewOne;
imgOut = self.animationImageViewTwo;
}
else {
imgIn = self.animationImageViewTwo;
imgOut = self.animationImageViewOne;
}
imgIn.image = [self.frames objectAtIndex:index];
[self.view sendSubviewToBack:imgOut];
double speed = 0.1;
[self performSelector:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index + 1] afterDelay:speed];
}
I created a project that does exactly what you describe (an effect like UIImageView animation sequence, but with a cross-fade between images.)
The project is on github: https://github.com/DuncanMC/Animate-Img
You need to have 2 image views.
The trick I use is to stack them on top of each other, and simply animate the top image view from alpha 1 -> 0, then switch images in the top image view and animate it from alpha 0 -> 1.0, so the new image is revealed.
The project demos a morph transition using only 5 frames. Each frame is marked with a number so you can see the frame changes. It has a switch which lets you turn cross-fading on or off.

How to spawn CCSprites in random heights like flappy bird

In the iOS game flappy bird, there are pipes that generate after a certain distance and they generate at random heights
I am also trying to make flappy bird pipes (I called it a tree branch in my code instead of pipe). Except the pipes are moving vertically instead of horizontally because it is a vertical scrolling game (it scrolls like the game doodle jump)
This is a drawing of what I want it to be: https://docs.google.com/drawings/d/18bxsVsNOlScCvgi1mwuzD2At7R6xKM3QCh6BfAVMuMo/edit?usp=sharing
(The horizontal lines are the branches)
So this is what I have tried to do so far to make the vertical branches (or pipes)...
in my .h
CCSprite *branch;
NSMutableArray *_branches;
CCSprite *obstacle;
CCNode *previousBranch;
CGFloat previousBranchYPosition;
in my .m
#implementation HelloWorldLayer
static const CGFloat firstBranchPosition = 426.f;
static const CGFloat distanceBetweenBranches = 140.f;
#define ARC4RANDOM_MAX 0x100000000
static const CGFloat minimumXPositionRightBranch = 280.f;
static const CGFloat maximumXPositionLeftBranch = 50.f;
static const CGFloat pipeDistance = 100.f;
static const CGFloat maximumXPositionRightBranch = maximumXPositionLeftBranch - pipeDistance;
setBranchInitialPosition method
/* This is where I am setting the initial position of the branches.
So I am specifying the position of the first branch and the other branches after it so it gets placed every time a certain distance is passed. I have a left branch and a right branch*/
-(void) setBranchInitialPosition {
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat range = maximumXPositionRightBranch - minimumXPositionRightBranch;
_rightBranch.position = ccp(minimumXPositionRightBranch + (random * range), _rightBranch.position.y);
_leftBranch.position = ccp(_rightBranch.position.x + pipeDistance, _leftBranch.position.y);
}
spawnNewBranches method
// This is how I want the branches to spawn and I want to add them to an array full of branches
- (void)spawnNewBranches {
previousBranch = [_branches lastObject];
previousBranchYPosition = previousBranch.position.y;
if (!previousBranch) {
// this is the first obstacle
previousBranchYPosition = firstBranchPosition;
}
_rightBranch = [CCSprite spriteWithFile:#"branch.png"];
_leftBranch = [CCSprite spriteWithFile:#"branch.png"];
[_leftBranch addChild:_rightBranch];
[self setBranchInitialPosition];
obstacle = [CCSprite node];
[obstacle addChild:_leftBranch];
obstacle.position = ccp(160, previousBranchYPosition + distanceBetweenBranches);
[self addChild:obstacle];
[_branches addObject:obstacle];
}
scroll method
-(void) scroll:(ccTime)dt
{
// moves the bg
background.position = ccp(screenCenter.x, background.position.y + [[NSUserDefaults standardUserDefaults] integerForKey:#"scrollSpeed"]*dt);
bg2.position = ccp(screenCenter.x, background.position.y-background.contentSize.height);
// it adds the new bg's to the screen before the old bg's move off the screen
if (background.position.y >= screenSize.height*1.5)
{
background.position = ccp(screenCenter.x, (screenCenter.y)-(background.size.height/2));
} else if (bg2.position.y >= screenSize.height*1.5) {
bg2.position = ccp(screenCenter.x, (screenCenter.y)-(bg2.size.height/2));
}
// This is where I want them to appear every certain distance and also move with the brackground
obstacle.position = ccp(obstacle.position.x, obstacle.position.y*[[NSUserDefaults standardUserDefaults] integerForKey:#"scrollSpeed"]*dt);
NSMutableArray *offScreenObstacles = nil;
if (obstacle.position.y >= screenSize.height*1.5) {
[offScreenObstacles addObject:obstacle];
}
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_branches removeObject:obstacleToRemove];
// for each removed obstacle, add a new one
[self spawnNewBranches];
}
}
Right now, the branches are appearing, but they stay in the bottom left corner and they dont move or spawn at all. I want to make them move with the background and spawn after a certain distance while also being generated in random heights. I provided you with all my code, do you know how I can make this work? Thanks in advance!
You may want to try placement of the pipes based on a trigonometric curve like sine or cosine (https://en.wikipedia.org/wiki/Trigonometric_functions). It seems like you are placing the pipes within a fairly define random range though if you change this range to an offset from the plot of the trigonometric curve it would take into account the ability of the player to transition between the open gaps better. At least that's my feel. I think the code would be easier to follow as well as I'm a bit confused going through it. You can also easily vary the difficulty of the curve by changing the parameters such as increasing the amplitude or frequency.
I created a copy of Flappy Bird just for fun. I used this code to create the pipes:
-(void)createPipes{
//Create Random
int from = 65;
int max = [[UIScreen mainScreen] bounds].size.height - 124;
int delta = max - from - dy;
int y = from + arc4random() % (delta - from);
//Pipe Bottom
UIImageView *pipeBottom = [[UIImageView alloc] init];
[pipeBottom setContentMode:UIViewContentModeTop];
[pipeBottom setImage:[UIImage imageNamed:#"pipeBottom"]];
[pipeBottom setFrame:CGRectMake(320, y+dy, 60, max - y - dy)];
[pipeBottom setClipsToBounds:YES];
//Pipe Top
UIImageView *pipeTop = [[UIImageView alloc] init];
[pipeTop setFrame:CGRectMake(320, 0, 60, y)];
[pipeTop setContentMode:UIViewContentModeBottom];
[pipeTop setImage:[UIImage imageNamed:#"pipeTop"]];
[self.view insertSubview:pipeTop atIndex:1];
[self.view insertSubview:pipeBottom atIndex:1];
if (!self.pipes)
self.pipes = [[NSMutableArray alloc] init];
[self.pipes addObject:pipeBottom];
[self.pipes addObject:pipeTop];
}
and to move them:
-(void)moveArray:(NSMutableArray *)array{
float ds = dv * dt;
NSMutableArray *trash = [NSMutableArray array];
for (UIImageView *obj in array) {
CGRect frame = obj.frame;
frame.origin.x -= ds;
if (frame.origin.x < -frame.size.width) {
[obj removeFromSuperview];
[trash addObject:obj];
}else{
obj.frame = frame;
}
}
[array removeObjectsInArray:trash];
}
-(void)movePipes{
[self moveArray:self.pipes];
}
I call this function every 0.01 seconds, to run the game:
-(void)runGame{
_time += dt;
if (_time >= 180.0/dv) {
_time = 0;
[self createPipes];
}
[self movePipes];
[self moveEnemies];
[self moveYoshi];
[self moveBar];
[self verifyScore];
[self verifyCollision];
[self verifyState];
}
I defined dt = 0.01 and dv = 110.
You can see my parody in youtube: (http://www.youtube.com/watch?v=tTcYdpSIKJg)
I hope this help you.
Best, Rafael Castro.

'CALayer position contains NaN: [nan nan]' when working with Bouncing Ball Array in C4

So I'm trying to just recreate a bouncing ball app using xcode and C4. What I'm trying to do right now is re-create some of the examples in Dan Shiffmans Nature of Code book in C4.
So far I've managed to create my own class of "Mover" objects that bounce around the screen. That's all working great. The next step was to start "applying some forces" to the movers and play around with that.
I seem to be getting an exception thats crashing the program now that I've added methods to apply force. I put on exception breakpoints and this line in C4Control.m is where it stops:
if(_animationDuration == 0.0f) super.center = center;
Looking into this more, I notice that neither super.center or center properties have a value assigned to them (both of NaN). I'm not sure what's causing this? This seemed to start once I decided to set each Movers initial velocity/accel to 0 and only assign "fores" (C4Vectors for wind and gravity) to create motion in these objects. When I comment out the 2 lines that apply each force, the problem goes away.
This is the implementation of my mover class:
//Init Method
- (id)initWithPoint:(CGPoint)point andCanvasController:(C4CanvasController *)canvasController {
self = [super init];
if (self) {
canvasFrame = CGPointMake(canvasController.canvas.width,
canvasController.canvas.height);
diam = [C4Math randomInt:70];
mass = diam * 0.5f;
CGRect ellipseFrame = CGRectMake(0, 0, diam, diam);
[self ellipse:ellipseFrame];
self.lineWidth = 0.0f;
self.fillColor = [UIColor colorWithRed:[C4Math randomInt:60]/60
green:[C4Math randomInt:100]/100.0f
blue:0.9f
alpha:0.3f];
int foo = arc4random() % (int)([canvasController.canvas height]);
int bar = arc4random() % (int)([canvasController.canvas width]);
self.location = [C4Vector vectorWithX:foo
Y:bar
Z:0.0f];
self.origin = self.location.CGPoint;
self.accel = [C4Vector vectorWithX:0.00f Y:0.0f Z:0.0f];
self.vel = [C4Vector vectorWithX:0.00f Y:0.0f Z:0.0f];
}
return self;
}
//Method to calculate updated mover position
-(void)update{
[self.vel add:self.accel];
[self.location add:self.vel];
self.origin = self.location.CGPoint;
[self.accel multiplyScalar:0];
[self checkBounds];
}
//Checking if mover has reached edge of screen
-(void)checkBounds{
if (self.origin.x > canvasFrame.x - diam){
self.location.x = canvasFrame.x - diam;
self.vel.x *= -1;
}
else if (self.origin.x < 0){
self.vel.x *= -1;
self.location.x = 0;
}
if(self.origin.y > canvasFrame.y - diam){
self.location.y = canvasFrame.y - diam;
self.vel.y *= -1;
}
else if(self.origin.y < 0){
self.vel.y *= -1;
self.location.y = 0;
}
}
//Start C4Timer to run update every 1/60th of a second
- (void) run{
updateTimer = [C4Timer automaticTimerWithInterval:1/60.0f
target:self
method:#"update"
repeats:YES];
}
//Calculate a force to apply to mover
- (void)applyForce:(C4Vector *)force{
[force divideScalar:mass];
[self.accel add:force];
}
In my C4WorkSpace.m file:
#import "C4Workspace.h"
#import "Mover.h"
#implementation C4WorkSpace{
Mover *testMover;
NSMutableArray *moverArray;
}
-(void)setup {
C4Vector *gravity = [C4Vector vectorWithX:0.01f Y:0.0 Z:0.0f];
C4Vector *wind = [C4Vector vectorWithX:0 Y:0.4 Z:0];
moverArray = [[NSMutableArray alloc]init];
//Not using foo/bar variables right now, but usually use them to create CGPoint p
//int foo = arc4random() % (int)([self.canvas height]);
//int bar = arc4random() % (int)([self.canvas width]);
for (int i = 0; i < 500; i++) {
CGPoint p = CGPointMake(0,0);
Mover *m = [[Mover alloc]initWithPoint:p andCanvasController:self];
[moverArray addObject:m];
}
for (Mover *m in moverArray){
[self addShape:m];
[m run];
[m applyForce:gravity];
[m applyForce:wind];
}
}
#end
Sorry for how long this was, any and all help is much appreciated!

Set sprite using background position

I am new to cocos2d so please help me if u can
I have background moving from right to left, and the background contains small windows with 3 rows of windows
_spaceDust1 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
_spaceDust2 = [CCSprite spriteWithFile:#"bg_front_spacedust.png"];
CGPoint dustSpeed = ccp(0.1 , 0.1);
CGPoint bgSpeed = ccp(0.05 , 0.05);
[_backgroundNode addChild:_spaceDust1 z:0 parallaxRatio:dustSpeed positionOffset:ccp(0,winSize.height / 2)];
[_backgroundNode addChild:_spaceDust2 z:0 parallaxRatio:dustSpeed positionOffset:ccp(_spaceDust1.contentSize.width , winSize.height / 2)];
Now add enemies wich also move from right to left with same speed
_robbers = [[CCArray alloc] initWithCapacity:kNumAstroids];
for (int i = 0; i < kNumAstroids; ++i) {
CCSprite *asteroid = [CCSprite spriteWithSpriteFrameName:#"robber.png"];
asteroid.visible = NO;
[_batchNode addChild:asteroid];
[_robbers addObject:asteroid];
}
in update method:
double curTime = CACurrentMediaTime();
if (curTime > _nextRunemanSpawn) {
float randSecs = [self randomValueBetween:0.20 andValue:1.0];
_nextRunemanSpawn = randSecs + curTime;
float randY = 80.0;
float randY1 = 185.0;
float randY2 = 293.0;
float randDuration = [self randomValueBetween:5.2 andValue:5.2];
float randDuration1 = [self randomValueBetween:1.0 andValue:1.0];
CCSprite *asteroid = [_robbers objectAtIndex:_nextRobber];
_nextRobber++;
if (_nextRobber >= _robbers.count) {
_nextRobber = 0;
}
//[asteroid stopAllActions];
int winChoice = arc4random() % 3;
if (winChoice == 0) {
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY);
asteroid.visible = YES;
}
else if(winChoice == 1){
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY1);
asteroid.visible = YES;
}else {
asteroid.position = ccp(winSize.width +asteroid.contentSize.width / 2 , randY2);
asteroid.visible = YES;
}
[asteroid runAction:[CCSequence actions:[CCMoveBy actionWithDuration:randDuration position:ccp(-winSize.width-asteroid.contentSize.width, 0)],
[CCCallFuncN actionWithTarget:self selector:#selector(setInvisible:)],nil]];
All is going well but i want to set this enemies in to window and in random position
so how can i set x-argument of enemies so it can be fix in to window of the background?
For some reason I cannot comment so this is written as an answer. What exactly are you trying to do? I am a bit confused. It sounds like you want the X position to be set so that it is in one of 3 random positions, is that correct?

Resources