【发布时间】:2014-11-19 22:00:38
【问题描述】:
我有一个带有 UIPanGestureRecogniser 的自定义 UIView,用于捕获点和速度。我将这些点和速度存储在 NSMutableArrays 中,然后使用数据创建 UIBezierPaths,然后将其添加到撤消/重做历史记录中。
当用户在屏幕上移动手指时,撤消历史会不断添加新路径,并且每条新路径都被绘制到屏幕外的图形上下文中,裁剪(到路径的大小)然后在 UIView 上绘制。
我的问题是:现在我正在创建一个捏到缩放功能并将缩放和平移变换应用于绘图视图,任何在放大时完成的绘图最终都会出现在错误的位置(它向上和手指在屏幕上的左侧)和错误的尺寸(更小)。在撤消和重做之前,您实际上无法看到您正在绘制的内容。我在想屏幕外上下文或屏幕的错误矩形正在更新,或者由于变换而存储在撤消历史记录中的路径点具有不同的原点/参考点(不知道该怎么称呼它!)。
这是我的第一个 iOS 应用程序(可能存在一些愚蠢的错误),我已经使用了许多不同的教程来做到这一点,但我对变换如何影响路径感到困惑。以及如何确保路径以正确的比例绘制在屏幕外上下文的正确位置。我尝试过变换路径、变换点并尝试反转变换,但我就是不明白。
所以这里是代码(为数量道歉)。我包括处理捕捉点、制作路径和在屏幕上绘图的内容……捏识别器将放大绘图视图,并将其转换为将其放大到捏的中心。
在 ViewController 中,我以整个屏幕的大小创建绘图视图 (VelocityDrawer) 并添加手势识别器:
VelocityDrawer *slv = [[VelocityDrawer alloc] initWithFrame:CGRectMake(0,0,768,1024)];
slv.tag = 100;
drawingView = slv;
drawingView.delegate = self;
drawingView.currentPen = finePen;
然后initWithFrame:(CGRect)frame 在 VelocityDrawer 中:
self.undoHistory = [[NSMutableArray alloc] init];
self.redoHistory = [[NSMutableArray alloc] init];
// create offscreen context
drawingContext = [self createOffscreenContext:frame.size];
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
panGestureRecognizer.maximumNumberOfTouches = 1;
[self addGestureRecognizer:panGestureRecognizer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
tapGestureRecognizer.numberOfTouchesRequired = 1;
[self addGestureRecognizer:tapGestureRecognizer];
[self clearHistoryBitmaps];
屏幕外上下文是这样创建的:
- (CGContextRef) createOffscreenContext: (CGSize) size {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
float scaleFactor = [[UIScreen mainScreen] scale];
// must use whole numbers so invalid context does not happen
NSInteger sw = (NSInteger)(size.width * scaleFactor);
NSInteger sh = (NSInteger)(size.height * scaleFactor);
CGContextRef context = CGBitmapContextCreate(NULL,
sw,
sh,
8,
sw * 4,
colorSpace,
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextScaleCTM(context, scaleFactor, scaleFactor);
return context;
}
在handlePanGesture。我捕获点,计算extractSize 中线的大小或宽度(基于手指速度),然后将信息添加到数组中:
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan)
{
CGPoint point = [panGestureRecognizer locationInView:panGestureRecognizer.view];
CGPoint prev = [[points firstObject] pos];
float size;
size = currentPen.minWidth/2;
// Add point to array
[self addPoint:point withSize:size];
// Add empty group to history
[undoHistory addObject:[[NSMutableArray alloc] init]];
}
if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
CGPoint point = [panGestureRecognizer locationInView:panGestureRecognizer.view];
currentPoint = [(LinePoint *)[points lastObject] pos];
float size = clampf([self extractSize:panGestureRecognizer], currentPen.minWidth, currentPen.maxWidth);
[self addPoint:point withSize:size];
NSMutableArray *pArr = [[NSMutableArray alloc] init];
UIBezierPath *sizer = [[UIBezierPath alloc] init];
// interpolate points to make smooth variable width line
NSMutableArray *interPoints = [self calculateSmoothLinePoints];
// code continues
我循环遍历interPoints(识别器中最新点和先前点之间的插值点数组)创建将在屏幕上绘制的路径:
// other code here
// loop starts
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(path, NULL, prevT1.x, prevT1.y, mid2.x, mid2.y);
CGPathAddLineToPoint(path, NULL, mid2b.x, mid2b.y);
CGPathAddQuadCurveToPoint(path, NULL, prevB1.x, prevB1.y, mid1b.x, mid1b.y);
CGPathAddLineToPoint(path, NULL, mid1.x, mid1.y);
UIBezierPath *aPath = [UIBezierPath bezierPath];
aPath.CGPath = path;
[sizer appendPath:aPath];
[pArr addObject:aPath];
// more code here
// loop ends
CGPathRelease(path);
将所有路径添加到pArr 后,我创建一个HistoryItem 并用路径、线条颜色、线条宽度等填充它。
HistoryItem *action = [[HistoryItem alloc] initWithPaths:pArr
andLineColour:self.lineColor
andLineWidth:self.lineWidth
andDrawMode:self.currentDrawMode
andScale:self.scale];
[self addAction:action];
addAction 将HistoryItem 添加到撤消堆栈。请注意,我记录了self.scale,但不做任何事情。然后我得到边界矩形(drawBox)并调用setNeedsDisplayInRect
CGRect drawBox = CGPathGetBoundingBox(sizer.CGPath);
//Pad bounding box to respect line width
drawBox.origin.x -= self.lineWidth * 1;
drawBox.origin.y -= self.lineWidth * 1;
drawBox.size.width += self.lineWidth * 2;
drawBox.size.height += self.lineWidth * 2;
[self setNeedsDisplayInRect:drawBox];
当手势完成时,我在线条上添加一个圆形末端。代码省略。
最后绘制矩形:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// offScreen context
UIGraphicsPushContext(drawingContext);
HistoryItem *action = [[undoHistory lastObject] lastObject];
if(currentDrawMode == DRAW || currentDrawMode == ERASE) {
for (UIBezierPath *p in action.pathsToDraw) {
p.lineWidth = 1;
p.lineCapStyle = kCGLineCapRound;
[action.lineColor setFill];
[action.lineColor setStroke];
[p fill];
[p stroke];
}
}
if(currentDrawMode == UNDO) {
CGContextClearRect(drawingContext, self.bounds);
for (NSArray *actionGroup in undoHistory) {
for (HistoryItem *undoAction in actionGroup) {
for (UIBezierPath *p in undoAction.pathsToDraw) {
p.lineWidth = 1;
p.lineCapStyle = kCGLineCapRound;
[undoAction.lineColor setFill];
[undoAction.lineColor setStroke];
[p fill];
[p stroke];
}
}
}
}
// similar code for redo omitted
也许这里的框架/尺寸有问题?
// Continuation of drawRect:
CGImageRef cgImage = CGBitmapContextCreateImage(drawingContext);
CGContextClipToRect(context, rect);
CGContextDrawImage(context, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), cgImage);
CGImageRelease(cgImage);
[super drawRect:rect];
}
【问题讨论】:
-
你找到答案了吗?我有同样的问题,在任何地方都找不到合适的答案......