Detect (Using UITapGeusture) and Drag Line (Using UIPanGesture) - ios

I want to drag a drawn line which is done by Me. The below code snippet working fine with help of stack overflow user answer.
//My instances in .h file
CGPoint location1,location2;
LineView* l;
- (void)viewDidLoad
{
[super viewDidLoad];
l = [[LineView alloc]initWithFrame:self.view.frame];
[self.view addSubview:l];
UIPinchGestureRecognizer *linepinch = [[UIPinchGestureRecognizer alloc]
initWithTarget:l action:#selector(handleLinePinch:)];
[l addGestureRecognizer:linepinch];
}
- (void)handleLinePinch:(UIPinchGestureRecognizer *)gesture
{
NSUInteger num_touches = [gesture numberOfTouches];
// save locations to some instance variables, like `CGPoint location1, location2;`
if (num_touches >= 1) {
location1 = [gesture locationOfTouch:0 inView:l];
}
if (num_touches >= 2) {
location2 = [gesture locationOfTouch:1 inView:l];
}
[l drawRect:location1 Loc2:location2];
[l setNeedsDisplay];
}
LineView.m
- (void)drawRect:(CGPoint)location1 Loc2:(CGPoint)location2 {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
CGContextMoveToPoint(context, location1.x, location1.y);
CGContextAddLineToPoint(context, location2.x, location2.y);
CGContextStrokePath(context);
}
The above code snippet working fine but i want to drag this line by using UITapGeustureRecognizer or someother.thanks in advance.

Related

Dashed lines with CGContextStrokePath

I am developing an app where the user should be able to draw lines with the finger. The user can enable dashed lines, but I am having a strange problem when trying to do so. If the user drags the finger slowly a solid line appears, but when doing it fast the dashes appears. I would like the dashes to appear no matter what when enabled. The problem is shown on the gif below:
I am collecting the touch points with a UIPanGestureRecognizer:
-(void)dragGestureCaptured:(UIPanGestureRecognizer *)gesture
{
NSValue* touchPoint = [NSValue valueWithCGPoint:[gesture locationInView:self.drawingView]];
if(gesture.state == UIGestureRecognizerStateBegan)
{
[self initializePreviousPointValues:[touchPoint CGPointValue]];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
EBLog(#"Dragging ends..");
return;
}
previousPoint2 = previousPoint1;
previousPoint1 = currentPoint;
currentPoint = [touchPoint CGPointValue];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
[self.drawingView addDrawColor:self.currentDrawColor];
[self.drawingView.controlPoints addObject:[NSValue valueWithCGPoint:previousPoint1]];
[self.drawingView.points addObject:[NSValue valueWithCGPoint:mid2]];
[self.drawingView.movePoints addObject:[NSValue valueWithCGPoint:mid1]];
[self.drawingView setNeedsDisplay];
}
The drawingView uses the controlsPoints, points and movePoints to draw the lines:
- (void)drawLinesInContext:(CGContextRef)context
{
for (int i = 0; i < [self.points count]; i++)
{
CGPoint movePoint = [[self.movePoints objectAtIndex:i] CGPointValue];
CGPoint controlPoint = [[self.controlPoints objectAtIndex:i] CGPointValue];
CGPoint point = [[self.points objectAtIndex:i] CGPointValue];
CGContextMoveToPoint(context, movePoint.x, movePoint.y);
CGContextAddQuadCurveToPoint(context, controlPoint.x, controlPoint.y, point.x, point.y);
NSNumber* colorHash = [self.colorHashes objectAtIndex:i];
UIColor* col = [self.colorsForHashValue objectForKey:colorHash];
CGFloat red = 0.0f, blue = 0.0f, green = 0.0f, alpha = 1.0f;
[col getRed:&red green:&green blue:&blue alpha:&alpha];
if (alpha == 0.0f)
{
penWidth = 15.0f;
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
}
else
{
penWidth = 5.0f;
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, alpha);
}
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, penWidth);
CGContextSetShouldAntialias(context, false);
CGContextSetAllowsAntialiasing(context, false);
if (self.drawDashedLines)
{
CGFloat dashLengths[] = {10.0f, 10.0f};
CGContextSetLineDash(context, 0.0f, dashLengths, 2);
}
CGContextStrokePath(context);
}
}
drawLinesInContext: is invoked in drawRect:.
What am I doing wrong, since the dashes don't appear when dragging slowly?
I have already seen the Drawing a dashed line with CGContextSetLineDash and tried the CGPathAddLineToPoint to see if it makes a difference, but it didn't. I was experiencing the exact same problem.
The problem is that you draw separate paths between each subsequent points, and
each path starts with a new dash pattern. If the finger is moved slowly, you draw
lots of very short path. It the path is shorter than 10 points (the first
dash length which is drawn) then no dash pattern is seen at all.
The following pseudo-code hopefully shows the idea how to solve this. Instead of
for i = 1 ... N {
move to point[i-1]
curve to point[i]
set line dash
stroke path
}
it should be
move to point[0]
for i = 1 ... N {
curve to point[i]
}
set line dash
stroke path

