objective c pass block as parameter with OOP - iphone

i make a class called "Tile" which is a square, and the passed block will get called when touched.
-(id) initWithRect: (CGRect) r color: (ccColor4B) c block: (void (^) (void)) blk {
if ((self = [super init])) {
rect = r;
color = c;
block = blk;
tile = [CCLayerColor layerWithColor:color width:rect.size.width height:rect.size.height];
tile.position = ccp(rect.origin.x, rect.origin.y);
[self addChild: tile];
self.isTouchEnabled = YES;
}
return self;
}
//rect is the square, i use CCLayerColor to represent the square.
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint touchLocation = [Helper locationFromTouch: touch];
if (CGRectContainsPoint(rect, touchLocation)) {
block();
[tile setColor:ccGRAY];
return YES;
}
else {
return NO;
}
}
//when touched, just call the block.
then i make a couple of Tiles as follows:
Tile* aTile = [Tile tileWithMidPos:ccp(512, 500) width:300 height:200 color:ccc4(250, 250, 250, 250) block:^{
[Helper playEffectButtonClicked];
}];
but all the tiles actually execute the block that is passed by the last tile.
what is the problem here? (every tile is an object, so they should call their own block)

Blocks are allocated on the stack.
In this case the Tile class should make a copy of the blk argument:
-(id) initWithRect: (CGRect) r color: (ccColor4B) c block: (void (^) (void)) blk {
if ((self = [super init])) {
rect = r;
color = c;
block = [blk copy];
tile = [CCLayerColor layerWithColor:color width:rect.size.width height:rect.size.height];
tile.position = ccp(rect.origin.x, rect.origin.y);
[self addChild: tile];
self.isTouchEnabled = YES;
}
return self;
}
- (void)dealloc {
[block release];
[super dealloc];
}
If you are using ARC, you won't need to worry about memory management (copy and release) if they are passed to methods with block parameters. If you pass stack allocated blocks to objects with id parameters, you must still copy:
[myArray addObject:[^{ // some block } copy]];
Mike Ash has an article well worth reading regarding blocks and ARC.

Related

Draw straight line on uiview

I'm writing a small word search game for the iPad.
i have a UIViewController and I tile the letters random in a UIView (lettersView). I set the label's tag in a 10x10 size matrix, using a UILabel for each letter.
Here is my problem: I don't know how to draw over letters (UILabels).
I have researched the matter and am confused which way to use (touch, opengl, quartz, hittest).
I just want to draw a straight line from the first point to the last point (touch down to touch up).
when user taps on the lettersView, I must find which letter was tapped. I can find the letter if I get the which UILabel was tapped first.
while the user moves their finger, it should draw the line, but while the user moves it must delete the old ones.
when the user touches up how can I get the last UILabel? i can check if its drawable, if I get the first and last uilabel.
(i found a small drawing sample written in ARC mode. but i cant convert it to non-ARC.)
i did all what you said. but i have some problems.
it draws the line on drawRect method. when i draw second line, it deletes the old one.
here is the codes:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
touchStart = [[touches anyObject] locationInView:self];
float xPos=touchStart.x;
float yPos=touchStart.y;
startIndexCol = floor(xPos / letterWidth);
startIndexRow = floor(yPos / letterHeight);
xPos = (startIndexCol * letterWidth) + letterWidth/2;
yPos = (startIndexRow * letterHeight) + letterHeight/2;
touchStart = CGPointMake(xPos, yPos);
touchCurrent = touchStart;
[self setNeedsDisplay];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
touchCurrent = [[touches anyObject] locationInView:self];
//CGRect frame = CGRectMake(touchStart.x, touchStart.y, touchCurrent.x, touchCurrent.y);
//[self setNeedsDisplayInRect:frame];
[self setNeedsDisplay];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchCurrent = [[touches anyObject] locationInView:self];
//[self hitTest:touchCurrent withEvent:event];
float xPos=touchCurrent.x;
float yPos=touchCurrent.y;
endIndexCol = floor(xPos / letterWidth);
endIndexRow = floor(yPos / letterHeight);
xPos = (endIndexCol * letterWidth) + letterWidth/2;
yPos = (endIndexRow * letterHeight) + letterHeight/2;
touchCurrent = CGPointMake(xPos, yPos);
//CGRect frame = CGRectMake(touchStart.x, touchStart.y, touchCurrent.x, touchCurrent.y);
//[self setNeedsDisplayInRect:frame];
//check if it can be drawn
if ((startIndexCol == endIndexCol) && (startIndexRow == endIndexRow)) {
//clear, do nothing
touchStart = touchCurrent = (CGPoint) { -1, -1 };
startIndexCol = startIndexRow = endIndexCol = endIndexRow = -1;
}
else{
if (startIndexRow == endIndexRow) {//horizontal
if (endIndexCol > startIndexCol) {
[delegate checkWordInHorizontal:startIndexRow start:startIndexCol end:endIndexCol];
}
else{
[delegate checkWordInHorizontalBack:startIndexRow start:startIndexCol end:endIndexCol];
}
}
else if (startIndexCol == endIndexCol){ //vertical
if (endIndexRow > startIndexRow) {
[delegate checkWordInVertical:startIndexCol start:startIndexRow end:endIndexRow];
}
else{
[delegate checkWordInVerticalBack:startIndexCol start:startIndexRow end:endIndexRow];
}
}
}
[self setNeedsDisplay];
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *subview = [super hitTest:point withEvent:event];
if ([subview isEqual:self]) {
NSLog(#"point:%f - %f", point.x, point.y);
return self;
}
return nil;
}
-(void)drawLine{
NSLog(#"draw it");
drawIt=YES;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (drawIt) {
NSLog(#"drawit");
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 20.0);
CGContextSetRGBStrokeColor(ctx, 1.0, 2.0, 0, 0.5);
CGContextMoveToPoint(ctx, touchStart.x, touchStart.y);
CGContextAddLineToPoint(ctx, touchCurrent.x, touchCurrent.y);
CGContextStrokePath(ctx);
drawIt=NO;
touchStart = touchCurrent = (CGPoint) { -1, -1 };
startIndexCol = startIndexRow = endIndexCol = endIndexRow = -1;
}
[[UIColor redColor] setStroke];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:touchStart];
[path addLineToPoint:touchCurrent];
[path setLineWidth:20.0];
[path strokeWithBlendMode:kCGBlendModeNormal alpha:0.5];
// [path stroke];
}
First, the UILabels are likely to cause you more trouble than good. For such as simple grid, it is likely easier to just draw the letters by hand in drawRect:, along with your connecting line. The overall structure would look like this:
In touchesBegan:withEvent:, you would make a note of the starting point in an ivar.
In touchesMoved:withEvent:, you would update the end-point in an ivar and call [self setNeedsDisplayInRect:]. Pass the rectangle defined by your two points.
In touchesEnded:withEvent:, you would make a hit-test, do whatever processing that entails, and then clear your starting and end points and call [self setNeedsDisplayInRect:].
By "make a hit-test," I mean calculate which letter is under the touch. Since you're laying these out in a grid, this should be very simple math.
In drawRect:, you would use [NSString drawAtPoint:withFont:] to draw each letter. You would then use UIBezierPath to draw the line (you could also use CGPathRef, but I'd personally use the UIKit object).

How to add a magnifier to custom control?

How to add a magnifier to custom control? Control is a child of UIView. (It's a UIWebView - but native magnification functionality doesn't work at some pages.)
UPDATED:
Maybe it's possible to force a draw of magnifier on UIWebView?
1. Add the following files to your project:
MagnifierView.h:
//
// MagnifierView.h
// SimplerMaskTest
//
#import <UIKit/UIKit.h>
#interface MagnifierView : UIView {
UIView *viewToMagnify;
CGPoint touchPoint;
}
#property (nonatomic, retain) UIView *viewToMagnify;
#property (assign) CGPoint touchPoint;
#end
MagnifierView.m:
//
// MagnifierView.m
// SimplerMaskTest
//
#import "MagnifierView.h"
#import <QuartzCore/QuartzCore.h>
#implementation MagnifierView
#synthesize viewToMagnify;
#dynamic touchPoint;
- (id)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame radius:118];
}
- (id)initWithFrame:(CGRect)frame radius:(int)r {
int radius = r;
if ((self = [super initWithFrame:CGRectMake(0, 0, radius, radius)])) {
//Make the layer circular.
self.layer.cornerRadius = radius / 2;
self.layer.masksToBounds = YES;
}
return self;
}
- (void)setTouchPoint:(CGPoint)pt {
touchPoint = pt;
// whenever touchPoint is set, update the position of the magnifier (to just above what's being magnified)
self.center = CGPointMake(pt.x, pt.y-66);
}
- (CGPoint)getTouchPoint {
return touchPoint;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = self.bounds;
CGImageRef mask = [UIImage imageNamed: #"loupe-mask#2x.png"].CGImage;
UIImage *glass = [UIImage imageNamed: #"loupe-hi#2x.png"];
CGContextSaveGState(context);
CGContextClipToMask(context, bounds, mask);
CGContextFillRect(context, bounds);
CGContextScaleCTM(context, 1.2, 1.2);
//draw your subject view here
CGContextTranslateCTM(context,1*(self.frame.size.width*0.5),1*(self.frame.size.height*0.5));
//CGContextScaleCTM(context, 1.5, 1.5);
CGContextTranslateCTM(context,-1*(touchPoint.x),-1*(touchPoint.y));
[self.viewToMagnify.layer renderInContext:context];
CGContextRestoreGState(context);
[glass drawInRect: bounds];
}
- (void)dealloc {
[viewToMagnify release];
[super dealloc];
}
#end
TouchReader.h:
//
// TouchReader.h
// SimplerMaskTest
//
#import <UIKit/UIKit.h>
#import "MagnifierView.h"
#interface TouchReader : UIView {
NSTimer *touchTimer;
MagnifierView *loop;
}
#property (nonatomic, retain) NSTimer *touchTimer;
- (void)addLoop;
- (void)handleAction:(id)timerObj;
#end
TouchReader.m:
//
// TouchReader.m
// SimplerMaskTest
//
#import "TouchReader.h"
#import "MagnifierView.h"
#implementation TouchReader
#synthesize touchTimer;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.touchTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(addLoop) userInfo:nil repeats:NO];
// just create one loop and re-use it.
if (loop == nil) {
loop = [[MagnifierView alloc] init];
loop.viewToMagnify = self;
}
UITouch *touch = [touches anyObject];
loop.touchPoint = [touch locationInView:self];
[loop setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self handleAction:touches];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
}
- (void)addLoop {
// add the loop to the superview. if we add it to the view it magnifies, it'll magnify itself!
[self.superview addSubview:loop];
// here, we could do some nice animation instead of just adding the subview...
}
- (void)handleAction:(id)timerObj {
NSSet *touches = timerObj;
UITouch *touch = [touches anyObject];
loop.touchPoint = [touch locationInView:self];
[loop setNeedsDisplay];
}
- (void)dealloc {
[loop release];
loop = nil;
[super dealloc];
}
#end
Based on: http://coffeeshopped.com/2010/03/a-simpler-magnifying-glass-loupe-view-for-the-iphone
2. Add the following images:
Used images on the code:
loupe-hi#2x.png:
loupe-mask#2x.png:
Original but centered images with a shadow (not used at this moment):
loupe-shadow-hi#2x.png:
loupe-shadow-mask#2x.png:
3. Replace the main UIView on your xib-file by TouchReader
The magnifier will work automaticaly except controls that captures touch events themselfs (for example, UIWebView). And the code above doesn't support the images with a shadow. Please add new answer to the qustion if you successfully fix this issue.
UPDATED:
Change the following code to add UIWebView support. UIView should remain UIView.
#interface TouchReader : UILongPressGestureRecognizer
And add a gesture to webView:
TouchReader* gestureMagnifier = [[[TouchReader alloc] initWithTarget:self action:#selector(handleMagnifier:)] autorelease];
gestureMagnifier.webView = editSource;
gestureMagnifier.delegate = self;
gestureMagnifier.minimumPressDuration = 0.5;
[webView addGestureRecognizer:gestureMagnifier];
TouchReader.h:
//- (void)handleAction:(id)timerObj;
-(void) handleGestureAction:(CGPoint)location;
TouchReader.m:
-(void)awakeFromNib
{
UILongPressGestureRecognizer * longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self addGestureRecognizer:longPressGesture];
}
-(void)handleGesture:(UILongPressGestureRecognizer *)longPressGesture
{
CGPoint location = [longPressGesture locationInView:self];
switch (longPressGesture.state) {
case UIGestureRecognizerStateBegan:
self.touchTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(addLoop)
userInfo:nil
repeats:NO];
// just create one loop and re-use it.
if(loop == nil){
loop = [[MagnifierView alloc] init];
loop.viewToMagnify = self;
}
loop.touchPoint = location;
[loop setNeedsDisplay];
break;
case UIGestureRecognizerStateChanged:
[self handleGestureAction:location];
break;
case UIGestureRecognizerStateEnded:
[self.touchTimer invalidate];
self.touchTimer = nil;
[loop removeFromSuperview];
loop=nil;
break;
default:
break;
}
}
- (void)addLoop {
// add the loop to the superview. if we add it to the view it magnifies, it'll magnify itself!
[self.superview addSubview:loop];
}
-(void) handleGestureAction:(CGPoint)location
{
loop.touchPoint = location;
[loop setNeedsDisplay];
}
You don't need the touches... methods any more.

Cocos2d Chipmunk: Touch Shapes?

Today i got a cocos2d question!
I got a few shapes and the space-manager set up in my .m file by using nodes:
- (CCNode*) createBlockAt:(cpVect)pt
width:(int)w
height:(int)h
mass:(int)mass
{
cpShape *shape = [smgr addRectAt:pt mass:mass width:w height:h rotation:0];
cpShapeNode *node = [cpShapeNode nodeWithShape:shape];
node.color = ccc3(56+rand()%200, 56+rand()%200, 56+rand()%200);
[self addChild:node];
return node;
}
- (CCNode*) createCircleAt:(cpVect)pt
mass:(int)mass
radius:(int)radius
{
cpShape *shape = [smgr addCircleAt:pt mass:mass radius:radius];
cpShapeNode *node1 = [cpShapeNode nodeWithShape:shape];
CCSprite *sprt = [CCSprite spriteWithFile:#"fire.png"];
node1.color = ccc3(56+rand()%200, 56+rand()%200, 56+rand()%200);
[self addChild:node1];
return node1;
}
then i actually make the shapes, set up a background, alloc and start the space-manager in my init method:
- (id) init
{
[super init];
CCSprite *background = [CCSprite spriteWithFile:#"BGP.png"];
background.position = ccp(240,160);
[self addChild:background];
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:NO];
//allocate our space manager
smgr = [[SpaceManagerCocos2d alloc] init];
smgr.constantDt = 1/55.0;
[smgr addWindowContainmentWithFriction:1.0 elasticity:1.0 inset:cpvzero];
[self createBlockAt:cpv(160,50) width:50 height:100 mass:100];
[self createBlockAt:cpv(320,50) width:50 height:100 mass:100];
[self createBlockAt:cpv(240,110) width:210 height:20 mass:100];
[self createCircleAt:cpv(240,140) mass:25 radius:20];
[smgr start];
return self;
}
Now, i want a shape to be removed if it's touched. What is the best way to do that???
I hope that somebody out there can help me :)
Edit:
Plz somebody, start a bounty! Or i never get this answered :(
-DD
How about like this?
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
CGPoint point = [self convertTouchToNodeSpace:touch];
cpShape *shape = [smgr getShapeAt:point];
if (shape) {
[self removeChild:shape->data cleanup:YES];
[smgr removeAndFreeShape:shape];
}
return YES;
}

how to create a path using coregraphics?

i want to create a path.something like i touch the screen and draw line in touchmove event.when line intersect from starting point.fill that path using any colour.
now see in the image i've drawn a line.i just want to detect if line intersects again to start point.then fill that path with my own desired color.also i m using core graphics to draw line but it's very slow on real device.could you tell me a way to improve speed?
Header:
#import <UIKit/UIKit.h>
#interface myView : UIView {
CGMutablePathRef path;
CGPathRef drawingPath;
CGRect start;
BOOL outsideStart;
}
#end
Implementation:
#import "myView.h"
#implementation myView
- (id) init {
if (self = [super init]) {
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
}
}
- (void) finishPath {
if (drawingPath) {
CGPathRelease(drawingPath);
}
CGPathCloseSubpath(path);
drawingPath = CGPathCreateCopy(path);
CGPathRelease(path);
path = NULL;
[self setNeedsDisplay];
return;
}
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
path = CGPathCreateMutable();
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
start = CGRectZero;
start.origin = p;
start = CGRectInset(start,-10, -10);
outsideStart = NO;
CGPathMoveToPoint(path, NULL, p.x, p.y);
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!path) {
return;
}
UITouch *t = [touches anyObject];
CGPoint p = [t locationInView:self];
if (CGRectContainsPoint(start,p)) {
if (outsideStart) {
[self finishPath];
}
} else {
outsideStart = YES;
}
CGPathAddLineToPoint(path,NULL,p.x,p.y);
[self setNeedsDisplay];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!path) {
return;
}
[self finishPath];
}
- (void) touchesCanceled:(NSSet *)touches withEvent:(UIEvent *)event {
if (!path) {
return;
}
CGPathRelease(path);
path = NULL;
}
- (void) drawInRect:(CGRect)rect {
CGContextRef g = UIGraphicsGetCurrentContext();
if (drawingPath) {
CGContextAddPath(g,drawingPath);
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
CGContextDrawPath(g,kCGPathFillStroke);
}
if (path) {
CGContextAddPath(g,path);
[[UIColor blackColor] setStroke];
CGContextDrawPath(g,kCGPathStroke);
}
}
- (void) dealloc {
if (drawingPath) {
CGPathRelease(drawingPath);
}
if (path) {
CGPathRelease(path);
}
[super dealloc];
}
#end
Note that you will probably want to do something so you aren't actually calling setNeedsDisplay every time the path changes. This can get very slow. Suggestions include having an NSTimer that fires every x milliseconds to check if it needs to redisplay and do so if it does, or only redrawing if the touch has moved a significant distance.
Maybe it can be useful to you link text
Core Graphics should definitely not be using [self setNeedsDisplay] every time the image changes, which is probably why your code is so slow on the device. Drawing is slow. If you use OpenGL ES to draw the lines, it will be much quicker, at the expense of being more difficult to understand.

