Objective-C NSArray troubles - iphone

Ok I am trying to deal a deck of cards at certain positions, but I am stuck at a spot. I am only dealing one less than the max. I am trying to deal to every spot in the index of the nsarray.
- (CGPoint)animateDealingToPlayer:(Player *)player withDelay:(NSTimeInterval)delay
{
self.frame = CGRectMake(-100.0f, -100.0f, CardWidth, CardHeight);
self.transform = CGAffineTransformMakeRotation(M_PI);
NSArray *position = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(0, 0)],
[NSValue valueWithCGPoint:CGPointMake(0, 0)],
[NSValue valueWithCGPoint:CGPointMake(0, 0)],
[NSValue valueWithCGPoint:CGPointMake(0, 0)],
[NSValue valueWithCGPoint:CGPointMake(10, 50)],
nil];
for(int i=0; i<5; i++) {
NSValue *value = [position objectAtIndex:i];
CGPoint point = [value CGPointValue];
NSLog(#"%#",NSStringFromCGPoint(point));
self.center = point;
}
}
I am dealing the card at 10,50 and not dealing the card at previous positions such as 0,0. This is probably easy to solve, but I can't seem to remember how to fix this. Please help me. Also, note that I am still somewhat new to objective c so you may have to dumb this down if you solve this lol.

The problem is that you are setting the cards position 5 times without doing any animation or giving the card object a chance to be redrawn in between changing it's position. iOS doesn't actually position the card until the run loop gets a chance to fire. There are a number of possible solutions, but you should probably look into adding an animation block inside your loop so that the movement is queued for the the display.

Related

Draw lines between points(multiple)