ios-Trying to draw points on image and then connect them through lines

i can draw points on my image using the code in touchesbegan. and i am storing my coordinates in NSMutable array. i want it to draw lines as i mark points on the screen.. but my drawRect isnt firing i guess .. can u please tell me what to do..
-(void) drawRect:(CGRect)rect
{
int *count = 0;
if([pointarray count]!=0)
{
float firstpointx= [[pointarray objectAtIndex:0]floatValue];
float firstpointy= [[pointarray objectAtIndex:1]floatValue];
float secondpointx= [[pointarray objectAtIndex:2]floatValue];
float secondpointy= [[pointarray objectAtIndex:3]floatValue];
//NSMutableArray *coordinates = [[NSMutableArray alloc] init];
for (int i=0; i<=[pointarray count]; i++) {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 2.0);
CGContextMoveToPoint(ctx, firstpointx, firstpointy);///move to ur first dot
CGContextAddLineToPoint(ctx, secondpointx, secondpointy);//add line from first dot to second dot
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextStrokePath(ctx);
[pointarray removeAllObjects];//remove first two points from ur array so that next line is not drawn in continuous with previous line
}
count++;
}
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
pointarray=[[NSMutableArray alloc]init];
CGPoint curPoint = [[touches anyObject] locationInView:self.map];
[pointarray addObject:[NSNumber numberWithFloat:curPoint.x]];
[pointarray addObject:[NSNumber numberWithFloat:curPoint.y]];
[_map setNeedsDisplay];
[self logMessage:[NSString stringWithFormat:#"Sending : %#", pointarray]];
NSLog(#"the point array is %#",pointarray);
NSArray *coordinates = [[NSArray alloc]initWithArray:pointarray copyItems:YES];
NSLog(#"the coordinate array %#",coordinates);
//[self.map setNeedsDisplay]; // calls drawRectMethod
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(curPoint.x, curPoint.y, 10, 10)];
view.backgroundColor = [UIColor redColor];
[self.map addSubview:view];
}
On touchedBegan:withEvent: you just call setNeedsDisplay on _map, not on self, that's why the view doesn't get redraw.
Also, you just add two points, but in drawRect you code like if it's sure that the array contains four points. Probably what you want to do is to add two points in touchesEnded:withEvent? If so you should call setNeedsDisplay from there.
You should call setNeedsDisplay from this method:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}

Apply Touches to Custom Control

I made a Custom Circle Control, Were i am Sending values by Slider from my RootView Controller. Can You help me out, how to Apply touches to Circle Control were i can change the values, like making custom slider around my Custom Circle.
#import "RKCustomCircle.h"
#implementation RKCustomCircle
#synthesize sliderPerccentageValue;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
xPos = 320/2;
yPos = 250;
radius = 80;
rotationAngle = 0;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawCircleChart: context];
}
- (void) drawCircleChart:(CGContextRef) context
{
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 320, 480));
float a = rotationAngle;
[self drawCirclewithStartingAngle:a withContext:context];
}
- (void) drawCirclewithStartingAngle:(float)startAngle withContext:(CGContextRef) context
{
float endAngle = startAngle + (sliderPerccentageValue/ 100) * (M_PI*2);
float adjY = yPos;
float rad = radius;
CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextBeginPath(context);
CGContextMoveToPoint(context, xPos, adjY);
CGContextAddArc(context, xPos, adjY, rad, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
RootView is,
#implementation RKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
circleView = [[RKCustomCircle alloc] initWithFrame:CGRectMake(0, 100, 320, 360)];
[circleView addTarget:self action:#selector(newValue:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:circleView];
}
- (IBAction)sliderValueChangedInPercentage:(UISlider *)sender {
circleView.sliderPerccentageValue =sender.value;
[circleView setNeedsDisplay];
}
Use UITapGestureRecognizer.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(oneTap:)];
[singleTap setNumberOfTapsRequired:1];
[singleTap setNumberOfTouchesRequired:1];
[circleView addGestureRecognizer:singleTap];
Add this method
- (void)oneTap:(UIGestureRecognizer *)gesture
{
NSLog(#"Touch occur");
}
All your things are correct, but only the following part is incorrect.
- (IBAction)sliderValueChangedInPercentage:(UISlider *)sender {
circleView.sliderPerccentageValue =sender.value;
[circleView setNeedsDisplay];
}
The above code doesn't supply any value because you have attached to your control that implements UIControl, not UISlider. You don't have to either, your UIControl will work..
Instead you can have touch events inside your control itself no need to add any action from outside, and you have to detect circular touch and keep track of values, see this app here it will help you to get values when touched circular.
Hope it will help you. All the best.
You need to override the pointInside:withEvent: method of the circle view. Refer to this for more information.

How to bring UIBezierPath to the back of a MKAnnotation object?

In my app, user draws a shape on map and using UIBeizerPath i am drawing that path. Then based on the coordinates of the path i am displaying the results which are only in that area. Everything works great except that now when Annotations drops on the Map view the pins looks like they are behind the path which means path looks in the front.
I am using this code to display the Annotation and path :
-(void)clearAnnotationAndPath:(id)sender {
[_mapView removeAnnotations:_mapView.annotations];
path = [UIBezierPath bezierPath];
[shapeLayer removeFromSuperlayer];
}
- (void)handleGesture:(UIPanGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:_pathOverlay];
if (gesture.state == UIGestureRecognizerStateBegan)
{
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
shapeLayer.strokeColor = [[UIColor greenColor] CGColor];
shapeLayer.lineWidth = 5.0;
//[_mapView.layer addSublayer:shapeLayer];
[pathOverlay.layer addSublayer:shapeLayer];
path = [UIBezierPath bezierPath];
[path moveToPoint:location];
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
[path addLineToPoint:location];
shapeLayer.path = [path CGPath];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// MKMapView *mapView = (MKMapView *)gesture.view;
[path addLineToPoint:location];
[path closePath];
allStations = [RoadmapData sharedInstance].data;
for (int i=0; i<[allStations count]; i++) {
NSDictionary * itemNo = [allStations objectAtIndex:i];
NSString * fullAddress = [NSString stringWithFormat:#"%#,%#,%#,%#",[itemNo objectForKey:#"address"],[itemNo objectForKey:#"city"],[itemNo objectForKey:#"state"],[itemNo objectForKey:#"zip"]];
CLGeocoder * geoCoder = [[CLGeocoder alloc]init];
[geoCoder geocodeAddressString:fullAddress completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(#"Geocode failed with error: %#", error);
return;
}
if(placemarks && placemarks.count > 0)
{
CLPlacemark *placemark = placemarks[0];
CLLocation *location = placemark.location;
CLLocationCoordinate2D coords = location.coordinate;
CGPoint loc = [_mapView convertCoordinate:coords toPointToView:_pathOverlay];
if ([path containsPoint:loc])
{
NSString * name = [itemNo objectForKey:#"name"];
stationAnn = [[LocationAnnotation alloc]initWithCoordinate:coords Title:name subTitle:#"Wells Fargo Offer" annIndex:i];
stationAnn.tag = i;
[_mapView addAnnotation:stationAnn];
}
else{
NSLog(#"Out of boundary");
}
}
}];
[self turnOffGesture:gesture];
}
}
}
- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views{
if (views.count > 0) {
UIView* firstAnnotation = [views objectAtIndex:0];
UIView* parentView = [firstAnnotation superview];
if (_pathOverlay == nil){
// create a transparent view to add bezier paths to
pathOverlay = [[UIView alloc] initWithFrame: parentView.frame];
pathOverlay.opaque = NO;
pathOverlay.backgroundColor = [UIColor clearColor];
[parentView addSubview:pathOverlay];
}
// make sure annotations stay above pathOverlay
for (UIView* view in views) {
[parentView bringSubviewToFront:view];
}
}
}
Also once i go back from this and view and come again its not even drawing the Path.
Please help.
Thanks,
Apparently, when you add your bezier path to the map via:
[_mapView.layer addSublayer:shapeLayer];
it is getting added above some internal layer that MKMapView uses to draw the annotations. If you take a look at this somewhat related question, you'll see that you can implement the MKMapViewDelegate protocol, and get callbacks when new station annotations are added. When this happens, you basically inspect the view heirarchy of the newly added annotations, and insert a new, transparent UIView layer underneath them. You take care to bring all the annotations in front of this transparent UIView.
// always remember to assign the delegate to get callbacks!
_mapView.delegate = self;
...
#pragma mark - MKMapViewDelegate
- (void)mapView:(MKMapView *)aMapView didAddAnnotationViews:(NSArray *)views{
if (views.count > 0) {
UIView* firstAnnotation = [views objectAtIndex:0];
UIView* parentView = [firstAnnotation superview];
// NOTE: could perform this initialization in viewDidLoad, too
if (self.pathOverlay == nil){
// create a transparent view to add bezier paths to
pathOverlay = [[UIView alloc] initWithFrame: parentView.frame];
pathOverlay.opaque = NO;
pathOverlay.backgroundColor = [UIColor clearColor];
[parentView addSubview:pathOverlay];
}
// make sure annotations stay above pathOverlay
for (UIView* view in views) {
[parentView bringSubviewToFront:view];
}
}
}
Then, instead of adding your shape layer to _mapView.layer, you add it to your transparent view layer, also using this new layer in the coordinate conversion:
- (void)handleGesture:(UIPanGestureRecognizer*)gesture
{
CGPoint location = [gesture locationInView: self.pathOverlay];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if (!shapeLayer)
{
shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
shapeLayer.strokeColor = [[UIColor greenColor] CGColor];
shapeLayer.lineWidth = 5.0;
[pathOverlay.layer addSublayer:shapeLayer]; // <- change here !!!
}
self.path = [[UIBezierPath alloc] init];
[path moveToPoint:location];
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
[path addLineToPoint:location];
shapeLayer.path = [path CGPath];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
/*
* This code is the same as what you already have ...
*/
// But replace this next line with the following line ...
//CGPoint loc = [_mapView convertCoordinate:coords toPointToView:self];
CGPoint loc = [_mapView convertCoordinate:coords toPointToView: self.pathOverlay];
/*
* And again use the rest of your original code
*/
}
}
where I also added an ivar (and property) for the new transparent layer:
UIView* pathOverlay;
I tested this with a bogus grid of stations and got the following results:
P.S. I'd also recommend getting rid of your static variables. Just make them ivars/properties of your class.

Is CGPoint in MKPolygonView?

I'm trying to figure out a way to detect which MKOverlayView (actually MKPolygonView) was tapped and then change its color.
I got it running with this code:
- (void)mapTapped:(UITapGestureRecognizer *)recognizer {
MKMapView *mapView = (MKMapView *)recognizer.view;
MKPolygonView *tappedOverlay = nil;
for (id<MKOverlay> overlay in mapView.overlays)
{
MKPolygonView *view = (MKPolygonView *)[mapView viewForOverlay:overlay];
if (view){
// Get view frame rect in the mapView's coordinate system
CGRect viewFrameInMapView = [view.superview convertRect:view.frame toView:mapView];
// Get touch point in the mapView's coordinate system
CGPoint point = [recognizer locationInView:mapView];
// Check if the touch is within the view bounds
if (CGRectContainsPoint(viewFrameInMapView, point))
{
tappedOverlay = view;
break;
}
}
}
if([[tappedOverlay fillColor] isEqual:[[UIColor cyanColor] colorWithAlphaComponent:0.2]]){
[listOverlays addObject:tappedOverlay];
tappedOverlay.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
}
else{
[listOverlays removeObject:tappedOverlay];
tappedOverlay.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
}
//tappedOverlay.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
}
Which works but sometimes, depending where I tap it gets wrong which MKPolygonView was tapped. I suppose because CGRectContainsPoint doesnt calculate properly the area, since it's not a rectangle it's a Polygon.
What other methods there are to do this? I tried CGPathContainsPoint but I get worse results.
Thanks to #Ana Karenina, that pointed out the right way, this is how you have to convert the gesture so that the method CGPathContainsPoint' works right.
- (void)mapTapped:(UITapGestureRecognizer *)recognizer{
MKMapView *mapView = (MKMapView *)recognizer.view;
MKPolygonView *tappedOverlay = nil;
int i = 0;
for (id<MKOverlay> overlay in mapView.overlays)
{
MKPolygonView *view = (MKPolygonView *)[mapView viewForOverlay:overlay];
if (view){
CGPoint touchPoint = [recognizer locationInView:mapView];
CLLocationCoordinate2D touchMapCoordinate =
[mapView convertPoint:touchPoint toCoordinateFromView:mapView];
MKMapPoint mapPoint = MKMapPointForCoordinate(touchMapCoordinate);
CGPoint polygonViewPoint = [view pointForMapPoint:mapPoint];
if(CGPathContainsPoint(view.path, NULL, polygonViewPoint, NO)){
tappedOverlay = view;
tappedOverlay.tag = i;
break;
}
}
i++;
}
if([[tappedOverlay fillColor] isEqual:[[UIColor cyanColor] colorWithAlphaComponent:0.2]]){
[listOverlays addObject:tappedOverlay];
tappedOverlay.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.2];
}
else{
[listOverlays removeObject:tappedOverlay];
tappedOverlay.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.2];
}
//tappedOverlay.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.7];
}

Resources