How to enable touchEvents (scroll and pan) in MKMapview below a custom UIView?

alt text http://www.gisnotes.com/wordpress/wp-content/uploads/2009/09/poly.png
In a nutshell, I am trying to figure out how to scale the geometry (point, line, and polygon) implemented in a custom view (geometryView) on top of MKMapView (mapView).
What I did was..
Create DrawMapViewController. Add the UIBarButtonItems (MAP, PT, LN, PG) on the bottom toolbar.
When you click on map button, you are able to pan/zoom on the Map. This enables the mapView by setting the geometryView.hidden = YES;
When any of the three geometry buttons is clicked, the geometryView is displayed by geometryView.hidden = NO, thus, enabling touchEvents and drawing the geometry from GeometryView.drawRect's method.
Layer ordering is as follows: mapView is at the bottom of geometryView.
-geometryView
-mapView
What is my problem?
Ideally, during "map" mode and when the user is panning and zooming, I am hoping if its possible to display the drawing from geometryView. But when the user hits "map", then geometryView.hidden = YES, thus the drawing disappears. If I make geometryView visible, then the user interacts with geometryView not mapView, so there's no zooming and panning.
Is it possible to handle touchEvents (pan/zoom) of MKMapView below a custom view while the custom View is displayed? Any other ideas/approaches is very much appreciated.
Thanks,
Rupert
GeometryView Listing:
#synthesize mapview, pinFactory;
- (id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSLog(#"DrawRect called");
CGContextRef context = UIGraphicsGetCurrentContext();
// Drawing lines with a white stroke color
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
if(pinFactory.geometryState == 2){ //Draw Line
if( [pinFactory actualPinCount] > 1){
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
for (int i = 1; i < ([pinFactory actualPinCount]); i++)
{
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
}
CGContextStrokePath(context);
}
}
else if(pinFactory.geometryState == 3){ //Draw Polygon
//if there are two points, draw a line first.
//if there are three points, fill the polygon
if( [pinFactory actualPinCount] == 2){
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:1];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
CGContextStrokePath(context);
}
else if([pinFactory actualPinCount] > 2){
//fill with a blue color
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
Pin *pin1 = (Pin *)[[pinFactory pinArray] objectAtIndex:0];
CGPoint pt1 = pin1.touchLocation;
CGContextMoveToPoint(context, pt1.x, pt1.y);
for (int i = 1; i < ([pinFactory actualPinCount]); i++)
{
Pin *pin2 = (Pin *)[[pinFactory pinArray] objectAtIndex:i];
CGPoint pt2 = pin2.touchLocation;
CGContextAddLineToPoint(context, pt2.x, pt2.y);
}
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self setNeedsDisplay];
UITouch* aTouch = [touches anyObject];
location = [aTouch locationInView:self];
NSLog(#"touchesBegan: x:%f, y:%f", location.x, location.y );
CLLocationCoordinate2D coordinate = [mapview convertPoint:location toCoordinateFromView:self];
switch (pinFactory.geometryState) {
case 1:{
if( [pinFactory actualPinCount] == 1){
//[UIView beginAnimations:#"stalk" context:nil];
//[UIView setAnimationDuration:1];
//[UIView setAnimationBeginsFromCurrentState:YES];
Pin *pin = (Pin *)[pinFactory getObjectAtIndex:0];
[mapview removeAnnotation:pin];
[pinFactory removeObject:pin];
Pin *newPin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:newPin];
[mapview addAnnotation:newPin];
[newPin release];
//[UIView commitAnimations];
}
else{
//Lets add a new pin to the geometry
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
}
break;
}
case 2:{
//Lets add a new pin
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
[self setNeedsDisplay];
break;
}
case 3:{
//Lets add a new pin
Pin *pin = [[Pin alloc] initWithCoordinate:coordinate initLocation:location withTitle:#"My Pin"];
[pinFactory addObject:pin];
[mapview addAnnotation:pin];
[pin release];
[self setNeedsDisplay];
break;
}
default:
break;
}
}
- (void)dealloc {
[super dealloc];
}
#end
DrawMapViewController Listing:
#import "DrawMapViewController.h"
#import "Pin.h"
#implementation DrawMapViewController
#synthesize mapview, mapBarButton, pointBarButton, lineBarButton, polygonBarButton, geometryView;
/* State represents state of the map
* 0 = map
* 1 = point
* 2 = line
* 3 = polygon
*/
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
self.title = #"Map";
}
return self;
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
mapview.mapType = MKMapTypeSatellite;
NSMutableArray *pinArray = [[NSMutableArray alloc] initWithObjects:nil];
pinFactory = [[PinFactory alloc] initWithArray:pinArray];
pinFactory.map = mapview;
[pinArray release];
geometryView = [[GeometryView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 372.0f)];
geometryView.pinFactory = pinFactory;
geometryView.mapview = mapview;
geometryView.backgroundColor = [UIColor clearColor];
[self.view addSubview:geometryView];
[self changeButtonAndViewState:0];
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[mapBarButton release];
[pointBarButton release];
[lineBarButton release];
[polygonBarButton release];
[super dealloc];
}
- (IBAction)mapBarButtonPressed{
NSLog(#"mapBarButtonPressed");
[self changeButtonAndViewState:0];
}
- (IBAction)pointBarButtonPressed{
NSLog(#"pointBarButtonPressed");
[self changeButtonAndViewState:1];
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
}
- (IBAction)lineBarButtonPressed{
NSLog(#"lineBarButtonPressed");
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
[self changeButtonAndViewState:2];
}
- (IBAction)polygonBarButtonPressed{
NSLog(#"polygonBarButtonPressed");
if( [pinFactory actualPinCount] > 0){
[self resetGeometry];
}
[self changeButtonAndViewState:3];
}
- (void)resetGeometry{
NSLog(#"resetting geometry.. deleting all pins");
[mapview removeAnnotations:[pinFactory pinArray]];
NSMutableArray *array = [pinFactory pinArray];
[array removeAllObjects];
[geometryView setNeedsDisplay];
}
- (void)changeButtonAndViewState:(int)s{
[pinFactory setGeometryState:s];
mapBarButton.style = UIBarButtonItemStyleBordered;
pointBarButton.style = UIBarButtonItemStyleBordered;
lineBarButton.style = UIBarButtonItemStyleBordered;
polygonBarButton.style = UIBarButtonItemStyleBordered;
pointBarButton.enabled = YES;
lineBarButton.enabled = YES;
polygonBarButton.enabled = YES;
switch ([pinFactory geometryState]) {
case 0:{
mapBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = YES;
break;
}
case 1:{
pointBarButton.enabled = NO;
pointBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
case 2:{
lineBarButton.enabled = NO;
lineBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
case 3:{
polygonBarButton.enabled = NO;
polygonBarButton.style = UIBarButtonItemStyleDone;
geometryView.hidden = NO;
break;
}
default:
break;
}
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animate{
NSLog(#"regionDidChangeAnimated");
}
#end
Hey I see nobody has answered you, and I just figured out how to do this. Unfortunately you cannot intercept events and forward them to the mapView so something like
#interface MapOverlay
MKMapView* map;
#end
#implementation
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Do my stuff
foo();
bar();
// Forward to the map
[map touchesBegan....];
}
#end
This is what you want to do but it WILL NOT WORK. For some reason you cannot intercept the map's events, nor can you do the reverse and subclass MKMapView and overload it's touch methods. The only way I have found to do this is as follows.
Create your MapOverlay view, set it to have a transparent background and disable touches to it. Overlay this on top of your MKMapView. Then do the following
Subclass UIWindow with a custom class that will forward all touches to your overlay, either handling the logic as "if the overlay is not hidden, then forward", or in the overlay itself keep state. Anyway it looks like this
#implementation CustomWindow
- (void)sendEvent:(UIEvent*)event
{
[super sendEvent:event];
// Forward the event to the overlay
}
When you are forwarding the event to the overlay, you first check if the touches are inside of your mapView region, then figure out what type of touches they are, then call the correct touch method on your overlay.
Good luck!
I know this answer probably comes too late, but I'll take a stab anyway for the benefit of those (such as myself) who've also come across this problem as well.
The MKMapView class's touch events are all handled by a UIScrollView internally. You can capture the events of this scroll view by making the MKMapView a subview of a custom UIView, and providing your custom touches methods in the custom view.
The trick is to keep track of the UIScrollView used by the MKMapView. To do this, I overrode the "hitTest" method, which returns "self", which I believe means this custom view should handle the touch events.
Also, the hitTest method gets the UIScrollView of the MKMapView. In my custom UIView I called it "echo_to" because the events are then echoed to the UIScrollView to make the map work as it does normally.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Get the UIView (in this case, a UIScrollView) that
// would ordinarily handle the events
echo_to = [super hitTest:point withEvent:event];
// But let it be known we'll handle them instead.
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches Began");
// add your custom annotation behavior here
[echo_to touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touches moved");
// add your custom annotation behavior here
[echo_to touchesMoved:touches withEvent:event];
}
Best of luck.
Depending on the sophistication of your geometryView, you may be able to use an annotation view to draw it.
The downside to this approach is that the view won't scale with the map (although you can use the mapView:regionDidChangeanimated: delegate method to redraw after a zoom), but it will pan with the map.
The hitTest trick works great as long as you don't need users to be able to tap on annotations, or pinch-and-zoom the map. Neither of these are able to be forwarded.