I've created a class method that returns a random position as an NSValue object.
I want to store the returned positions in a mutable array so the next time the class method gets called it checks the positions in the mutable array and if it finds one that is too close to the one stored it generates a new one.
What I can't seem to figure out is how to allocate and initialize my mutable array to use with my class method. I've tried allocating it in the class method. but that won't work because it will get initialized on each call therefor the previously stored values are... erased?
then I want to use the generated position to place a number of circles on the view (without them overlapping). right now the randomly generated positions work, but they overlap because of the mutable array not being implemented correctly in my randomposition class.
I'm still a beginner in objective c...and programming in general...
#implementation randomPosition
NSMutableArray *positionsArrayPlayer1;
+(NSValue *) givePosition:(int)player forRadius: (CGFloat) radius
{
if (player == 1)
{
//set range for arc4random
int fromNumberY = 550;
int toNumberY = 950;
//set range for arc4random
int fromNumberX = 0;
int toNumberX = 700;
BOOL far_enough_away = NO;
CGPoint newpoint;
while(!far_enough_away)
{
newpoint = CGPointMake((arc4random()%(toNumberX-fromNumberX+1))+fromNumberX,
(arc4random()%(toNumberY-fromNumberY+1))+fromNumberY);
far_enough_away = YES;
for(NSValue *existing in positionsArrayPlayer1)
{
NSLog(#"test");
CGPoint pointb = [existing CGPointValue];
CGFloat deltay = pointb.y-newpoint.y;
CGFloat deltax = pointb.x-newpoint.x;
CGFloat distance = sqrt(pow(deltax,2) + pow(deltay,2));
//fail if closer than desired radius
if(distance < radius )
{
far_enough_away = NO;
NSLog(#"test");
break;
}
[positionsArrayPlayer1 addObject:[NSValue valueWithCGPoint:newpoint]];
}
NSLog(#"%#",positionsArrayPlayer1);
}
return [NSValue valueWithCGPoint:newpoint];
} else if (player == 2 ){
//more stuff to come here....
} else {
NSLog(#"player invalid");
}
return nil;
}
here's the method I use in my viewcontroller. I also store the circle object that is placed on the view in a separate array for other manipulations.
- (void) positionCirclesPlayer1
{
radius = 70;
CGRect position;
for (int i = 0; i < [colors count];i++)
{
CGRect positionCircleInCenter = CGRectMake(self.view.frame.size.width/2-35, self.view.frame.size.height/2-35, radius, radius);
Circle *myCircle = [[Circle alloc] initWithFrame:positionCircleInCenter radius:radius color:[colors objectAtIndex:i]];
myCircle.label.text = [NSString stringWithFormat:#"%i", i];
myCircle.alpha = 0;
myCircle.userInteractionEnabled = NO;
theNewPointPlayer1 = [randomPosition givePosition:1 forRadius:radius];
position = CGRectMake(theNewPointPlayer1.CGPointValue.x, theNewPointPlayer1.CGPointValue.y, 70, 70);
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
myCircle.frame = position; myCircle.alpha = 1;} completion:nil];
[playerOneCircles addObject:myCircle];
[self.view addSubview:myCircle];
}
}
Before learning Obj-C, you should probably learn something about C :)
+(NSValue*)givePosition:(int)player forRadius:(CGFloat)radius {
//this is executed when the method is called for the first time
static NSMutableArray *positionsArrayPlayer1 = nil;
if (positionsArrayPlayer1 == nil) { //checking if this is the first time execution
positionsArrayPlayer1 = [[NSMutableArray alloc] init];
}
}
or
//global variable accessible only from this file
static NSMutableArray *positionsArrayPlayer1;
#implementation randomPosition
//this method is called when the class is loaded.
+(void)initialize {
positionsArrayPlayer1 = [[NSMutableArray alloc] init];
}
+(NSValue*)givePosition:(int)player forRadius:(CGFloat)radius {
//do something with the array
}
#end
Note that implementing this functionality using class methods is probably not a very good architecture.
Related
I have Thrift server, I can get information and load them in my UITableView, at the first I load 10 rows, and after scrolling to the end each time I want to load 10 more rows(with information) but it's never work for me,
would you please help me in this implementation,
Thanks in advance!
Here is my numberOfRowsInSection method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _notes.count;
}
my method scrollViewDidScroll
-(void)scrollViewDidScroll: (UIScrollView*)scrollView
{
float scrollViewHeight = scrollView.frame.size.height;
float scrollContentSizeHeight = scrollView.contentSize.height;
float scrollOffset = scrollView.contentOffset.y;
if (scrollOffset == 1)
{
[self.tableView setContentOffset:CGPointMake(0, 44)];
}
else if (scrollOffset + scrollViewHeight == scrollContentSizeHeight)
{
// I don't know what should I put here
NSLog(#"%# we are at the end", _notes); //==> _notes is null
//we are at the end, it's in the log each time that scroll, is at the end
}
Here is how I connect to the server
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSURL *url = [NSURL URLWithString:BASE_URL];
THTTPClient *transport = [[THTTPClient alloc] initWithURL:url];
TBinaryProtocol *protocol = [[TBinaryProtocol alloc]
initWithTransport:transport
strictRead:YES
strictWrite:YES];
server = [[thrift_Client alloc] initWithProtocol:protocol];
_notes = [server get_notes:10 offset:0 sort_by:0 search_for:result];
__weak NotesTable *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.notesTable reloadData];
});
});
[self.view setNeedsDisplay];
}
Use SVPullToRefresh, it offers nice encapsulation. Add the library and register your UITableView with
[tableView addInfiniteScrollingWithActionHandler:^{
// prepend data to dataSource, insert cells at top of table view
// call [tableView.pullToRefreshView stopAnimating] when done
}];
Hi use this it works perfectly for me try this...
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
CGPoint offset = aScrollView.contentOffset;
CGRect bounds = aScrollView.bounds;
CGSize size = aScrollView.contentSize;
UIEdgeInsets inset = aScrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance)
{
//Put your load more data method here...
}
}
}
You should put your request. The one that is executing on viewdidappear.. remove from viewdidappear put it inside a method. Then call this method from viewdidappear and from this place you put the log and instead of your implementation of didscroll this is one is better
- (void)scrollViewDidScroll: (UIScrollView *)scroll {
NSInteger currentOffset = scroll.contentOffset.y;
NSInteger maximumOffset = scroll.contentSize.height - scroll.frame.size.height;
// Change 10.0 to adjust the distance from bottom
if (maximumOffset - currentOffset <= 10.0) {
[self methodThatAddsDataAndReloadsTableView];
}
}
I have the following class
#import <UIKit/UIKit.h>
#interface UIImageViewInfo : UIImageView
#property(nonatomic,strong) NSString* colorInfo;
#end
when creating an UIImageViewInfo in function1(post below) I get the correct result
"UIImageViewInfo: 0x1d8acd60; baseClass = UIImageView; frame = (0 0;
20 20); opaque = NO; userInteractionEnabled = NO; tag = 2000; layer =
CALayer: 0x1d8a0560>>"
but creating the something in function2(posted below) I get the -(null)
UIImageViewInfo: 0x8372c40; baseClass = UIImageView; frame = (0 0; 20
20); alpha = 0; hidden = YES; opaque = NO; userInteractionEnabled =
NO; tag = 2000; layer = CALayer: 0x8380090>> - (null)
Why is there a - (null) ??
Thanx in advance
P.S> here is more from my code
-(void)function1{
UIImageViewInfo *image;
self.solutionPinSlots = [[NSMutableArray alloc] init];
for (int i=0; i<M; i++) {
image = [[UIImageViewInfo alloc] initWithImage:[UIImage imageNamed:#"solutionPeg#2x.png"]];
image.translatesAutoresizingMaskIntoConstraints=NO;
image.hidden=YES;
image.alpha = 0;
image.colorInfo = #"clear";
image.tag = 2000*(self.brainController.slotsIndex+1) +i;
[self.solutionPinSlots addObject:image];
[self.view addSubview:(UIImageViewInfo *)self.solutionPinSlots[i]];
}
NSLog(#"solutionSlots: %#",self.solutionPinSlots);
__block int counter = 0;
[self.brainController.solution enumerateObjectsUsingBlock:^(NSString *peg, NSUInteger idx, BOOL *stop){
for (int i=0;i<M;i++) {
if ([self.brainController.slots[self.brainController.slotsIndex][i] isEqual:peg]) {
if (i==idx) {
((UIImageViewInfo *) self.solutionPinSlots[counter]).backgroundColor = [UIColor whiteColor];
((UIImageViewInfo *) self.solutionPinSlots[counter]).colorInfo=#"white";
}else{
((UIImageViewInfo *) self.solutionPinSlots[counter]).backgroundColor = [UIColor blackColor];
((UIImageViewInfo *) self.solutionPinSlots[counter]).colorInfo=#"black";}
((UIImageViewInfo *) self.solutionPinSlots[counter]).hidden=NO;
((UIImageViewInfo *) self.solutionPinSlots[counter++]).alpha=1;
}
}
}];
[self.solutionPinSlots shuffle];
[self updateSolutionPinSlots];
}
-(void)function2{
NSString *obj;
for (int idx=0; idx< M;idx++ ) {
UIImageViewInfo *image = [[UIImageViewInfo alloc] initWithImage:[UIImage imageNamed:#"solutionPeg#2x.png"]];
image.translatesAutoresizingMaskIntoConstraints=NO;
obj = (NSString *) [self.solutionPinSlots objectAtIndex:idx];
image.tag=2000*(self.brainController.slotsIndex+1)+idx;
image.hidden=NO;
image.alpha = 1;
if ([obj isEqualToString:#"clear"]) {
image.hidden=YES;
image.alpha = 0;
image.colorInfo=#"clear";
image.backgroundColor=[UIColor clearColor];
}else if ([obj isEqualToString:#"white"]){
image.colorInfo=#"white";
image.backgroundColor=[UIColor whiteColor];
}else if ([obj isEqualToString:#"black"]){
image.colorInfo=#"black";
image.backgroundColor=[UIColor blackColor];
}else{
NSLog(#"Something is Wrong!!!");
}
[self.solutionPinSlots replaceObjectAtIndex:idx withObject:image];
[self.view addSubview:image];
}
}
Never mind. It is coming from the description method of UIView, which is rather complex and dumps a bunch of information about the view. One of the conditionals is being tripped up by your configuration and causing the (null) at the end.
Safe to ignore.
There is no UIImageViewInfo class in UIKit, so the implementation must be in your project. In that implementation, there is likely a description method that is implemented something like:
- (NSString*)description {
....
return [NSString stringWithFormat:#"UIImageViewInfo:%p; %# - %#", self, [super description], someInstanceVariableThatHappensToBeNil]];
}
Only your description is likely more complex due to the sometimes does this sometimes that output.
Not that you should not prefix classes with UI when subclassing the UIKit.
It is because your NSLog call (which you do not show) is asking for a second object, and that second object is null.
I have an array set up with my colors, but when I swipe the view it always changes the color to the last item in the array.
What am I missing? Is my for loop set up correctly?
Here's my code:
- (void)singleLeftSwipe:(UISwipeGestureRecognizer *)recognizer
{
UIColor * whiteColor = [UIColor whiteColor];
UIColor * blueColor = [UIColor blueColor];
UIColor * redColor = [UIColor redColor];
_colorArray = [[NSArray alloc] initWithObjects:blueColor, redColor, whiteColor, nil];
for (int i = 0; i < [_colorArray count]; i++)
{
id colorObject = [_colorArray objectAtIndex:i];
_noteView.aTextView.backgroundColor = colorObject;
}
}
Thanks!
No, your loop is not set up correctly. You shouldn't be looping with every swipe. The entire loop executes every swipe. This steps through every color and sets the view's color to that color. Naturally, the last color is the one that gets seen.
Instead, keep an index in memory and increment / decrement it with each swipe. After each swipe, update the color of your view.
// Declare two new properties in the class extension
#interface MyClass ()
#property (nonatomic) NSInteger cursor;
#property (nonatomic, strong) NSArray *colorArray;
...
#end
//In your designated initializer (may not be init depending on your superclasses)
//Instantiate the array of colors to choose from.
- (id)init {
self = [super init];
if (self) {
_colorArray = #[ [UIColor whiteColor], [UIColor blueColor], [UIColor redColor] ];
}
return self;
}
//Implement your gesture recognizer callback.
//This handles swipes to the left and right. Left swipes advance cursor, right swipes decrement
- (void)singleSwipe:(UISwipeGestureRecognizer *)recognizer
{
UISwipeGestureRecognizerDirection direction = [recognizer direction];
if (direction == UISwipeGestureRecognizerDirectionLeft) {
// Increment cursor
self.cursor += 1;
// If cursor is outside bounds of array, wrap around.
// Chose not to use % to be more explicit.
if (self.cursor >= [self.colorArray count]) {
self.cursor = 0;
}
}
else if (direction == UISwipeGestureRecognizerDirectionRight) {
// Decrement cursor
self.cursor -= 1;
// If outside bounds of array, wrap around.
if (self.cursor < 0) {
self.cursor = [self.colorArray count] - 1;
}
}
// After adjusting the cursor, we update the color.
[self showColorAtCursor];
}
// Implement a method to change color
- (void)showColorAtCursor
{
UIColor *c = self.colorArray[self.cursor];
_noteView.aTextView.backgroundColor = c;
}
I am new to objective c and trying the learn the basics of creating a UI. In my UIView class, I have created a grid along with buttons, yet those buttons don't actually exist (as far as I can tell). Ideally, when I click a button, the image is supposed to change, but that doesn't happen. Where should I be looking at to fix?
- (id)initWithFrame:(CGRect)frame {
if( self = [super init]){
tiles_ = [NSMutableArray array];
tileClosed = NO;
}
return self = [super initWithFrame:frame];
}
- (void) initTile : (Tile *) sender
{
int MINE_COUNT = 16;
for(int i = 0; i < MINE_COUNT; i++){
while(1){
int rand = random() % [tiles_ count];
Tile * tile = [tiles_ objectAtIndex:rand];
if(tile != sender && !tile.isMine){
tile.isMine = YES;
break;
}
}
}
tileClosed = YES;
}
- (void)drawRect:(CGRect)rect
{
NSLog( #"drawRect:" );
CGContextRef context = UIGraphicsGetCurrentContext();
// shrink into upper left quadrant
CGRect bounds = [self bounds]; // get view's location and size
CGFloat w = CGRectGetWidth( bounds ); // w = width of view (in points)
CGFloat h = CGRectGetHeight ( bounds ); // h = height of view (in points)
dw = w/16.0f; // dw = width of cell (in points)
dh = h/16.0f; // dh = height of cell (in points)
NSLog( #"view (width,height) = (%g,%g)", w, h );
NSLog( #"cell (width,height) = (%g,%g)", dw, dh );
// draw lines to form a 16x16 cell grid
CGContextBeginPath( context ); // begin collecting drawing operations
for ( int i = 1; i < 16; ++i )
{
// draw horizontal grid line
CGContextMoveToPoint( context, 0, i*dh );
CGContextAddLineToPoint( context, w, i*dh );
}
for ( int i = 1; i < 16; ++i )
{
// draw vertical grid line
CGContextMoveToPoint( context, i*dw, 0 );
CGContextAddLineToPoint( context, i*dw, h );
}
for(int x=1; x<16;x++){
for(int y=1;y<16;y++){
Tile * tile = [[Tile alloc] init];
[tile setFrame:CGRectMake(x * 16.0f, y * 16.0f, 16.0f, 16.0f)];
[tile addTarget:self action:#selector(clickCell:) forControlEvents:UIControlEventTouchUpInside];
[tiles_ addObject: tile]; }
}
[[UIColor grayColor] setStroke]; // use gray as stroke color
CGContextDrawPath( context, kCGPathStroke ); // execute collected drawing ops
}
- (void) clickCell : (Tile *) sender
{
if(! tileClosed) [self initTile: sender];
[sender open];
}
Your initwithFrame: method is broken: the return line should just be return self;.
What you're doing at the moment in effect clobbers the tiles_ array, so it ends up as nil, hence attempting to store tiles in it does nothing (and hence they aren't retained).
Your init method is incorrect and you can change it simply to
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame]
if( self)
{
tiles_ = [NSMutableArray array];
tileClosed = NO;
}
return self;
}
and for the buttons to be appearing you are not adding it to the subview the
[self.view addSubview:tile];
is missing.
I am creating an app that will show how a bubble sort works. The one problem I am having is the animation. It seems to be skipping and the animation is firing all at once. Within my if statement I want to swap the two images and animate the swap. Then I want a delay so once they swap it will go back through the loop and swap the next two images. Below is my code. I have tried many things like sleep() but it did not seems to work. The images are four boxes and I'm swapping them by left to right, smallest to largest.
for (int i = 0; i < 3; i++)
{
UIImageView *temp1 = [imagesArray objectAtIndex:i];
UIImageView *temp2 = [imagesArray objectAtIndex:i+1];
temp1Width = temp1.frame.size.width;
temp2Width = temp2.frame.size.width;
if (temp1Width > temp2Width)
{
CGPoint tempPoint = temp1.center;
[UIView animateWithDuration:1.0
delay: 1.0
options: nil
animations:^{
temp1.center = temp2.center;
}
completion:^(BOOL finished){
[UIView animateWithDuration:1.0
delay: 0.0
options: nil
animations:^{
temp2.center = tempPoint;
}
completion:^(BOOL finished){
}];
}];
[imagesArray exchangeObjectAtIndex:i+1 withObjectAtIndex:i];
}
}
Any Help would be appreciated.
If you apply an animation to a view that's already being animated, the new animation replaces the existing animation. It doesn't just get tacked on to the end. That's why your version doesn't work the way you want.
This happens even if you set a delay on the new animation. The existing animation will be removed, and the new animation will start after the specified delay. This prevents k20's simple answer from working. (Too bad, because that would be a really easy solution.)
So, a very simple alternative is to defer adding each additional animation at all. This is pretty easy with the dispatch_after function. First, let's factor out the code that swaps the views' Y origins into a helper function:
static void swapViewYOrigins(UIView *a, UIView *b) {
CGRect aFrame = a.frame;
CGRect bFrame = b.frame;
CGFloat t = aFrame.origin.y;
aFrame.origin.y = bFrame.origin.y;
bFrame.origin.y = t;
a.frame = aFrame;
b.frame = bFrame;
}
Now we can write the sort function, making it use dispatch_after to postpone each successive animation:
- (IBAction)sortButtonWasTapped {
NSTimeInterval duration = 1;
dispatch_time_t time = DISPATCH_TIME_NOW;
for (int i = imagesArray.count - 1; i > 0; --i) {
for (int j = 0; j < i; ++j) {
UIView *a = imagesArray[j];
UIView *b = imagesArray[j+1];
if (a.frame.size.width > b.frame.size.width) {
imagesArray[j] = b;
imagesArray[j+1] = a;
dispatch_after(time, dispatch_get_main_queue(), ^{
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
swapViewYOrigins(a, b);
} completion:nil];
});
time = dispatch_time(time, duration * NSEC_PER_SEC);
}
}
}
}
So this is an ok solution, but it does have one shortcoming. It's not easy to add a button that cancels the animations, because there's no API that removes a pending dispatch_after block from the queue.
If you want to support cancelation, it's better to explicitly manage the queue of pending operations. We can do that by adding an instance variable to hold the queue:
#implementation ViewController {
IBOutletCollection(UIView) NSMutableArray *imagesArray;
NSMutableArray *pendingSwaps;
}
Then, we modify sortButtonWasTapped to add a block to the queue instead of calling dispatch_after, and we make it start running the queue after it finishes sorting:
- (IBAction)sortButtonWasTapped {
pendingSwaps = [NSMutableArray array];
for (int i = imagesArray.count - 1; i > 0; --i) {
for (int j = 0; j < i; ++j) {
UIView *a = imagesArray[j];
UIView *b = imagesArray[j+1];
if (a.frame.size.width > b.frame.size.width) {
imagesArray[j] = b;
imagesArray[j+1] = a;
[pendingSwaps addObject:^{
swapViewYOrigins(a, b);
}];
}
}
}
[self runPendingSwaps];
}
We're using the same swapViewYOrigins function as before. We run the pending swaps like this:
- (void)runPendingSwaps {
if (pendingSwaps.count == 0)
return;
void (^swapBlock)(void) = pendingSwaps[0];
[pendingSwaps removeObjectAtIndex:0];
[UIView animateWithDuration:1 animations:swapBlock completion:^(BOOL finished) {
[self runPendingSwaps];
}];
}
So, we just stop if the queue is empty. Otherwise, we take the first block off the queue, and we use it as the animation block of a UIView animation. We give the animation a completion block that calls runPendingSwaps again, which starts the next pending animation (if there is one) after the current one finishes.
To cancel, we can just set pendingSwaps to nil:
- (IBAction)cancelButtonWasTapped {
pendingSwaps = nil;
}
Note that if the user taps Cancel, the views will not necessarily be in the same order on the screen as they are in imagesArray, because imagesArray is fully sorted when sortButtonWasTapped returns. We can fix that by sorting imagesArray by Y origins (using the built-in sort) before starting the bubble sort:
- (IBAction)sortButtonWasTapped {
[self sortImagesArrayByY];
// etc. same as previous version
}
- (void)sortImagesArrayByY {
[imagesArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
CGFloat d = [obj1 frame].origin.y - [obj2 frame].origin.y;
return
d < 0 ? NSOrderedAscending
: d > 0 ? NSOrderedDescending
: NSOrderedSame;
}];
}
With this in place, you can click the Sort button, then click Cancel before the views have been fully sorted on screen, then click Sort again and it will resume (by performing bubble sort again).
Try doing it implicitly with a timer instead of using a loop and a delay. For example, you could use a scheduled NSTimer whose duration is equal to or greater than the animation time to fire this method:
- (void)performNextAnimation:(NSTimer *)timer
{
BOOL didSwap = NO;
for(NSInteger i = 0; i < [self.imageViews count] - 1; ++i)
{
UIImageView *image1 = [self.imageViews objectAtIndex:i];
UIImageView *image2 = [self.imageViews objectAtIndex:(i + 1)];
if(image1.frame.size.width <= image2.frame.size.width)
continue;
CGPoint center1 = image1.center;
CGPoint center2 = image2.center;
[UIView animateWithDuration:AnimationDuration
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^
{
image1.center = center2;
image2.center = center1;
}
completion:nil];
[self.imageViews exchangeObjectAtIndex:(i + 1) withObjectAtIndex:i];
didSwap = YES;
break;
}
if(!didSwap)
{
[self.animationTimer invalidate];
self.animationTimer = nil;
NSLog(#"Done!");
}
}
This simply finds the first two views that are out of order and swaps them, moving them to one another's center point simultaneously. You can, of course, choose any type of animation to perform for each out-of-order pair.
For completeness, here's how I set up my timer and views in the first place:
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:AnimationDuration
target:self
selector:#selector(performNextAnimation:)
userInfo:nil
repeats:YES];
CGFloat viewCenterX = self.view.center.x;
self.imageViews = [NSMutableArray array];
const CGFloat imageHeight = 35;
const CGFloat imageMargin = 10;
for(size_t i = 0; i < 10; ++i)
{
CGFloat imageWidth = arc4random_uniform(250) + 30;
CGRect imageFrame = CGRectMake(viewCenterX - imageWidth * 0.5, i * (imageHeight + imageMargin) + imageMargin,
imageWidth, imageHeight);
UIImageView *imageView = [[UIImageView alloc] initWithFrame:imageFrame];
imageView.backgroundColor = [UIColor redColor];
[self.view addSubview:imageView];
[self.imageViews addObject:imageView];
}
Even if you call the animateWithDuration:delay: different times, they are being called at almost the same time (the "for" time). As all of them have the same delay, you don't see the animation.
An easy fix would be using a dynamic delay:
[UIView animateWithDuration:AnimationDuration
delay:i
...