Cocos2d 3.0 + Chipmunk + CCAnimation: moving physics body with animated object. How? - ios

I have a boat with attached physics body. This boat is static physics body. Boat moving with CCAnimateMoveTo from left to right. When I tap on screen my character fall down. I detect collisions well. But I want that after collision my character just fall on boat and keep moving with it. Character is dynamic body. Link to sample video: Sample video
Here I create a boat:
- (void)createBoat
{
currentBoat = [CCSprite spriteWithImageNamed:#"Boat.png"];
currentBoat.position = ccp(0 - currentBoat.boundingBox.size.width, winSize.height * 0.2);
// currentBoat.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){0, 0, currentBoat.contentSize.width, currentBoat.contentSize.height * 0.5} cornerRadius:0];
CGPoint shape[6];
shape[0] = ccp(0, 30);
shape[1] = ccp(64, 10);
shape[2] = ccp(128, 30);
shape[3] = ccp(128, 0);
shape[4] = ccp(0, 0);
shape[5] = ccp(0, 30);
currentBoat.physicsBody = [CCPhysicsBody bodyWithPolylineFromPoints:shape count:6 cornerRadius:0 looped:NO];
currentBoat.physicsBody.type = CCPhysicsBodyTypeStatic;
currentBoat.physicsBody.collisionGroup = #"BoatGroup";
currentBoat.physicsBody.collisionType = #"BoatCollision";
[physicsWorld addChild:currentBoat z:PHYSICS_Z+3];
id actionMoveBoat = [[CCActionMoveTo alloc] initWithDuration:5.0f position:ccp(winSize.width + currentBoat.boundingBox.size.width, currentBoat.position.y)];
id actionMethod = [CCActionCallFunc actionWithTarget:self selector:#selector(createBoat)];
[currentBoat runAction:[CCActionSequence actions:actionMoveBoat, [[CCActionRemove alloc] init], actionMethod, nil]];
}
Character creation:
- (void)createCharacter
{
if (needCharacter)
{
CCSprite *newCharacter = [CCSprite spriteWithImageNamed:#"Character.png"];
newCharacter.opacity = 0;
newCharacter.position = ccp(winSize.width * 0.5, winSize.height * 0.76);
newCharacter.physicsBody = [CCPhysicsBody bodyWithRect:(CGRect){CGPointZero, newCharacter.contentSize} cornerRadius:0];
newCharacter.physicsBody.affectedByGravity = NO;
newCharacter.physicsBody.allowsRotation = YES;
newCharacter.physicsBody.collisionGroup = #"playerGroup";
newCharacter.physicsBody.collisionType = #"playerCollision";
[physicsWorld addChild:newCharacter z:PHYSICS_Z+4];
id actionFadeIn = [[CCActionFadeIn alloc] initWithDuration:0.5];
[newCharacter runAction:actionFadeIn];
[allCharacters addObject:newCharacter];
needCharacter = false;
touchDone = false;
}
}
Then detection touch and collision:
- (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCNode *lastCharacter = [allCharacters lastObject];
if (!touchDone)
{
[lastCharacter.physicsBody applyImpulse:ccp(0, 300)];
lastCharacter.physicsBody.type = CCPhysicsBodyTypeDynamic;
lastCharacter.physicsBody.affectedByGravity = YES;
touchDone = true;
}
}
- (BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair playerCollision:(CCNode *)currentCharacterC BoatCollision:(CCNode *)currentBoatC {
currentCharacterC.physicsBody.collisionType = #"tmpCollision";
CCLOG(#"score++");
if ([allCharacters containsObject:currentCharacterC])
{
score++;
[scores setString:[NSString stringWithFormat:#"%d", score]];
[allCharacters removeAllObjects];
if (lives != 0)
{
needCharacter = true;
[self createCharacter];
}
}
CCLOG(#"allCharacters = %#", allCharacters);
return YES;
}

well I too faced the same problem, tried increasing the friction/surface velocity etc, it didn't really work well.
Finally had to resolve it my own way, whenever player lands on any body, you keep a pointer to that body as say _bodyUnderFeet of the player object.
Now, in the update loop add the velocity of the _bodyUnderFeet to the players calculated velocity. See the use of zeroVel_x
Sample code is here:
void Player::update(float dt)
{
float zeroVel_x = 0;
if(_bodyUnderFeet != NULL)
{
zeroVel_x = _bodyUnderFeet->v.x;
}
cpBody *bd = getCPBody();
assert(bd);
//log("Body angle %f", bd->a);
//check forward backward or at rest
float accel = _accelGround;
if(_onGroundBoost < UP_BOOST)
{
accel = _accelAir;
}
if(_touchPressedB)
{
if(!isFlippedX())
{
setFlippedX(true);
}
//cpBodySetVel(bd, cpv(cpBodyGetVel(bd).x - accel*0.25, 0));
cpVect bdv = cpBodyGetVel(bd);
bdv.x = bdv.x - zeroVel_x;
if(bdv.x > 0) bdv.x = 0;
if(bdv.x > -_maxVelocity.x)
cpBodySetVel(bd, cpv(zeroVel_x + bdv.x - accel*0.25, bdv.y));
}
else if(_touchPressedF)
{
if(isFlippedX())
{
setFlippedX(false);
}
//cpBodySetVel(bd, cpv(cpBodyGetVel(bd).x + accel*0.25, 0));
cpVect bdv = cpBodyGetVel(bd);
bdv.x = bdv.x - zeroVel_x;
if(bdv.x < 0) bdv.x = 0;
if(bdv.x < _maxVelocity.x)
cpBodySetVel(bd, cpv(zeroVel_x+bdv.x + accel*0.25, bdv.y));
}
else
{
cpFloat bd_x = cpBodyGetVel(bd).x;
bd_x = bd_x - zeroVel_x;
if(bd_x>0)
{
if(bd_x > accel*0.25)
{
cpBodySetVel(bd, cpv(zeroVel_x+bd_x - accel*0.25, cpBodyGetVel(bd).y));
}
else
{
cpBodySetVel(bd, cpv(zeroVel_x+0, cpBodyGetVel(bd).y));
}
}
else if(bd_x < 0)
{
if(bd_x < accel*0.25)
{
cpBodySetVel(bd, cpv(zeroVel_x+bd_x + accel*0.25, cpBodyGetVel(bd).y));
}
else
{
cpBodySetVel(bd, cpv(zeroVel_x+0, cpBodyGetVel(bd).y));
}
}
}
//check jump
if(_touchPressedJ)
{
if(_onGroundBoost)
{
cpVect bdv = cpBodyGetVel(bd);
if(bdv.y < 0) bdv.y = 0;
if((bdv.y + _accelUp) < _maxVelocity.y)
{
cpBodySetVel(bd, cpv(bdv.x, bdv.y + _accelUp));
--_onGroundBoost;
}
else
{
cpBodySetVel(bd, cpv(bdv.x, _maxVelocity.y));
_onGroundBoost = 0;
}
}
}
//check shots
if(_touchPressedS)
{
if(!_bulletFired)
{
}
}
//check home
if(_touchPressedH)
{
}
boundRotation(bd);
}

I think,
you can increase the friction between man and boat after he fall down. and reduce the elasticity . then the boat can 'take' the man go together...
just a trick.

Related

NodesAtPoint does not find a node

I'm trying to create 10 balloons in my app. I create a random CGPoint to set my node's position and check, in case that already exist a node at this point I try again.
I don't know why, the balloons keep being placed over another balloon.
Any ideia of what I am doing wrong?
Here is my code:
-(void)gerarBaloes:(int)quantidade
{
balloons = [NSMutableArray new];
u_int32_t maxHorizontal = 900;
u_int32_t minHorizontal = 100;
u_int32_t maxVertical = 650;
u_int32_t minVertical = 100;
for (int i=1; i<= quantidade; i++) {
CGPoint posicao = CGPointMake(arc4random_uniform(maxHorizontal - minHorizontal + 1) + minHorizontal, arc4random_uniform(maxVertical - minVertical + 1) + minVertical);
while (![self validPosition:posicao]) {
posicao = CGPointMake(arc4random_uniform(maxHorizontal - minHorizontal + 1) + minHorizontal, arc4random_uniform(maxVertical - minVertical + 1) + minVertical);
}
balao* nBalao = [[balao alloc]initWithPosition:posicao];
SKLabelNode* numero = [[SKLabelNode alloc]initWithFontNamed:#"Arial Rounded MT Bold"];
numero.text = [NSString stringWithFormat:#"%d",i];
numero.zPosition = 1;
nBalao.body.zPosition = 0;
[nBalao addChild:numero];
nBalao.body.name = #"balloon";
[balloons addObject:nBalao];
[self addChild:nBalao];
}
}
And this is my validation method:
-(BOOL)validPosition:(CGPoint)position
{
NSArray* nodes = [self nodesAtPoint:position];
NSLog(#"total de nodes %d",nodes.count);
if (nodes.count > 0) {
for (SKNode* n in nodes) {
if ([n isKindOfClass:[balao class]]) {
return NO;
}
}
return YES;
}else{
return YES;
}
}
Balao.m class:
#implementation balao
-(id)initWithPosition:(CGPoint)pos
{
if (self = [super init]) {
self.position = pos;
int n = arc4random_uniform(4);
_balloonAtlas = [SKTextureAtlas atlasNamed:[NSString stringWithFormat:#"balloon%d",n]];
_explosionFrames = [NSMutableArray array];
int numImages = _balloonAtlas.textureNames.count;
for (int i=1; i<= numImages; i++){
NSString *textureName = [NSString stringWithFormat:#"balloon_%d",i];
SKTexture *temp = [_balloonAtlas textureNamed:textureName];
[_explosionFrames addObject:temp];
}
SKTexture *textInicial = [_explosionFrames[0] copy];
[_explosionFrames removeObjectAtIndex:0];
self.body = [SKSpriteNode spriteNodeWithTexture:textInicial];
[self addChild:_body];
}
return self;
}
- (void)explodeBalloon
{
SKAction* explosao = [SKAction animateWithTextures:_explosionFrames timePerFrame:0.02f resize:NO restore:YES];
[self.body runAction:explosao completion:^(void){
[self removeFromParent];
}];
[self runAction:[SKAction playSoundFileNamed:#"popSound.mp3" waitForCompletion:NO]];
}
This is how the balloons appears:
Edit:
I tried the suggested method, with CGRectCointainsPoint, but it didn't work too. :/
Checking for nodes at a given point is not enough to prevent overlap. You need to check whether the point falls inside the frame of another balloon. You can modify the function like this
-(BOOL)validPosition:(CGPoint)position
{
NSArray* nodes = [self children];
if (nodes.count > 0) {
for (SKNode* n in nodes) {
if ([n isKindOfClass:[balao class]]) {
if (CGRectContainsPoint([n getBalloonFrame], position)) // Changed line
{
return NO
}
}
}
return YES;
}
return YES;
}
CGRectContainsPoint checks whether a given rectangle contains a point.
Inside balao class define the following function. Also note the changed line in the above code.
-(void)getBalloonFrame
{
return CGRectOffset(self.body.frame, self.frame.origin.x, self.frame.origin.y)
}

Cocos2d, using greater than and less than limits on an integer value to display a sprite

I'm using cocos2d to create a DNA themed Flappy Bird style game (long story, don't expect to make any money off of it). I'm trying to create a system of rewarding a "medal" based on the score reached on a certain round. I'm using and greater than and less than methods to indicate whether or not a certain "medal" should be received. However, all of my medals show up at the same time instead of individually based on score. Here is the applicable code:
NSInteger _points;
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair hero:(CCNode *)hero goal:(CCNode *)goal {
[goal removeFromParent];
_points++;
_scoreLabel.string = [NSString stringWithFormat:#"%d", _points];
[self saveState];
[self loadSavedState];
if (_points > _highScore) {
_highScore = _points;
}
return TRUE;
}
- (void)gameOver {
if (!_gameOver) {
_scrollSpeed = 0.f;
_gameOver = TRUE;
_restartButton.visible = TRUE;
_menuButton.visible = TRUE;
_gameOverText.visible = TRUE;
_score.visible = TRUE;
_highScoreValue.visible = TRUE;
_highScoreLabel.visible = TRUE;
_medal.visible = TRUE;
if (9 < _points < 20) {
_uracil.visible = TRUE;
_uracilLabel.visible = TRUE;
}
if (19 < _points < 30) {
_thymine.visible = TRUE;
_thymineLabel.visible = TRUE;
}
if (29 < _points < 40) {
_cytosine.visible = TRUE;
_cytosineLabel.visible = TRUE;
}
if (39 < _points < 50) {
_guanine.visible = TRUE;
_guanineLabel.visible = TRUE;
}
if (49 < _points < 60) {
_adenine.visible = TRUE;
_adenineLabel.visible = TRUE;
}
_scoreLabel.position = ccp(260, 358);
_hero.physicsBody.allowsRotation = FALSE;
[_hero stopAllActions];
CCActionMoveBy *moveBy = [CCActionMoveBy actionWithDuration:0.2f position:ccp(-5, 5)];
CCActionInterval *reverseMovement = [moveBy reverse];
CCActionSequence *shakeSequence = [CCActionSequence actionWithArray:#[moveBy, reverseMovement]];
CCActionEaseBounce *bounce = [CCActionEaseBounce actionWithAction:shakeSequence];
[self runAction:bounce];
}
}
How can I make it so that the "medals" show up when they are supposed to based on score?
Thanks for the help.
Replace these
if (9 < _points < 20)
With
if (_points > 9 && _points < 20)

How to draw a route between two rectangular Blocks on a custom map using A-Star Algorithm?

I am very new to objective-C, so the task seems typical for me. I am plotting a custom map of rectangular blocks, and also need to draw an interactive route between two given blocks. All the thing is fine, but the problem is that the route drawn using A-Star algorithm goes diagonally looks very scattered. I want a smooth route made by simple lines without any diagonal.
-(void)findPath:(int)startX :(int)startY :(int)endX :(int)endY
{
int x,y;
int newX,newY;
int currentX,currentY;
NSMutableArray *openList, *closedList;
if((startX == endX) && (startY == endY))
return;
openList = [NSMutableArray array];
//BOOL animate = [shouldAnimateButton state];
if(animate)
pointerToOpenList = openList;
closedList = [NSMutableArray array];
PathFindNode *currentNode = nil;
PathFindNode *aNode = nil;
PathFindNode *startNode = [PathFindNode node];
startNode->nodeX = startX;
startNode->nodeY = startY;
startNode->parentNode = nil;
startNode->cost = 0;
[openList addObject: startNode];
while([openList count])
{
currentNode = [self lowestCostNodeInArray: openList];
if((currentNode->nodeX == endX) && (currentNode->nodeY == endY))
{
//********** PATH FOUND ********************
aNode = currentNode->parentNode;
while(aNode->parentNode != nil)
{
tileMap[aNode->nodeX][aNode->nodeY] = TILE_MARKED;
aNode = aNode->parentNode;
}
return;
//*****************************************//
}
else
{
[closedList addObject: currentNode];
[openList removeObject: currentNode];
currentX = currentNode->nodeX;
currentY = currentNode->nodeY;
for(y=-1;y<=1;y++)
{
newY = currentY+y;
for(x=-1;x<=1;x++)
{
newX = currentX+x;
if(y || x) //avoid 0,0
{
if((newX>=0)&&(newY>=0)&&(newX<H)&&(newY<W))
{
if(![self nodeInArray: openList withX: newX Y:newY])
{
if(![self nodeInArray: closedList withX: newX Y:newY])
{
if(![self spaceIsBlocked: newX :newY])
{
//the 'cost':
aNode = [PathFindNode node];
aNode->nodeX = newX;
aNode->nodeY = newY;
aNode->parentNode = currentNode;
aNode->cost = currentNode->cost + 1;
//distance, added to the existing cost
aNode->cost += (abs((newX) - endX) + abs((newY) - endY));
//aNode->cost += MAX(abs(newX - endX), abs(newY - endY));
//aNode->cost += sqrt(pow(newX - endX, 2) + pow(newY - endY, 2));
NSLog(#"Path: %d",aNode->cost);
[openList addObject: aNode];
// if(animate) // animation
// [self setNeedsDisplay];
}
}
}
}
}
}
}
}
}
//**** NO PATH FOUND *****
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"OOPs!!" message:#"Couldn't find a path, Please try for another nearest Exhibitor." delegate:self cancelButtonTitle:#"Done" otherButtonTitles:nil, nil];
[alert show];
}
Finally i am running a loop to check tileMap[][], and the node array tileMap[x][y] marked as TILE_MARKED is the nodes to be drawn in blue colored route.
I am attaching the image below, can anyone kindly help me?
Thanks in advance and have a great day.
-asim

How to make a sprite jump inside update loop in Cocos2D

I have a ball sprite that falls from the top and a boy underneath it. When the ball hits the boy's head I want it to follow a parabolic path. I tried using CCJumpTo as follows but I cannot get it to work. I'm calling the action inside the update loop. Am I not allowed to do that? Cant I call CCJumpTo inside the update loop?
- (void) jump
{
if(!method_called)
{
method_called=TRUE;
CCActionInterval *jump1 = [CCJumpTo actionWithDuration:3 position:CGPointMake(400, 400) height:150 jumps:1];
[_ball runAction:jump1];
NSLog(#"something");
}
else
{
NSLog(#"do nothing");
}
}
- (void)update:(ccTime)dt
{
time ++;
if ( dpad.leftJoystick.velocity.x > 0 && x < 2000 ) {
x = x + 10;
} else if ( dpad.leftJoystick.velocity.x < 0 && x > 0 ) {
x = x - 10;
}
if (x > 10 && x < 2000)
_boy.position = ccp(x, 100);
_ball.position = ccp(ball_x, ball_y);
ball_y = _ball.position.y - (speed * rebound);
_boy.anchorPoint = ccp(0, 0);
CGRect boyBox = CGRectMake(_boy.position.x, _boy.position.y, [_boy boundingBox].size.width, [_boy boundingBox].size.height);
_ball.anchorPoint = ccp(0, 0);
CGRect ballBox = CGRectMake(_ball.position.x, _ball.position.y, [_ball boundingBox].size.width, [_ball boundingBox].size.height);
if (CGRectIntersectsRect(boyBox, ballBox) && flag == 0)
{
rebound = -rebound;
flag = 1;
topFlag = 0;
[_ball stopAllActions];
[self jump];
}
if (_ball.position.y > 700 && topFlag == 0)
{
rebound = -rebound;
flag = 0;
topFlag = 1;
}
}
Thanks in advance.
EDIT:
Here's my init method
-(id) init
{
// always call "super" init
// Apple recommends to re-assign "self" with the "super's" return value
if( (self=[super init]) ) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
self.ball = [CCSprite spriteWithFile:#"ball.png"
rect:CGRectMake(0, 0, 50, 50)];
self.ball.position = ccp(275, 999);
[self addChild:self.ball];
x = 0;
ball_y = 650;
ball_x = 300;
rebound = 1;
flag = 0;
topFlag = 0;
speed = 5;
time = 0;
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
#"boy.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"boy.png"];
//spriteSheet.scaleX = 0.5;
//spriteSheet.scaleY = 0.5;
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <= 8; ++i) {
[walkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"boy%d.png", i]]];
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:.1f];
self.boy = [CCSprite spriteWithSpriteFrameName:#"boy1.png"];
_boy.position = ccp(100000, 100000);
self.walkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim]];
[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO];
[_boy runAction:_walkAction];
_boy.flipX = YES;
[spriteSheet addChild:_boy];
dpad = [[MyJoystick alloc] init];
[self addChild:dpad z:10];
[self schedule:#selector(update:)];
}
[self addChild:spriteSheet];
}
return self;
}
Keep your action code inside a method and call it from update method using a boolean variable so that it would be called once only. Something like this:
take a boolean variable method_called in .h file:
-(void)update:(ccTime)dt
{
_boy.anchorPoint = ccp(0, 0);
CGRect boyBox = CGRectMake(_boy.position.x, _boy.position.y, [_boy boundingBox].size.width, [_boy boundingBox].size.height);
_ball.anchorPoint = ccp(0, 0);
CGRect ballBox = CGRectMake(_ball.position.x, _ball.position.y, [_ball boundingBox].size.width, [_ball boundingBox].size.height);
if (CGRectIntersectsRect(boyBox, ballBox) && flag == 0)
{
flag = 1; topFlag = 0;
[self callJumpAction];
}
}
-(void)callJumpAction
{
if(!method_called)
{
method_called=true;
CCActionInterval *jump1 = [CCJumpTo actionWithDuration:3 position:CGPointMake(400, 400) height:150 jumps:1];
[_ball runAction:jump1];
}
else
{
NSLog(#"do nothing");
}
}
Hope this will help.

How to get PDF annotations when i touch on ipad screen

I have developed an application which displays PDF on ipad using CATiled Layer. So far, so good& But there is a problem which really makes me cut my last hairs. I have a PDF with embedded annotations. Each annotation has an URL. I can find the coordinates of touch area but the question is how I can find if there is an annotation under my finger and how to extract URL to open it in browser?
If anyone can share any thoughts about how this can be done, I will really appreciate your help!
Thanks in advance
Getting the annotations isn't very difficult with CGPDF although it required a little fiddling around for me at first.
//Get the current page ref
CGPDFPageRef currentPdfPage = CGPDFDocumentGetPage(pdfDocumentRef, page);
//Get the page dictionary
CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(currentPdfPage);
CGPDFArrayRef annotsArray;
//Get the Annots array
if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &annotsArray)) {
//DLog(#"No Annots found for page %d", page);
[self updateProgress];
return;
}
int annotsArrayCount = CGPDFArrayGetCount(annotsArray);
//DLog(#"%d annots found for page %d in file %#", annotsArrayCount, page, _fileName);
NSMutableArray* touchRectsArray = [[NSMutableArray alloc] initWithCapacity:annotsArrayCount];
for (int j=annotsArrayCount; j >= 0; j--) {
int destPageNumber = 0;
NSString* uri = nil;
//DLog(#"%d/%d", j+1, annotsArrayCount);
CGPDFObjectRef aDictObj;
if(!CGPDFArrayGetObject(annotsArray, j, &aDictObj)) {
//DLog(#"%#", #"can't get dictionary object");
continue;
}
CGPDFDictionaryRef annotDict;
if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) {
//DLog(#"%#", #"can't get annotDict");
continue;
}
//------------
CGPDFDictionaryRef aDict;
CGPDFArrayRef destArray;
if(CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) {
CGPDFStringRef uriStringRef;
if(CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) {
char* uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);
uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
}
} else {
continue;
}
This will get you the URLs.
Getting the rects:
CGPDFArrayRef rectArray;
if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) {
DLog(#"%#", #"can't get Rect");
}
int arrayCount = CGPDFArrayGetCount(rectArray);
CGPDFReal coords[4];
for (int k = 0; k < arrayCount; k++) {
CGPDFObjectRef rectObj;
if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) {
DLog(#"%#", #"can't get rect data");
}
CGPDFReal coord;
if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) {
DLog(#"%#", #"can't get coords");
}
coords[k] = coord;
}
CGRect drawRect = [[SharedConfig valueForKey:#"screenSize"] CGRectValue];
BOOL drawBoxIsLandscape = NO;
if (1 < drawRect.size.width/drawRect.size.height) {
drawBoxIsLandscape = YES;
}
CGRect pageRect = CGRectIntegral(CGPDFPageGetBoxRect(currentPdfPage, kCGPDFMediaBox));
landscape = NO;
if (1 < pageRect.size.width/pageRect.size.height) {
landscape = YES;
}
float ratio = 0.0;
//Get the rect of the clickable area
//CGRect coordsRect = CGRectMake(coords[0], coords[1], coords[2], coords[3]);
//Transform to new coordinate system
CGRect originalRect = CGRectMake(coords[0], (pageRect.size.height-(coords[3]-coords[1]))-coords[1], coords[2]-coords[0], coords[3]-coords[1]);
CGPDFInteger pageRotate = 0;
CGPDFDictionaryGetInteger(pageDictionary, "Rotate", &pageRotate);
if (pageRotate == 90 || pageRotate == 270) {
CGFloat temp = pageRect.size.width;
pageRect.size.width = pageRect.size.height;
pageRect.size.height = temp;
ratio = drawRect.size.height / pageRect.size.height;
}
if (drawBoxIsLandscape) {
ratio = landscape ? (drawRect.size.height/pageRect.size.height) : (drawRect.size.height/pageRect.size.width);
if (landscape && drawRect.size.width < pageRect.size.width*ratio) {
ratio = drawRect.size.width/pageRect.size.width;
} else if (!landscape && drawRect.size.height < pageRect.size.width*ratio) {
ratio = drawRect.size.height/pageRect.size.width;
}
} else {
ratio = landscape ? (drawRect.size.height/pageRect.size.width) : (drawRect.size.height/pageRect.size.height);
if (landscape && drawRect.size.width < pageRect.size.height*ratio) {
ratio = drawRect.size.width/pageRect.size.height;
} else if (!landscape && drawRect.size.height < pageRect.size.height*ratio) {
ratio = drawRect.size.height/pageRect.size.height;
}
}
CGRect calculatedRect = CGRectMake(originalRect.origin.x*ratio, originalRect.origin.y*ratio, originalRect.size.width*ratio, originalRect.size.height*ratio);
if ((landscape && !drawBoxIsLandscape) || (!landscape && drawBoxIsLandscape)) {
CGFloat width = calculatedRect.size.width;
calculatedRect.size.width = calculatedRect.size.height;
calculatedRect.size.height = width;
CGFloat yModifier = drawRect.size.height-(pageRect.size.width*ratio);
CGFloat x = calculatedRect.origin.x;
calculatedRect.origin.x = calculatedRect.origin.y;
calculatedRect.origin.y = drawRect.size.height-(x+calculatedRect.size.height)-yModifier;
}
if (nil != uri) {
[touchRectsArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithCGRect:calculatedRect], #"rect", uri, #"targetUrl", nil]];
}
As you can see this bit of code first gets the rectangle of the annotation, transforms it to the device coordinate system, then does some recalculation, sizing and repositioning, based on page to screen ratio, rotation factor, etc. At the end you will have an array of the touch-active areas for that page. For handling the touches the following simple solution can be used:
- (void) tapGesture:(UIGestureRecognizer*)sender
{
if (UIGestureRecognizerStateEnded == sender.state) {
CGPoint touchPoint = [sender locationInView:self.view];
if (nil != self.touchRects) for (int i=0; i<[self.touchRects count]; i++) {
if (CGRectContainsPoint([[[self.touchRects objectAtIndex:i] objectForKey:#"rect"] CGRectValue], touchPoint)) {
if ([[self.touchRects objectAtIndex:i] objectForKey:#"targetUrl"]) {
NSString* targetUrl = [[self.touchRects objectAtIndex:i] objectForKey:#"targetUrl"];
DLog(#"Hit found for target url: %#", targetUrl);
NSURL* url = [NSURL URLWithString:targetUrl];
[[UIApplication sharedApplication] openURL:url];
} return;
}
}
DLog(#"No hit found for touch at %#", NSStringFromCGPoint(touchPoint));
}
}

Resources