我有一个UIPanGestureRecogniser的自定义UIView用于捕获点和速度 . 我将这些点和速度存储在NSMutableArrays中,然后使用这些数据创建UIBezierPaths,然后将其添加到撤消/重做历史记录中 .
当用户在屏幕上移动他们的手指时,撤消历史不断地获得添加到其中的新路径,并且每个新路径被绘制到屏幕外图形上下文,剪切(到路径的大小)然后在UIView上绘制 .
My problem is: 现在我正在创建一个缩放到缩放功能并将缩放和平移变换应用到绘图视图,放大时完成的任何绘图都会在错误的位置(手指在屏幕的上方和左侧)和错误的尺寸(较小) . 你可以't actually see what you are drawing until you undo and redo it. I was thinking the wrong rectangle of the offscreen context or screen is getting updated or the points of the paths stored in the undo history have different origin/reference point due to the transforms (don'知道怎么称呼它!) .
这是我的第一个iOS应用程序(可能有一些愚蠢的错误),我已经使用了许多不同的教程来实现这一点,但我仍然坚持转换如何影响路径 . 以及如何确保路径以正确的比例在屏幕外的正确位置绘制 . 我试过改变路径,转换点并尝试反转变换,但我只是没有得到它 .
所以这是代码(数量道歉) . 我包括了捕捉点数,制作路径和绘图到屏幕的内容...一个捏合识别器将放大绘图视图,它将被翻译为放大到捏的中心 .
在ViewController中,我以整个屏幕的大小创建绘图视图( VelocityDrawer
)并添加手势识别器:
VelocityDrawer *slv = [[VelocityDrawer alloc] initWithFrame:CGRectMake(0,0,768,1024)];
slv.tag = 100;
drawingView = slv;
drawingView.delegate = self;
drawingView.currentPen = finePen;
然后在VelocityDrawer中 initWithFrame:(CGRect)frame
:
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];
当手势结束时,我在线上添加一个圆形末端 . 代码省略 .
最后drawRect:
- (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];
}
1 回答
重绘线条时,您需要对点进行相同的变换 . 存储在数组中的点相对于渲染的画布 . 我建议你存储原始帧,然后在下一行渲染中缩放原始帧和新大小之间的坐标 .