with the help i am able to draw circle with the coordinated using:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context,[UIColor grayColor].CGColor);
/**** Get values from reponse and fill it in this array. ******/
NSArray *objectCoords = [NSArray arrayWithObjects: #"{{20,80},{5,5}}", #"{{120,60},{5,5}}", #"{{60,84},{5,5}}", #"{{80,88},{5,5}}", #"{{100,93},{5,5}}", #"{{20,20},{5,5}}", #"{{160,70},{5,5}}", #"{{128,68},{5,5}}", #"{{90,60},{5,5}}", #"{{110,80},{5,5}}", nil];
for (NSString* objectCoord in objectCoords) {
CGRect coord = CGRectFromString(objectCoord);
// Draw using your coord
CGContextAddEllipseInRect(context, coord);
}
Now what i am trying to achieve is draw lines between the points(circle),as shown in the attached image.I know we can draw a line between 2 points, but here in this case, the lines needs to be drawn between one to multiple point/circle .Please suggest me to achieve this result.
You can just draw multiple lines. First, come up with a model to represent between which points you want to draw lines. For example, you could have an array of lines, where each line is defined, itself, as an array with the indices of two points, the starting point and the ending point.
For example, if you want to draw lines from point 1 to 3, 4, and 5, and from point 3 to 4 and 5, and between 4 and 5, you could do something like:
CGContextSetLineWidth(context, 1.0);
NSArray *lines = #[#[#(1), #(3)],
#[#(1), #(4)],
#[#(1), #(5)],
#[#(3), #(4)],
#[#(3), #(5)],
#[#(4), #(5)]];
for (NSArray *points in lines) {
NSInteger startIndex = [points[0] integerValue];
NSInteger endIndex = [points[1] integerValue];
CGRect startRect = CGRectFromString(objectCoords[startIndex]);
CGRect endRect = CGRectFromString(objectCoords[endIndex]);
CGContextMoveToPoint(context, CGRectGetMidX(startRect), CGRectGetMidY(startRect));
CGContextAddLineToPoint(context, CGRectGetMidX(endRect), CGRectGetMidY(endRect));
}
CGContextDrawPath(context, kCGPathStroke);
There are tons of different ways of doing it, but just come up with some model that represents where you want to draw the lines, and then iterate through that model to draw all of the individual lines.
If you wanted to have every point draw lines to the three closest points (which is not what your picture does, but it's what you asked for in a subsequent comment), you can:
build array of indices, indices in your objectCoords array; and
now iterate through each point in objectCoords:
build new array of indices, sortedIndices, sorted by the distance that the point represented by that index is from the current object in objectCoords; and
draw the three closest lines.
Thus:
// build array of indices (0, 1, 2, ...)
NSMutableArray *indices = [NSMutableArray array];
[objectCoords enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[indices addObject:#(idx)];
}];
// now go through all of the points in objectCoords
[objectCoords enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
// build new array of indices sorted by distance from the current point
NSArray *sortedIndices = [indices sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
CGFloat distance1 = [self distanceFromPointAtIndex:idx
toPointAtIndex:[obj1 integerValue]
usingObjectCoords:objectCoords];
CGFloat distance2 = [self distanceFromPointAtIndex:idx
toPointAtIndex:[obj2 integerValue]
usingObjectCoords:objectCoords];
if (distance1 < distance2)
return NSOrderedAscending;
else if (distance1 > distance2)
return NSOrderedDescending;
return NSOrderedSame;
}];
// now draw lines to the three closest indices
// (skipping 0, because that's probably the current point)
for (NSInteger i = 1; i < 4 && i < [sortedIndices count]; i++) {
NSInteger index = [sortedIndices[i] integerValue];
CGRect startRect = CGRectFromString(objectCoords[idx]);
CGRect endRect = CGRectFromString(objectCoords[index]);
CGContextMoveToPoint(context, CGRectGetMidX(startRect), CGRectGetMidY(startRect));
CGContextAddLineToPoint(context, CGRectGetMidX(endRect), CGRectGetMidY(endRect));
}
}];
CGContextSetLineWidth(context, 1);
CGContextDrawPath(context, kCGPathStroke);
And this uses the following method to calculate the distance between two points:
- (CGFloat)distanceFromPointAtIndex:(NSInteger)index1 toPointAtIndex:(NSInteger)index2 usingObjectCoords:(NSArray *)objectCoords
{
CGRect rect1 = CGRectFromString(objectCoords[index1]);
CGRect rect2 = CGRectFromString(objectCoords[index2]);
return hypotf(CGRectGetMidX(rect1) - CGRectGetMidX(rect2), CGRectGetMidY(rect1) - CGRectGetMidY(rect2));
}
Using the objectCoords in your original example, that yields:
This is a little unrelated to your original question, but rather than an array of strings, like so:
NSArray *objectCoords = #[#"{{20,80},{5,5}}",
#"{{120,60},{5,5}}",
#"{{60,84},{5,5}}",
#"{{80,88},{5,5}}",
#"{{100,93},{5,5}}",
#"{{20,20},{5,5}}",
#"{{160,70},{5,5}}",
#"{{128,68},{5,5}}",
#"{{90,60},{5,5}}",
#"{{110,80},{5,5}}"];
I might suggest employing an array of NSValue objects:
NSArray *objectCoords = #[[NSValue valueWithCGRect:CGRectMake(20,80,5,5)],
[NSValue valueWithCGRect:CGRectMake(120,60,5,5)],
[NSValue valueWithCGRect:CGRectMake(60,84,5,5)],
[NSValue valueWithCGRect:CGRectMake(80,88,5,5)],
[NSValue valueWithCGRect:CGRectMake(100,93,5,5)],
[NSValue valueWithCGRect:CGRectMake(20,20,5,5)],
[NSValue valueWithCGRect:CGRectMake(160,70,5,5)],
[NSValue valueWithCGRect:CGRectMake(128,68,5,5)],
[NSValue valueWithCGRect:CGRectMake(90,60,5,5)],
[NSValue valueWithCGRect:CGRectMake(110,80,5,5)]];
Then, when you're extracting the CGRect value, instead of:
CGRect rect = CGRectFromString(objectCoords[index]);
You would do:
CGRect rect = [objectCoords[index] CGRectValue];
I know this looks more cumbersome, but using NSValue is going to be more efficient (which is useful when doing a lot or repeated calculations of distances).
Even better, you might want to define your own model object that more intuitively defines a point that you want to chart, e.g.:
#interface ChartPoint : NSObject
#property (nonatomic) CGPoint center;
#property (nonatomic) CGFloat radius;
#end
and
#implementation ChartPoint
+ (instancetype) chartPointWithCenter:(CGPoint)center radius:(CGFloat)radius
{
ChartPoint *chartPoint = [[ChartPoint alloc] init];
chartPoint.center = center;
chartPoint.radius = radius;
return chartPoint;
}
- (CGFloat)distanceToPoint:(ChartPoint *)otherPoint
{
return hypotf(self.center.x - otherPoint.center.x, self.center.y - otherPoint.center.y);
}
#end
And then you can create an array of them like so:
NSArray *objectCoords = #[[ChartPoint chartPointWithCenter:CGPointMake(20,80) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(120,60) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(60,84) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(80,88) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(100,93) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(20,20) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(160,70) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(128,68) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(90,60) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(110,80) radius:5]];
But this
avoids inefficient CGRectFromString;
avoids needing to do those repeated CGRectGetMidX and CGRectGetMidY calls to determine the center of the CGRect; and, most importantly,
more accurately represents what your objects really are.
Obviously, when you want to draw your points, instead of doing:
NSString *string = objectCoords[idx];
CGRect *rect = CGRectFromString(string);
CGContextAddEllipseInRect(context, rect);
You'd do:
ChartPoint *point = objectCoords[idx];
CGContextAddArc(context, point.center.x, point.center.y, point.radius, 0, M_PI * 2.0, YES);

