moving several balls around a board sprite kit Xcode - sprite-kit

I have a small app that I have several sprite "balls" that move around the board using MotionManager.gravity.
I have code that when one of any of the balls meets an edge the stop at the edge. also if one of any of the balls gets to the corner it stops in the corner. I have also written code in which if two or three of the balls are on any one edge and touch each other (they have collision detection turned on) they stop in they maintain their relative position to the other ball.
Here is my code for that:
//this allocs the motionManager and set up the gravity values.
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.deviceMotionUpdateInterval = 0.005f;
self.motionQueue = [[NSOperationQueue alloc] init];
self.motionQueue.name = [[[NSBundle mainBundle] bundleIdentifier] stringByAppendingString:#".motion"];
self.updatePosition = NO;
[self.motionManager startDeviceMotionUpdatesToQueue:self.motionQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
#synchronized(self) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMaximumFractionDigits:2];
[formatter setRoundingMode: NSNumberFormatterRoundUp];
{
_numberString = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.gravity.x / 15.0 *200]];
_numberStringy = [formatter stringFromNumber:[NSNumber numberWithFloat:motion.gravity.y / 15.0 *200]];
n = [_numberString intValue];
y = [_numberStringy intValue];
}
self.gravity = motion.gravity;
self.updatePosition = YES;
}
}];
[self startDisplayLink];
//this shows how I stop the ball at the edge
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
#synchronized(self) {
if (!self.updatePosition)
return;
self.ball.position = CGPointMake(self.ball.position.x + (n*.99), self.ball.position.y + (y*.99));
self.ball2.position = CGPointMake(self.ball2.position.x + (n*.98), self.ball2.position.y +(y*.98));
self.ball3.position = CGPointMake(self.ball3.position.x + n, self.ball3.position.y + y);
if (ball.position.x <=340) {
ball.position = CGPointMake(340, ball.position.y);
}
if (ball.position.x >=684) {
ball.position = CGPointMake(684, ball.position.y);
}
if (ball.position.y <=148) {
ball.position = CGPointMake( ball.position.x,148);
}
if (ball.position.y >=620) {
ball.position = CGPointMake(ball.position.x,620);
}
//this stops it in the corner
if ( ball.position.x >=684 && ball.position.y <=148 ) {
ball.position = CGPointMake(684, 148);
}
if ( ball.position.x >=684 && ball.position.y >=620 ) {
ball.position = CGPointMake(684, 620);
}
if ( ball.position.x <=340 && ball.position.y <=148 ) {
ball.position = CGPointMake(340, 148);
}
if ( ball.position.x <=340 && ball.position.y >=620 ) {
ball.position = CGPointMake(340, 620);
}
//and this shows how I have set it up that if they are on one side and ball1 is in the corner and ball2 is next and ball3 is next they keep their relative positions. I thought if I set the corner ball and the rest had collision detection turned on they would just stop. They do but they push ball1 in the corner out of its position. So I wrote the code
if (ball2.position.y == ball3.position.y && ball.position.y == ball2.position.y && ball.position.x < ball2.position.x && ball2.position.x < ball3.position.x ) {
int s = (self.ball3.position.x - self.ball2.position.x);
int t = ((self.ball2.position.y - self.ball3.position.y));
k = t + s;
int f =(self.ball2.position.x - self.ball.position.x);
int g = ((self.ball2.position.y - self.ball.position.y));
j = f+g;
int h =(self.ball3.position.x - self.ball.position.x);
int i = ((self.ball3.position.y - self.ball.position.y));
l = h + i;
NSLog(#"ball2centerx:%f",ball2.position.x);
NSLog(#"ball2centery:%f",ball2.position.y);
NSLog(#"ballcenterx:%f",ball.position.x);
NSLog(#"ballcentery:%f",ball.position.y);
NSLog(#"ball3centerx:%f",ball3.position.x);
NSLog(#"ball3centery:%f",ball3.position.y);
CGPoint w = CGPointMake(340, 148);
CGPoint x = CGPointMake(340, 620);
CGPoint v = CGPointMake(684, 148);
CGPoint z = CGPointMake(684, 620);
CGPoint m = CGPointMake(370, 148);
CGPoint q = CGPointMake(370, 620);
CGPoint o = CGPointMake(654, 148);
CGPoint p = CGPointMake(654, 620);
if (CGPointEqualToPoint(ball.position,w) ) {
if (f < 30) {
ball2.position = CGPointMake(370, 148);
}
}
if (CGPointEqualToPoint(ball.position,x)) {
if (f<30) {
ball2.position = CGPointMake(370, 620);
}
}
if (CGPointEqualToPoint(ball3.position,v)) {
if (s <30){
ball2.position = CGPointMake(654, 148);
}
}
if (CGPointEqualToPoint(ball3.position,z)) {
if (s <30){
ball2.position = CGPointMake(654, 620);
}
}
if (CGPointEqualToPoint(ball2.position,m)) {
if (s <30){
ball3.position = CGPointMake(400, 148);
}
}
if (CGPointEqualToPoint(ball2.position,o)) {
if (f<30) {
ball.position = CGPointMake(624, 148);
}
}
if (CGPointEqualToPoint(ball2.position,q)) {
if (s <30){
ball3.position = CGPointMake(400, 620);
}
}
if (CGPointEqualToPoint(ball2.position,p)) {
if (f<30) {
ball.position = CGPointMake(624, 620);
}
}
}
The problem is that as I add more ball the code is going to become extremely complex.
is there a way to write it so that if one ball (any ball) is in the corner and another one is in position two it will stay there and so forth. Kind of like a generic code for all ball configuration. instead of writing it for every configuration?

If you are going to have a number of nodes in your scene, it becomes tedious and impractical to create a property for each one. A good solution is to create each node and add it into an array. The array will allow you to keep a reference to it. The important thing is to create each node with its own unique name. You can do this by creating a counter property.
#property (nonatomic) int myCounter;
Then when you create your node set the node's name property like this:
myCounter++;
myNode.name = [NSString stringWithFormat:#"myNode-%i",myCounter];
Once you have fully created the node, add it to a NSMutableArray like this:
[self.myArray addObject:myNode];
You can add as many nodes to the array as you need.
To enumerate through the array you can do this:
for (SKSpriteNode *object in myArray) {
// let's look for a node with the name of myNode-2
if([myNode.name isEqualToString:#"myNode-2"]) {
NSLog(#"Found it");
}
}
Here is an example to enumerate the array to check for each node's position and set its speed to zero dependent on the IF statement:
for (SKSpriteNode *object in myArray) {
if((object.position.x >= 300) && (object.position.y >= 300)) {
object.physicsBody.velocity = CGVectorMake(0, 0);
}
}
Remember that because you are storing the objects in your array as SKSpriteNodes and enumerating your array using SKSpriteNode, you have access to all the corresponding properties like speed, position, name, etc...

Related

Ball makes contact with player but they are not even closer

What am I missing here? Log says "Ball hits player" every time it collides with frame border or even floor objects if added.
(I´m very new to Sprite Kit and it is my first time working with collisions. This is making me crazy :D)
#import "MyScene.h"
static const int starHitCategory = 1;
static const int playerHitCategory = 2;
static const int ballHitCategory = 3;
#interface MyScene ()
#end
#implementation MyScene{
SKSpriteNode *player;
SKSpriteNode *redball;
SKSpriteNode *star;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.physicsWorld.contactDelegate = self;
self.physicsWorld.gravity = CGVectorMake(0.0f, -3.0f);
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0.0f;
//initalizing player node
player = [SKSpriteNode spriteNodeWithImageNamed:#"player.png"];
player.physicsBody.categoryBitMask = playerHitCategory;
player.physicsBody.contactTestBitMask = playerHitCategory;
player.physicsBody.collisionBitMask = playerHitCategory;
player.position = CGPointMake(75,101);
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.size];
player.physicsBody.friction = 0.0f;
player.physicsBody.linearDamping = 0.5f;
player.physicsBody.mass = 10;
player.physicsBody.dynamic = YES;
player.physicsBody.usesPreciseCollisionDetection = YES;
player.physicsBody.allowsRotation = NO;
player.name = #"player";
[self addChild:player];
//initalizing Redball node
redball = [SKSpriteNode spriteNodeWithImageNamed:#"redbdall.png"];
redball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redball.frame.size.width/2];
redball.position = CGPointMake(290,200);
redball.physicsBody.usesPreciseCollisionDetection = YES;
redball.physicsBody.categoryBitMask = ballHitCategory;
redball.physicsBody.contactTestBitMask = ballHitCategory;
redball.physicsBody.collisionBitMask = ballHitCategory;
redball.physicsBody.friction = 2.0f;
redball.physicsBody.restitution = 1.0f;
redball.physicsBody.linearDamping = 0.0f;
redball.physicsBody.allowsRotation = NO;
[self addChild:redball];
//initalizing Star node
star = [SKSpriteNode spriteNodeWithImageNamed:#"star.png"];
star.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:star.size];
star.physicsBody.categoryBitMask = starHitCategory;
star.physicsBody.contactTestBitMask = starHitCategory;
star.physicsBody.collisionBitMask = starHitCategory;
star.position = CGPointMake(205,125);
[self addChild:star];
}
return self;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
if(firstBody.categoryBitMask == playerHitCategory || secondBody.categoryBitMask == ballHitCategory)
{
NSLog(#"ball hits player");
}
}
#end
Thanks in advance
Your if statement will be satisfied if the player/ball object will collide with whatever object it can collide with since you are not also checking that the other object is of the required category.
You should change you if statement to be more strict since you also do not know which object of the two is the ball or the player or the floor:
if((firstBody.categoryBitMask == playerHitCategory && secondBody.categoryBitMask == ballHitCategory) ||
(firstBody.categoryBitMask == ballHitCategory && secondBody.categoryBitMask == playerHitCategory))
{
NSLog(#"ball hits player");
}
This way you can seperate the collision between the objects. Do the same for other object you desire to detect collision with.
Note that you are also setting the bit masks wrong for each physics body object. Read here
look for the section : "Collision and Contact Example: Rockets in Space"
It is very important to understand what each mask property is used for collisions to be detected correctly. (otherwise you indeed can go nuts about it;))

Show UIImageView in a random position on the screen

I am developing a game that uses the gyroscope to keep enemies in relatively the same place, with the following code:
if([motionManager isGyroAvailable])
{
[motionManager setGyroUpdateInterval:0.05];
[motionManager startGyroUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMGyroData *gyroData, NSError *error)
{
valueX3 = gyroData.rotationRate.y* 50;
valueY3 = gyroData.rotationRate.x* 50;
int newX3 = (int)(enemyufoG.center.x +valueY3);
int newY3 = (int)(enemyufoG.center.y -valueX3);
CGPoint newCenter2 = CGPointMake(newX3, newY3);
enemyufoG.center = newCenter2;
valueX2 = gyroData.rotationRate.y* 50;
valueY2 = gyroData.rotationRate.x* 50;
int newX2 = (int)(enemyufoR.center.x +valueY2);
int newY2 = (int)(enemyufoR.center.y -valueX2);
CGPoint newCenter = CGPointMake(newX2, newY2);
enemyufoR.center = newCenter;
valueX = gyroData.rotationRate.y* 50;
valueY = gyroData.rotationRate.x* 50;
int newX = (int)(enemyAlien.center.x +valueY);
int newY = (int)(enemyAlien.center.y -valueX);
CGPoint newCenter3 = CGPointMake(newX, newY);
enemyAlien.center = newCenter3;
}];
}
Once you shoot an enemy that is in the crosshairs of the gun, it hides the UIImageView, then uses NSTimer to call a different method that shows it again. I would like to have the enemies reappear in random positions on the screen.
CGPoint pos = enemyAlien.center;
if ((pos.x > 254) && (pos.x < 304) && (pos.y > 140) && (pos.y < 160 && _ammoCount != 0))
{
enemyAlien.hidden = YES;
[dangerBar setProgress:dangerBar.progress-0.10];
_killCount = _killCount+3;
[killCountField setText: [NSString stringWithFormat:#"%d", _killCount]];
timer = [NSTimer scheduledTimerWithTimeInterval: 4.0
target: self
selector: #selector(showAlien)
userInfo: nil
repeats: NO];
}
- (void) showAlien {
enemyAlien.hidden = NO;
}
When I try and use enemyAlien.center = enemyAlien.center + arc4random()%100; above enemyAlien.hidden = NO, I get the following error:
'Invalid operands to binary expression ('CGPoint (aka 'struct CGPoint') and 'unsigned int').
You're trying to add an integer to a cgpoint.
try this.
enemyAlien.center = CGPointMake(enemyAlien.center.x + (arc4random()%100),enemyAlien.center.y + (arc4random()%100));
although this will only move the alien in a diagonal direction. You should probably change it to the following for a better experience.
enemyAlien.center = CGPointMake((arc4random()%SCREEN_WIDTH),(arc4random()%SCREEN_HEIGHT));
where SCREEN_WIDTH and SCREEN_HEIGHT are the dimensions of your playing field.

How to implement powerUps and other game altering objects in objective-C or cocos2d

Ok, so I have these powerups that I want to slow/speed up the movement of the other objects in the game for a few seconds.
I have an array of objects that I have a variable called spawnInterval that gets faster and faster as the game progresses, making the ame get harder after a few mins.
But I can't really grasp how to make it so the character in the game will react differently to different objects as in when the fastPowerUp is hit by the character sprite, the spawn interval doesn't change.
And vice versa with the slowPowerUp.
the code I have at the moment is this in a move sequence method that gets called in an update method:
-
(void) updateObstacles:(ccTime)delta{
for (int i = 0; i < 20; i++) {
//int randomizer = CCRANDOM_0_1() * [obstacles count];
//NSLog(#"randomizer: %i",randomizer);
CCSprite* randomObject = [obstacles randomObject];
currentObject = [obstacles indexOfObject:randomObject];
if ([randomObject numberOfRunningActions] == 0) {
[self runObstacleMoveSequence:randomObject withTimer:delta];
break;
}
}
}
-(void) runObstacleMoveSequence:(CCSprite *)object withTimer:(ccTime)delta{
static int time;
//Slowly increase object speed
numObstaclesMoved++;
if (!slowPowerUp && !fastPowerUp) {
time += delta;
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration -= 0.2f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
}
}
}else if (slowPowerUp && !fastPowerUp) {
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration += 3.0f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
if (time >= (delta + 3)) {
slowPowerUp = NO;
obstacleMoveDuration -= 3.0f;
}
}
}
}else if (!slowPowerUp && fastPowerUp) {
if (numObstaclesMoved % 17 == 0 && obstacleMoveDuration > 2.0f) {
obstacleMoveDuration -= 3.0f;
if (spawnInterval > 0.1f) {
[self unschedule:#selector(updateObstacles:)];
[self schedule:#selector(updateObstacles:) interval:spawnInterval];
spawnInterval-=0.1f;
NSLog(#"interval: %f",spawnInterval);
if (time >= (delta + 3)) {
fastPowerUp = NO;
obstacleMoveDuration += 3.0f;
}
}
}
}
CGSize screenSize = [[CCDirector sharedDirector]winSize];
CGPoint aboveScreenPosition = CGPointMake(object.position.x, screenSize.height - object.position.y);
int rotations = (CCRANDOM_0_1()*3) * 360;
float duration = (CCRANDOM_0_1()*5.0f) + 8.0f;
CCMoveTo* move = [CCMoveTo actionWithDuration:obstacleMoveDuration position:aboveScreenPosition];
CCRotateTo* rotate = [CCRotateBy actionWithDuration:duration angle:rotations];
CCSpawn* moveRotate = [CCSpawn actions: move, rotate, nil];
CCCallFuncN* call = [CCCallFuncN actionWithTarget:self selector:#selector(objectAboveScreen:)];
CCSequence* sequence = [CCSequence actions:moveRotate, call, nil];
[object runAction:sequence];
if (time >= (delta + 3)) {
fastPowerUp = NO;
}
}
-(void) objectAboveScreen:(id) sender{
//make sure sender is actually of the right class
NSAssert([sender isKindOfClass:[CCSprite class]], #"sender is not a CCSprite!");
CCSprite* obstacle = (CCSprite*)sender;
//move the back to the bottom of the screen
CGPoint pos = obstacle.position;
CGSize screenSize = [[CCDirector sharedDirector]winSize];
pos.y = (-screenSize.height - [obstacle texture].contentSize.height);
pos.x = CCRANDOM_0_1() * screenSize.width;
obstacle.position = pos;
}
I really just don't know where to go from here... Should I make the powerUps a different class? If so, how would I implement something like this? I really hate trying to ask for someone to solve my question, but I really just can't rack my brain around this and I'm rather new... if it were explained to me, then I know I would be able to implement it in future games on my own...
Thanks in advance, and let me know if more information is needed...
I'd do something like
in the .h file
float speedModifier;
-(void)resetPowerUp;
in the .m
-(void)resetPowerUp
{
speedModifier = 1;
}
wherever you are initializing the level
[self resetPowerUp];
upon collision with powerup:
speedModifier = 2;
[self performSelector:#selector(resetPowerUp) withObject:nil afterDelay:5];
then wherever you are moving whatever it is which speed should be effected by the powerup mode, multiply the speed of the animation (or divide the duration it takes for it to get wherever it's going) by speedModified
hope that helps

display different sprites randomly in cocos2d

am trying to jump the different sprite at randomly .
am having 5 disffrent sprites have to display randomly . one sprite to be displayed
am tryed below code but its crashed :- warning: 'CCSprite' may not respond to '+spriteWithName:'
NSString *Sprit;
NSInteger rnd = arc4random() % 6;
if (rnd == 1) {
Sprit = #"Target.png";
} else if (rnd == 2) {
Sprit = #"3.png";
}else if (rnd == 3) {
Sprit = #"5.png";
} else if (rnd == 4) {
Sprit = #"8.png";
} else if (rnd == 5) {
Sprit = #"10.png";
} else {
Sprit = #"13.png";
}
CCSprite *target = [CCSprite spriteWithName:Sprit];
target.position = ccp(winSize.height + (target.contentSize.height/4), actualX);
[self addChild:target ];
Did you mean to use:
CCSprite *target = [CCSprite spriteWithFile:Sprit];
instead? Note that it's spriteWith*File*
There is some documentation for the CCSprite class here:
http://www.cocos2d-x.org/embedded/cocos2d-x/d4/de7/classcocos2d_1_1_c_c_sprite.html

iPhone Map Kit cluster pinpoints

Regarding iPhone Map Kit cluster pinpoints:
I have 1000's of marks that I want to show on the map but it's just too many to handle so I want to cluster them.
Are there frameworks available or proof of concepts? That this is possible or is already been done?
You can use REVClusterMap to cluster
Note: This is a commercial product I'm affiliated with, but it solves this very problem.
I solved this problem in few of my apps and decided to extract it into a reusable framework. It's called Superpin and it is an (commercial, license costs $149) iOS Framework that internally uses quadtrees for annotation storage and performs grid-based clustering. The algorithm is quite fast, the included sample app is showing airports of the world (more than 30k+ annotations) and it's running quite smooth on an 3G iPhone.
This might be a bit like using a chainsaw to mow the lawn, but here is an excerpt from Algorithms in a Nutshell
Creating a KD-Tree...
public class KDFactory {
// Known comparators for partitioning points along dimensional axes.
private static Comparator<IMultiPoint> comparators[ ] ;
// Recursively construct KDTree using median method on input points.
public static KDTree generate (IMultiPoint [ ] points) {
if (points. length == 0) { return null; }
// median will be the root.
int maxD = points[ 0] . dimensionality( );
KDTree tree = new KDTree(maxD) ;
// Make dimensional comparators that compare points by ith dimension
comparators = new Comparator[ maxD+1] ;
for (int i = 1; i <= maxD; i++) {
comparators[ i] = new DimensionalComparator(i) ;
}
tree. setRoot(generate (1, maxD, points, 0, points. length-1) ) ;
return tree;
}
// generate the node for the d-th dimension (1 <= d <= maxD)
// for points[ left, right]
private static DimensionalNode generate (int d, int maxD,
IMultiPoint points[ ] ,
int left, int right) {
// Handle the easy cases first
if (right < left) { return null; }
if (right == left) { return new DimensionalNode (d, points[ left] ) ; }
// Order the array[ left, right] so the mth element will be the median
// and the elements prior to it will all be <=, though they won' t
// necessarily be sorted; similarly, the elements after will all be >=
int m = 1+(right-left) /2;
Selection. select(points, m, left, right, comparators[ d] ) ;
// Median point on this dimension becomes the parent
DimensionalNode dm = new DimensionalNode (d, points[ left+m-1] ) ;
// update to the next dimension, or reset back to 1
if (++d > maxD) { d = 1; }
// recursively compute left and right sub-trees, which translate
// into ' below' and ' above' for n-dimensions.
dm. setBelow(maxD, generate (d, maxD, points, left, left+m-2) ) ;
dm. setAbove(maxD, generate (d, maxD, points, left+m, right) ) ;
return dm;
}
}
Finding nearest neighbors best: O(log n) worst O(n)
// method in KDTree
public IMultiPoint nearest (IMultiPoint target) {
if (root == null) return null;
// find parent node to which target would have been inserted. This is our
// best shot at locating closest point; compute best distance guess so far
DimensionalNode parent = parent(target) ;
IMultiPoint result = parent. point;
double smallest = target. distance(result) ;
// now start back at the root, and check all rectangles that potentially
// overlap this smallest distance. If better one is found, return it.
double best[ ] = new double[ ] { smallest };
double raw[ ] = target. raw( );
IMultiPoint betterOne = root. nearest (raw, best) ;
if (betterOne ! = null) { return betterOne; }
return result;
}
// method in DimensionalNode. min[ 0] contains best computed shortest distance.
IMultiPoint nearest (double[ ] rawTarget, double min[ ] ) {
// Update minimum if we are closer.
IMultiPoint result = null;
// If shorter, update minimum
double d = shorter(rawTarget, min[ 0] ) ;
if (d >= 0 && d < min[ 0] ) {
min[ 0] = d;
result = point;
}
// determine if we must dive into the subtrees by computing direct
// perpendicular distance to the axis along which node separates
// the plane. If d is smaller than the current smallest distance,
// we could "bleed" over the plane so we must check both.
double dp = Math. abs(coord - rawTarget[ dimension-1] ) ;
IMultiPoint newResult = null;
if (dp < min[ 0] ) {
// must dive into both. Return closest one.
if (above ! = null) {
newResult = above. nearest (rawTarget, min) ;
if (newResult ! = null) { result = newResult; }
}
if (below ! = null) {
newResult = below. nearest(rawTarget, min) ;
if (newResult ! = null) { result = newResult; }
}
} else {
// only need to go in one! Determine which one now.
if (rawTarget[ dimension-1] < coord) {
if (below ! = null) {
newResult = below. nearest (rawTarget, min) ;
}
} else {
if (above ! = null) {
newResult = above. nearest (rawTarget, min) ;
}
}
// Use smaller result, if found.
if (newResult ! = null) { return newResult; }
}
return result;
}
More on KD-Trees at Wikipedia
I tried the others suggested here, and I also found OCMapView which has worked the best.
Its free and allows for easy grouping of annotations, which is what I needed. Its a bit newer & more updated than Revolver and to me is easier to implement.
A proof of concept is the Offline Maps app "OffMaps" ;)
http://itunes.apple.com/us/app/offmaps/id313854422?mt=8
I recently had to implement annotation clustering with MapKit. The solutions mentioned above are good, depending on your use case. I ended up going with FBAnnotationClustering (Objective-C) because it was free, and had lots of stars and few issues on github:
https://github.com/infinum/FBAnnotationClustering
The app I was working on was very map-centric, so it made sense to translate FBAnnotationClustering into Swift. Here's a blog post on the approach, which includes a link to the sample project on github.
http://ribl.co/blog/2015/05/28/map-clustering-with-swift-how-we-implemented-it-into-the-ribl-ios-app/
Inspired by WWDC 2011 video, this code works very well for me. Maybe not the fastest of all proposed here but it's for free and it's definitely the simplest.
It basically use 2 maps. One is hidden and hold every single annotation (allAnnotationMapView in my code). One is visible and show only the clusters or the annotations if single (mapView in my code).
- (void)didZoom:(UIGestureRecognizer*)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self updateVisibleAnnotations];
}
}
- (void)updateVisibleAnnotations {
static float marginFactor = 2.0f;
static float bucketSize = 50.0f;
MKMapRect visibleMapRect = [self.mapView visibleMapRect];
MKMapRect adjustedVisibleMapRect = MKMapRectInset(visibleMapRect, -marginFactor * visibleMapRect.size.width, -marginFactor * visibleMapRect.size.height);
CLLocationCoordinate2D leftCoordinate = [self.mapView convertPoint:CGPointZero toCoordinateFromView:self.view];
CLLocationCoordinate2D rightCoordinate = [self.mapView convertPoint:CGPointMake(bucketSize, 0) toCoordinateFromView:self.view];
double gridSize = MKMapPointForCoordinate(rightCoordinate).x - MKMapPointForCoordinate(leftCoordinate).x;
MKMapRect gridMapRect = MKMapRectMake(0, 0, gridSize, gridSize);
double startX = floor(MKMapRectGetMinX(adjustedVisibleMapRect) / gridSize) * gridSize;
double startY = floor(MKMapRectGetMinY(adjustedVisibleMapRect) / gridSize) * gridSize;
double endX = floor(MKMapRectGetMaxX(adjustedVisibleMapRect) / gridSize) * gridSize;
double endY = floor(MKMapRectGetMaxY(adjustedVisibleMapRect) / gridSize) * gridSize;
gridMapRect.origin.y = startY;
while(MKMapRectGetMinY(gridMapRect) <= endY) {
gridMapRect.origin.x = startX;
while (MKMapRectGetMinX(gridMapRect) <= endX) {
NSSet *allAnnotationsInBucket = [self.allAnnotationMapView annotationsInMapRect:gridMapRect];
NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect];
NSMutableSet *filteredAnnotationsInBucket = [[allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return shouldBeMerged;
}] mutableCopy];
NSSet *notMergedAnnotationsInBucket = [allAnnotationsInBucket objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL isPointMapItem = [obj isKindOfClass:[PointMapItem class]];
BOOL shouldBeMerged = NO;
if (isPointMapItem) {
PointMapItem *pointItem = (PointMapItem *)obj;
shouldBeMerged = pointItem.shouldBeMerged;
}
return isPointMapItem && !shouldBeMerged;
}];
for (PointMapItem *item in notMergedAnnotationsInBucket) {
[self.mapView addAnnotation:item];
}
if(filteredAnnotationsInBucket.count > 0) {
PointMapItem *annotationForGrid = (PointMapItem *)[self annotationInGrid:gridMapRect usingAnnotations:filteredAnnotationsInBucket];
[filteredAnnotationsInBucket removeObject:annotationForGrid];
annotationForGrid.containedAnnotations = [filteredAnnotationsInBucket allObjects];
[self.mapView addAnnotation:annotationForGrid];
//force reload of the image because it's not done if annotationForGrid is already present in the bucket!!
MKAnnotationView* annotationView = [self.mapView viewForAnnotation:annotationForGrid];
NSString *imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO];
UILabel *countLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 2, 8, 8)];
[countLabel setFont:[UIFont fontWithName:POINT_FONT_NAME size:10]];
[countLabel setTextColor:[UIColor whiteColor]];
[annotationView addSubview:countLabel];
imageName = [AnnotationsViewUtils imageNameForItem:annotationForGrid selected:NO];
annotationView.image = [UIImage imageNamed:imageName];
if (filteredAnnotationsInBucket.count > 0){
[self.mapView deselectAnnotation:annotationForGrid animated:NO];
}
for (PointMapItem *annotation in filteredAnnotationsInBucket) {
[self.mapView deselectAnnotation:annotation animated:NO];
annotation.clusterAnnotation = annotationForGrid;
annotation.containedAnnotations = nil;
if ([visibleAnnotationsInBucket containsObject:annotation]) {
CLLocationCoordinate2D actualCoordinate = annotation.coordinate;
[UIView animateWithDuration:0.3 animations:^{
annotation.coordinate = annotation.clusterAnnotation.coordinate;
} completion:^(BOOL finished) {
annotation.coordinate = actualCoordinate;
[self.mapView removeAnnotation:annotation];
}];
}
}
}
gridMapRect.origin.x += gridSize;
}
gridMapRect.origin.y += gridSize;
}
}
- (id<MKAnnotation>)annotationInGrid:(MKMapRect)gridMapRect usingAnnotations:(NSSet *)annotations {
NSSet *visibleAnnotationsInBucket = [self.mapView annotationsInMapRect:gridMapRect];
NSSet *annotationsForGridSet = [annotations objectsPassingTest:^BOOL(id obj, BOOL *stop) {
BOOL returnValue = ([visibleAnnotationsInBucket containsObject:obj]);
if (returnValue) {
*stop = YES;
}
return returnValue;
}];
if (annotationsForGridSet.count != 0) {
return [annotationsForGridSet anyObject];
}
MKMapPoint centerMapPoint = MKMapPointMake(MKMapRectGetMinX(gridMapRect), MKMapRectGetMidY(gridMapRect));
NSArray *sortedAnnotations = [[annotations allObjects] sortedArrayUsingComparator:^(id obj1, id obj2) {
MKMapPoint mapPoint1 = MKMapPointForCoordinate(((id<MKAnnotation>)obj1).coordinate);
MKMapPoint mapPoint2 = MKMapPointForCoordinate(((id<MKAnnotation>)obj2).coordinate);
CLLocationDistance distance1 = MKMetersBetweenMapPoints(mapPoint1, centerMapPoint);
CLLocationDistance distance2 = MKMetersBetweenMapPoints(mapPoint2, centerMapPoint);
if (distance1 < distance2) {
return NSOrderedAscending;
}
else if (distance1 > distance2) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
return [sortedAnnotations objectAtIndex:0];
}
I think Foto Brisko (iTunes link) does this.
I do not think there is a Cocoa Touch framework for it.