使用drawRect绘制可编辑的角度?
Posted
技术标签:
【中文标题】使用drawRect绘制可编辑的角度?【英文标题】:Draw editable angles using drawRect? 【发布时间】:2013-12-25 11:02:55 【问题描述】:我正在尝试创建一个用户每 3 次点击绘制角度的应用程序。每次点击都会创建角度点并将它们连接起来。
这很简单。但是,现在我正在尝试使这些角度可编辑,并且变得更加困难。当我完成 3 次点击并调用 touchesEnded 时,我将这三个点添加到一个名为 segment 的 NSObject 子类中,该子类具有三个属性:firstPoint、secondPoint 和 thirdPoint,并且它精确地存储了它。
然后我将“段”对象添加到数组中。每次调用 touchesBegan 时,我都会进入数组的 for 循环。如果点击在“段”对象的 3 个 CGPoints 中的任何一个的 25px 内,那么我不会绘制新角度,而是编辑该角度。
据我所知,在这一点上,感谢你们,没有任何问题或错误!我只是在检查保持我正在使用的当前方法的一般改进/简化。我希望我能把赏金给每个人,因为你们都非常乐于助人!
这是我迄今为止最大的项目,如果能提供一些见解,我将不胜感激。如果你准备好并且能够处理它,这是一个很好的挑战。 非常非常感谢任何帮助。如果你能想出更好的方法来处理我想要完成的事情,请务必告诉我!
注意:要绘制,只需点击/单击屏幕。
这是最相关的代码:
drawingsubclass.m
#import "SegmentDraw.h"
#import <QuartzCore/QuartzCore.h>
BOOL editing1 = FALSE;
BOOL editing2 = FALSE;
BOOL editing3 = FALSE;
BOOL erased = FALSE;
int radius = 35;
int handleSize = 20;
@implementation SegmentDraw
UIImage *incrementalImage;
@synthesize first;
@synthesize second;
@synthesize third;
- (id)initWithCoder:(NSCoder *)aDecoder
if (self = [super initWithCoder:aDecoder])
[self setMultipleTouchEnabled:NO];
[self setBackgroundColor:[UIColor clearColor]];
first = CGPointZero;
second = CGPointZero;
third = CGPointZero;
return self;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (Segment *currentSegment in self.tapArray)
int xDistance = abs(point.x - currentSegment.firstPoint.x);
int yDistance = abs(point.y - currentSegment.firstPoint.y);
int firstDistance = sqrtf(xDistance * xDistance + yDistance * yDistance);
int xDistance2 = abs(point.x - currentSegment.secondPoint.x);
int yDistance2 = abs(point.y - currentSegment.secondPoint.y);
int secondDistance = sqrtf(xDistance2 * xDistance2 + yDistance2 * yDistance2);
int xDistance3 = abs(point.x - currentSegment.thirdPoint.x);
int yDistance3 = abs(point.y - currentSegment.thirdPoint.y);
int thirdDistance = sqrtf(xDistance3 * xDistance3 + yDistance3 * yDistance3);
if (firstDistance <= radius)
NSLog(@"First point matches");
editing1 = TRUE;
editing2 = FALSE;
editing3 = FALSE;
first = point;
second = currentSegment.secondPoint;
third = currentSegment.thirdPoint;
self.segmentBeingEdited = currentSegment;
[self setNeedsDisplay];
[self drawBitmap];
erased = FALSE;
return;
else if (secondDistance <= radius)
NSLog(@"Second point matches");
editing2 = TRUE;
editing1 = FALSE;
editing3 = FALSE;
first = currentSegment.firstPoint;
second = point;
third = currentSegment.thirdPoint;
self.segmentBeingEdited = currentSegment;
[self setNeedsDisplay];
[self drawBitmap];
erased = FALSE;
return;
else if (thirdDistance <= radius)
NSLog(@"Third point matches");
editing3 = TRUE;
editing1 = FALSE;
editing2 = FALSE;
first = currentSegment.firstPoint;
second = currentSegment.secondPoint;
third = point;
self.segmentBeingEdited = currentSegment;
[self setNeedsDisplay];
[self drawBitmap];
erased = FALSE;
return;
else
editing1 = FALSE;
editing2 = FALSE;
editing3 = FALSE;
if (CGPointEqualToPoint(first, CGPointZero))
first = [touch locationInView:self];
else if (!CGPointEqualToPoint(first, CGPointZero) && CGPointEqualToPoint(second, CGPointZero))
second = [touch locationInView:self];
else if (!CGPointEqualToPoint(first, CGPointZero) && !(CGPointEqualToPoint(second, CGPointZero)) && CGPointEqualToPoint(third, CGPointZero))
third = [touch locationInView:self];
else
//[self drawBitmap];
first = [touch locationInView:self];
second = CGPointZero;
third = CGPointZero;
[self setNeedsDisplay];
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
for (Segment *currentSegment in self.tapArray)
if (editing1)
editing2 = FALSE;
editing3 = FALSE;
first = point;
second = self.segmentBeingEdited.secondPoint;
third = self.segmentBeingEdited.thirdPoint;
//self.segmentBeingEdited = currentSegment;
if (erased == FALSE)
[self drawBitmap];
// NSLog(@"YOU NEED TO ERASE.");
[self setNeedsDisplay];
return;
else if (editing2)
editing1 = FALSE;
editing3 = FALSE;
first = self.segmentBeingEdited.firstPoint;
second = point;
third = self.segmentBeingEdited.thirdPoint;
//self.segmentBeingEdited = currentSegment;
if (erased == FALSE)
[self drawBitmap];
// NSLog(@"YOU NEED TO ERASE.");
[self setNeedsDisplay];
return;
else if (editing3)
// NSLog(@"It's editing time, yo");
editing1 = FALSE;
editing2 = FALSE;
first = self.segmentBeingEdited.firstPoint;
second = self.segmentBeingEdited.secondPoint;
third = point;
//self.segmentBeingEdited = currentSegment;
if (erased == FALSE)
[self drawBitmap];
// NSLog(@"YOU NEED TO ERASE.");
[self setNeedsDisplay];
return;
else
editing1 = FALSE;
editing2 = FALSE;
editing3 = FALSE;
if (!CGPointEqualToPoint(first, CGPointZero) && CGPointEqualToPoint(second, CGPointZero))
first = [touch locationInView:self];
else if (!CGPointEqualToPoint(first, CGPointZero) && !(CGPointEqualToPoint(second, CGPointZero)) && CGPointEqualToPoint(third, CGPointZero))
second = [touch locationInView:self];
else if (!CGPointEqualToPoint(first, CGPointZero) && !(CGPointEqualToPoint(second, CGPointZero)) && !(CGPointEqualToPoint(third, CGPointZero)))
third = [touch locationInView:self];
else
first = [touch locationInView:self];
second = CGPointZero;
third = CGPointZero;
[self setNeedsDisplay];
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
NSUInteger index;
if (editing1 || editing2 || editing3)
index = [self.tapArray indexOfObject:self.segmentBeingEdited];
Segment *datSegment = [self.tapArray objectAtIndex:index];
datSegment.firstPoint = first;
datSegment.secondPoint = second;
datSegment.thirdPoint = third;
if (!CGPointEqualToPoint(third, CGPointZero) && editing1 == FALSE && editing2 == FALSE && editing3 == FALSE)
Segment *segment = [[Segment alloc] init];
segment.firstPoint = first;
segment.secondPoint = second;
segment.thirdPoint = third;
if (self.tapArray == nil)
self.tapArray = [[NSMutableArray alloc] init];
[self.tapArray addObject:segment];
editing1 = FALSE;
editing2 = FALSE;
editing3 = FALSE;
erased = FALSE;
[self drawBitmap];
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
- (void)drawRect:(CGRect)rect
[incrementalImage drawInRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 5.0);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
if (!CGPointEqualToPoint(first, CGPointZero))
CGRect rectangle = CGRectMake(first.x - 10, first.y - 10, handleSize, handleSize);
CGContextAddEllipseInRect(context, rectangle);
CGContextMoveToPoint(context, first.x, first.y);
if (!CGPointEqualToPoint(second, CGPointZero))
CGContextAddLineToPoint(context, second.x, second.y);
CGRect rectangle2 = CGRectMake(second.x - 10, second.y - 10, handleSize, handleSize);
CGContextAddEllipseInRect(context, rectangle2);
CGContextMoveToPoint(context, second.x, second.y);
if (!CGPointEqualToPoint(third, CGPointZero))
CGContextAddLineToPoint(context, third.x, third.y);
CGRect rectangle3 = CGRectMake(third.x - 10, third.y - 10, handleSize, handleSize);
CGContextAddEllipseInRect(context, rectangle3);
CGContextStrokePath(context);
- (void)drawBitmap
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
if (!incrementalImage) // first draw;
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object
[[UIColor clearColor] setFill];
[rectpath fill]; // fill it
[incrementalImage drawAtPoint:CGPointZero];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 5.0);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
if (editing1 || editing2 || editing3)
// CGContextBeginTransparencyLayer(context, NULL);
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextSetLineWidth(context, 6.0);
//CGContextSetBlendMode(context, kCGBlendModeColor);
if (!CGPointEqualToPoint(self.segmentBeingEdited.firstPoint, CGPointZero))
CGContextMoveToPoint(context, self.segmentBeingEdited.firstPoint.x, self.segmentBeingEdited.firstPoint.y);
if (!CGPointEqualToPoint(self.segmentBeingEdited.secondPoint, CGPointZero))
CGContextAddLineToPoint(context, self.segmentBeingEdited.secondPoint.x, self.segmentBeingEdited.secondPoint.y);
if (!CGPointEqualToPoint(self.segmentBeingEdited.thirdPoint, CGPointZero))
CGContextAddLineToPoint(context, self.segmentBeingEdited.thirdPoint.x, self.segmentBeingEdited.thirdPoint.y);
CGContextStrokePath(context);
// CGContextEndTransparencyLayer(context);
incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
erased = TRUE;
return;
CGContextSetBlendMode(context, kCGBlendModeNormal);
if (!CGPointEqualToPoint(first, CGPointZero))
CGContextMoveToPoint(context, first.x, first.y);
if (!CGPointEqualToPoint(second, CGPointZero))
CGContextAddLineToPoint(context, second.x, second.y);
if (!CGPointEqualToPoint(third, CGPointZero))
CGContextAddLineToPoint(context, third.x, third.y);
CGContextStrokePath(context);
incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
@end
回顾一下,我想画一堆角度,然后可以通过点击我要编辑的角度的 3 个点中的任何一个来编辑它们中的任何一个。然后通过拖动,编辑角度以扩展到水龙头当前所在的位置。只是想让它变得更好/更简单。欢迎任何 cmets/见解!
【问题讨论】:
这不是一个难题:您只需保留一个点和线段列表,并使用命中测试来决定用户拖动时要做什么。但是你的问题是完全开放式的。您不能要求别人免费为您编写所有代码,那么实际的问题是什么? @matt:我就是这么做的。保留点列表,并点击测试。它确实有效。我很好奇为什么只有最后创建的角度可以编辑,为什么只有第一个点是可编辑的。所有这些都应该是可编辑的,我不明白我的代码有什么问题。 求距离的算法不对,如果xDistance和yDistance都是25则距离大于35,最好用距离方程 @JosueEspinosa 感谢您澄清问题!很好的重写。 顺便说一句——检查一个点是否位于路径的 x 个像素内的一种方法是使用CGPathCreateCopyByStrokingPath()
,然后在结果上使用CGPathContainsPoint()
。跨度>
【参考方案1】:
好的——这可能不是你想要的……我创建了一个可编辑的角度 UI,我认为这是一个很好的方式来组合你想要做的事情。 (我喜欢这样的项目)
有一个canvas、view controller、angle view和angle handle view。画布上的点击会被记录下来。当点击 3 次时,将创建一个角度视图(其中包含手柄)。点击现有角度将其选中(显示手柄和选择 UI)
项目在github。
编辑
我没有使用手势识别器,但这可能是一个不错的选择。
【讨论】:
其实真的很美!!但是,我今年 16 岁,对 ios 应用程序的经验并不丰富,这个项目的全部目的是 a) 将它包含在我将很快发布的应用程序中,而且 b) 学习并成为一个更好的程序员.因此,我不想接受你的工作,而是想让我的方法奏效。但是,这太棒了,我感谢您的帮助。我将使用您的项目来尝试简化我的项目! 哦,只是一个旁注,这几乎正是我想要的,除了一个手柄应该出现在水龙头 1、2 和 3 上,然后在水龙头 3 上它们是可编辑的。再次感谢,我会研究代码。 谢谢...我只是想分享一些想法/我如何处理这样一个项目的组织。您当然可以使用任何代码,但我知道您已经开始使用自己的版本。祝你好运,玩得开心...... 是的 - 其余的功能,例如显示轻拍发生时以及可能通过边缘拖动整个角度是读者的练习:)【参考方案2】:您没有正确处理编辑标志。你打电话给touchesBegan:
if(yDistance2 <= 25 || editing)
NSLog(@"It's editing time, yo");
editing = TRUE; // Here's your problem
这意味着在touchesMoved
,当你点击时:
if(xDistance <= 25 || editing) // Note the "Or editing"
editing
等于 true,这意味着它将改为移动点 1,因为它首先命中 if 语句,无论哪个点设置它。
结论
您需要为每个点使用特定的编辑标志。不是共享的。至少没有or
检查。或者更好的是,处理touchesMoved
更好一点。您几乎不需要完全复制来自touchesBegan
的代码
旁注
虽然与您的问题不完全相关,但我相信您也在 if 语句中交换了 point2 和 point3。在检查 xDistance2 时:
else if(xDistance2 <= 25 || editing)
NSLog(@"X is pretty close");
if(yDistance2 <= 25 || editing)
NSLog(@"It's editing time, yo");
editing = TRUE;
first = currentSegment.firstPoint;
second = currentSegment.secondPoint;
third = point; // <- Why are you setting third to point and not second to point.
self.segmentBeingEdited = currentSegment;
[self setNeedsDisplay];
return;
检查 XDistance3 时同上。您将第二个指向点,而不是第三个。
【讨论】:
【参考方案3】:您的代码中有一个明显的错误和一个不明显的错误。
显而易见的是,在您的touchesBegan
方法中,您错误地设置了second
和third
的值 - 当您接近第二个点时,您将触摸点值分配给@987654324 @变量,反之亦然。
不明显的是,您有一个全局变量editing
,它在touchesBegan
中针对您正在编辑的任何点进行设置,然后在touchesMoved
中的or
条件中使用,这意味着只要你拖动任何东西,它就会立即落到数组中第一项的第一个点。
您的touchesMoved
方法中发生了太多事情。您应该确定在touchesBegan
中正在编辑哪个段的哪个点,然后在touchesMoved
中仅在您正在编辑特定点时才做任何事情 - 不要尝试再次确定您正在编辑的内容。您也不需要维护所有单独的状态 - 您有一个 segmentBeingEdited
属性,只需使用它和一个标志来说明您正在编辑哪个点,并在您拖动时直接更新值。
通过在您的日志消息中包含一些上下文,这几乎是显而易见的 - 到处复制粘贴相同的日志消息最终总会让您感到困惑。
这是您的 touchesXX
方法的改编版本。我已经为你删除了很多不必要的代码和状态。它仍然不能正常工作 - 绘制已编辑的段时存在问题。在某些时候,您将需要添加代码以进行完全重绘,或者将每个图像分开并删除不需要的图像,但希望我所解释的原则将是直截了当的。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
self.segmentBeingEdited = nil;
pointBeingEdited = 0;
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
// Are we editing an existing segment?
for(int i = 0;i<self.tapArray.count;i++)
Segment *currentSegment = [self.tapArray objectAtIndex:i];
int xDistance = abs(point.x - currentSegment.firstPoint.x);
int yDistance = abs(point.y - currentSegment.firstPoint.y);
int xDistance2 = abs(point.x - currentSegment.secondPoint.x);
int yDistance2 = abs(point.y - currentSegment.secondPoint.y);
int xDistance3 = abs(point.x - currentSegment.thirdPoint.x);
int yDistance3 = abs(point.y - currentSegment.thirdPoint.y);
if(xDistance <= 25)
if(yDistance <= 25)
pointBeingEdited = 1;
else if(xDistance2 <= 25 )
if(yDistance2 <= 25)
pointBeingEdited = 2;
else if(xDistance3 <= 25)
if(yDistance3 <= 25)
pointBeingEdited = 3;
if (pointBeingEdited != 0)
NSLog(@"editing point %d",pointBeingEdited);
self.segmentBeingEdited = currentSegment;
return;
// Creating a new segment. Update data.
if (pointBeingAdded == 0)
first = [touch locationInView:self];
second = CGPointZero;
third = CGPointZero;
else if (pointBeingAdded == 1)
second = [touch locationInView:self];
else if (pointBeingAdded == 2)
third = [touch locationInView:self];
pointBeingAdded++;
[self setNeedsDisplay];
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
if (!self.segmentBeingEdited)
return;
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
if (pointBeingEdited == 1)
self.segmentBeingEdited.firstPoint = point;
else if (pointBeingEdited == 2)
self.segmentBeingEdited.secondPoint = point;
else if (pointBeingEdited == 3)
self.segmentBeingEdited.thirdPoint = point;
[self drawBitmap];
[self setNeedsDisplay];
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
if(self.segmentBeingEdited)
first = CGPointZero;
second = CGPointZero;
third = CGPointZero;
if(pointBeingAdded == 3)
// Add new segment
Segment *segment = [[Segment alloc] init];
segment.firstPoint = first;
segment.secondPoint = second;
segment.thirdPoint = third;
if(self.tapArray == nil)
self.tapArray = [[NSMutableArray alloc] init];
[self.tapArray addObject:segment];
pointBeingAdded = 0;
[self drawBitmap];
self.segmentBeingEdited = nil;
【讨论】:
"到处复制粘贴相同的日志消息最终总是会让你感到困惑。" - 当然。很好的建议。【参考方案4】:我想对你的代码说几句:
1- 这不是必需的,但是您可以使用点击和平移手势识别器,我认为这会简化您的代码。
2- 当您尝试找到正在编辑的点时,您会重复很多代码,您可以像这样迭代每个段的点:
- (void)tapAction:(UITapGestureRecognizer *)recognizer
CGPoint point = [recognizer locationInView:self];
self.segmentBeingEdited = nil;
CGPoint currentPoint;
for (Segment *segment in self.tapArray)
for (NSInteger p = 0; p < 3; p++)
currentPoint = [segment pointAtIndex:p];
CGFloat distance = fDistance(point, currentPoint);
if (distance <= 25.0)
self.segmentBeingEdited = segment;
self.pointBeingEdited = p;
[self setNeedsDisplay];
return;
Segment *segment = [self.tapArray lastObject];
if (!segment || segment.points == 3)
segment = [[Segment alloc] init];
[self.tapArray addObject:segment];
[segment setPoint:point atIndex:segment.points];
segment.points = segment.points + 1;
[self setNeedsDisplay];
3- Segment
类可以具有根据索引 0、1、2 和已添加的点数获取和设置点的方法,我认为这会增加凝聚力并简化您的代码。
4- 用于确定从水龙头位置到某个点的距离是否小于 25 的算法...嗯,它有效,但在技术上不正确,您应该使用这样的算法:
static inline CGFloat fDistance(CGPoint point1, CGPoint point2)
CGFloat dx = point1.x - point2.x;
CGFloat dy = point1.y - point2.y;
return sqrtf(dx * dx + dy * dy);
5- 我注意到您在某些 if 中使用了 CGPointEqualToPoint(point, CGPointZero)
,这同样有效,但有更好的方法可以做到这一点,您可以使用标志来查找要添加的下一个点。
最后你可以使用平移手势识别器来编辑选中的点:
- (void)panAction:(UIPanGestureRecognizer *)recognizer
CGPoint location = [recognizer locationInView:self];
switch (recognizer.state)
case UIGestureRecognizerStateBegan:
CGPoint point = [self.segmentBeingEdited pointAtIndex:self.pointBeingEdited];
CGFloat distance = fDistance(location, point);
if (distance > 25.0)
self.segmentBeingEdited = nil;
break;
case UIGestureRecognizerStateChanged:
if (self.segmentBeingEdited)
[self.segmentBeingEdited setPoint:location atIndex:self.pointBeingEdited];
[self setNeedsDisplay];
default:
break;
我希望这会很有用。
【讨论】:
以上是关于使用drawRect绘制可编辑的角度?的主要内容,如果未能解决你的问题,请参考以下文章
用于绘制可编辑流程图的 Android(或 Android 兼容 Java)库