Setting Buttons frame(only x & y position) from array of CGPoints

1)I have an array of length 10 and named 'pointArrar'.This array contain CGPoints on its each index
2)Also I have 10 buttons.
Now I want to set Buttons frame(only x & y position) from array of CGPoints.I just want to know how to set buttons x and y position from CGPoint contained in array.
Well its very logical.
Lets say your array is like
NSArray * pointArrar = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(5.5, 6.6)],
[NSValue valueWithCGPoint:CGPointMake(7.7, 8.8)],
nil];
Then to add to your various buttons you need to simply put all the points into a loop like
for(int i=0; i<[pointArrar count] ; i++)
{
NSValue *val = [pointArrar objectAtIndex:i];
CGPoint p = [val CGPointValue];
[Button setCenter:p];
}
Here all the points have been added to your button. Hope this works for you :)
As i understand you want to use NSArray and it do not store any structures. So you can you NSValue to wrap you points and to add them to array. Here is an example..
NSArray *points = [NSArray array];
// wrap your point using NSValue
NSValue *pointValue = [NSValue valueWithCGPoint:CGPointMake(x,y)];
[points addObject:pointValue];
/// ... add points
// get your point back...
CGPoint point = [[points objectAtIndex:index] CGPointValue];
button.frame = CGRectMake(point.x, point.y, button.frame.size.width, button.frame.size.height;

IOS: move an UIImageView

In my app I want to move a little UIImageView with inside a .png; this is a little insect and I want to simulate his flight. At example I want that this png do when it move an inverted eight as the infinite simbol ∞
You may use CoreAnimation. You can subclass a view, create a subview for the insect, and then assign an animation to it, following a defined path.
Your UIImageView could be animated. If it's a fly, you can do a few frames for wing moves:
NSArray *images = [NSArray arrayWithObjects:..., nil];
insect.animationImages = images;
insect.animationDuration = ??;
insect.animationRepeatCount = 0;
[insect startAnimating];
Then set an init frame for the insect:
insect.frame = CGRectMake(-120, 310, [[images objectAtIndex:0] size].width, [[images objectAtIndex:0] size].height);
And then define the path:
CGMutablePathRef aPath;
CGFloat arcTop = insect.center.y - 50;
aPath = CGPathCreateMutable();
CGPathMoveToPoint(aPath, NULL, insect.center.x, insect.center.y);
CGPathAddCurveToPoint(aPath, NULL, insect.center.x, arcTop, 240, -100, 490, 360);
CAKeyframeAnimation* arcAnimation = [CAKeyframeAnimation animationWithKeyPath: #"position"];
arcAnimation.repeatCount = HUGE_VALF;
[arcAnimation setDuration: 4.5];
[arcAnimation setAutoreverses: NO];
arcAnimation.removedOnCompletion = NO;
arcAnimation.fillMode = kCAFillModeBoth;
[arcAnimation setPath: aPath];
CFRelease(aPath);
[insect.layer addAnimation: arcAnimation forKey: #"position"];
I leave how to do the infinite loop path up to you :)
Hope it helps!
Normally, if you were to be moving things around, I'd suggest using [UIView animate...]. However, you want something to move on a complex, curvy path. So instead, I'd suggest coming up with an equation that gives the (x,y) for the insect as a function of time, and then start an NSTimer with a fairly small time interval, and every time you get an update, move the insect (perhaps using [UIView animate...]).
Another way to go is to use a 2-d animation framework such as cocos2d - then, you can get an 'update' call linked to the frame refresh rate, inside of which you update the position of your insect using the same equation as from above.

After applying while condition code starts acting differently

I'm developing a game in which I want my image to reduce in size gradually. I'm reducing the frame size gradually in my code when it works fine. [I've already used CGAffineTransform and it doesn't suit my requirement.]
-(void)function
{
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
if(ravanImage1.center.y>=300&&ravanImage1.center.y<=370)
{
q=60;
z=60;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
if(ravanImage1.center.y>=230&&ravanImage1.center.y<=299)
{
q=40;
z=40;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
if(ravanImage1.center.y>=150&&ravanImage1.center.y<=229)
{
q=20;
z=20;
ravanImage1.frame=CGRectMake(150,((ravanImage1.frame.origin.y)-5),q,z);
}
}
But when I apply a while loop for the same code specifying wheather at what point to stop reducing the frame("while that point isn't reached"), it doesn't show the image frame reduction little by little as it shows it otherwise, but directly places the image at the end point with proper frame.
I want it to get displyed the way it gets without the while loop i.e. reduction little by little. Yes, while debugging it steps through all the steps properly.
Can anybody please help me?
As others have pointed out, manually adjusting the frame of your view will give you terrible performance. If you really don't want to use a standard UIView animation block for changing your view, you can specify bounds size values to animate through using a CAKeyframeAnimation applied to your view's layer:
CAKeyframeAnimation * customSizeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"bounds.size"];
NSArray *sizeValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:size1], [NSValue valueWithCGSize:size2], [NSValue valueWithCGSize:size3], nil];
[customSizeAnimation setValues:frameValues];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.5f], [NSNumber numberWithFloat:1.0f], nil];
[customSizeAnimation setKeyTimes:times];
customSizeAnimation.fillMode = kCAFillModeForwards;
customSizeAnimation.removedOnCompletion = NO;
[view.layer addAnimation: customSizeAnimation forKey:#"customSizeAnimation"];
This animation will start at size1, pass through size2 at the midway point in the animation, and end at size3. You can have an arbitrary number of key frames and times for your animation, so you should be able to achieve the effect you desire
EDIT (1/5/2010): Removed kCAAnimationPaced as a calculationMode, which would cause the key times to be ignored. Also, I forgot that frame was a derived property, so you need to animate something like the bounds size instead.
The reason it does that is because it executes the while loop very quickly. I think the best thing to do is put some sort of a delay timer after each step of the while loop, then you'll see each step and it won't just 'jump' to it's final state.
[self setTimer: [NSTimer scheduledTimerWithTimeInterval: 3.5
target: self
selector: #selector (function_name)
userInfo: nil
repeats: YES]];
try using this.
This' my move function in which I'm trying to change the size of my imageView. If you can point out any error, I'll be really grateful..
-(void)move
{
UIImageView *imageViewForAnimation = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ravan.jpg"]];
imageViewForAnimation.alpha = 1.0f;
CGSize size1=CGSizeMake(60,60);
CGSize size2=CGSizeMake(40,40);
CGSize size3=CGSizeMake(20,20);
CAKeyframeAnimation *customFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:#"frame"];
NSArray *sizeValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:size1], [NSValue valueWithCGSize:size2], [NSValue valueWithCGSize:size3], nil];
[customFrameAnimation setValues:sizeValues];
customFrameAnimation.duration=10.0;
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:0.5f], [NSNumber numberWithFloat:1.0f], nil];
[customFrameAnimation setKeyTimes:times];
customFrameAnimation.fillMode = kCAFillModeForwards;
customFrameAnimation.removedOnCompletion = NO;
[imageViewForAnimation.layer addAnimation:customFrameAnimation forKey:#"customFrameAnimation"];
[self.view addSubview:imageViewForAnimation];
}

Chaining animations and memory management

Got a question. I have a subclassed UIView that is acting as my background where I am scrolling the ground. The code is working really nice and according to the Instrumentation, I am not leaking nor is my created and still living Object allocation growing.
I have discovered else where in my application that adding an animation to a UIImageView that is owned by my subclassed UIView seems to bump up my retain count and removing all animations when I am done drops it back down.
My question is this, when you add an animation to a layer with a key, I am assuming that if there is already a used animation in that entry position in the backing dictionary that it is released and goes into the autorelease pool?
For example:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
NSString *keyValue = [theAnimation valueForKey:#"name"];
if ( [keyValue isEqual:#"step1"] && flag ) {
groundImageView2.layer.position = endPos;
CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:midEndPos];
position.duration = (kGroundSpeed/3.8);
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step2-1" forKey:#"name"];
[groundImageView2.layer addAnimation:position forKey:#"positionAnimation"];
groundImageView1.layer.position = startPos;
position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:midStartPos];
position.duration = (kGroundSpeed/3.8);
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step2-2" forKey:#"name"];
[groundImageView1.layer addAnimation:position forKey:#"positionAnimation"];
}
else if ( [keyValue isEqual:#"step2-2"] && flag ) {
groundImageView1.layer.position = midStartPos;
CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:#"position"];
position.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
position.toValue = [NSValue valueWithCGPoint:endPos];
position.duration = 12;
position.fillMode = kCAFillModeForwards;
[position setDelegate:self];
[position setRemovedOnCompletion:NO];
[position setValue:#"step1" forKey:#"name"];
[groundImageView1.layer addAnimation:position forKey:#"positionAnimation"];
}
}
This chains animations infinitely, and as I said one it is running the created and living object allocation doesn't change. I am assuming everytime I add an animation the one that exists in that key position is released.
Just wondering I am correct. Also, I am relatively new to Core Animation. I tried to play around with re-using the animations but got a little impatient. Is it possible to reuse animations?
Thanks!
Bryan
Your assumption is difficult to verify without Apple's source code. When you call addAnimation:forKey:, the CALayer makes a copy of the animation object. There is no guarantee from the API (in the docs or elsewhere) that it will release a running animation when another is added for the same key. It would be much safer for you to explicitly call removeAnimation:forKey: rather than relying on the API to do what you want.
Also, why have you set removedOnCompletion to NO in the code above? The default behavior of a CAAnimation is to remove itself from the layer once it has completed, and then call animationDidStop:finished: on the delegate. You can then add any other animation to the layer with the same key. Isn't this exactly what you're trying to do? It seems like you might be over thinking it a little.
As for your question about reusing animations: since the animation object is copied when added to a CALayer, you can hold a reference to an animation object that you create and keep adding it to as many layers as you want as many times as you like.