iPhone:绘制存储在数组中的CGLayers时崩溃

时间:2010-02-07 14:34:28

标签: iphone objective-c quartz-2d cgcontext cglayer

我正在尝试使用重做和撤消功能构建绘图应用。 我的想法是在“touchMoved”中的图层中绘制线条,然后将图层保存在“touchEnded”中。

我并不是因为我正在绘制正确的图层,所以一切正常,直到我清除我正在绘制的图像并尝试重绘数组中的图层。

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];   
    CGPoint currentPoint = [touch locationInView:self.view];

    UIGraphicsBeginImageContext(self.imageView.frame.size);
    [self.imageView.image drawInRect:self.imageView.frame];

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextRef myContext;

    layerRef = CGLayerCreateWithContext(context, self.imageView.frame.size, NULL);

    if (self.layer == nil) {
        myContext =CGLayerGetContext(layerRef);

        CGContextSetLineCap(myContext, kCGLineCapRound);
        CGContextSetLineWidth(myContext, 5.0);
        CGContextSetLineJoin(myContext,  kCGLineJoinRound);
        CGContextSetRGBStrokeColor(myContext, 1.0, 0.0, 0.0, 1.0);

        CGContextBeginPath(myContext);
        CGContextMoveToPoint(myContext, lastPoint.x, lastPoint.y);
        CGContextAddLineToPoint(myContext, currentPoint.x, currentPoint.y);
        CGContextStrokePath(myContext);

        CGContextDrawLayerAtPoint(context, CGPointMake(00, 00),layerRef); 
        self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();

                UIGraphicsEndImageContext();
        lastPoint = currentPoint;       
    }   
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.layerArray != nil) {
        NSLog(@"Saving layer");
        [self.layerArray addObject:[[NSValue alloc] initWithBytes:layerRef objCType:@encode(CGLayerRef)]];
        CGLayerRelease(layerRef);
    }
    NSLog(@"%d",[layerArray count]);
}

这是我正在尝试重绘图层的方法。 应用程序在到达CGContextDrawLayerAtPoint()

时崩溃
- (IBAction)redrawViewButton:(id)sender {
    UIGraphicsBeginImageContext(self.imageView.frame.size);
    [self.imageView.image drawInRect:self.imageView.frame];

    NSValue *val = [layerArray objectAtIndex:0];
    CGLayerRef layerToShow;
    [val getValue:&layerToShow];    

    CGContextRef context = CGLayerGetContext(layerToShow);
    CGContextDrawLayerAtPoint(context, CGPointMake(00, 00),layerToShow);

    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

3 个答案:

答案 0 :(得分:2)

我认为layerRef是你映射到self.layer的ivar?您似乎在访问器和直接ivar访问之间移动,这非常令人困惑且容易出错。确保始终通过访问者访问您的ivars。这将大大有助于避免内存管理问题。你可以实现像这样的layerproperty:

@property (nonatomic, readwrite, retain) CGLayerRef layer;

@synthesize layer = _layer;

- (void)setLayer:(CGLayer)aLayer
{
    CGLayerRetain(aLayer);
    CGLayerRelease(_layer);
    _layer = aLayer;
}

...

CGLayerRef layer = CGLayerCreateWithContext(context, self.imageView.frame.size, NULL);
self.layer = layer;
CGLayerRelease(layer);

重点是将{iv}的所有内存管理放在setLayer:内。在ivar访问中崩溃的最常见原因是你对它的内存管理管理不善。访问者可以保护您免受此攻击。

其他值得注意的一点:

  • 如果它保持在上下文中,则不要立即将其设置为nil。在你的情况下,你发布了layerRef,但你没有清除ivar。这意味着如果你获得touchesEnded:再次获得另一个touchesMoved:之前,你将双重释放该层。这可能是你问题的真正原因。访问者可以保护您免受此攻击。

  • 你的touchesMoved:代码似乎非常错误。每次搬家时都要创建一个新图层。对于单个touchesMoved:,您可以获得数十个touchesEnd:。或者你根本得不到touchesMoved:。我想你的意思是把这段代码放在touchesBegan:

答案 1 :(得分:2)

一些随机的事情:

touchedEnded:withEvent:中存在内存泄漏,您正在向self.llayerArray添加一个保留对象,但在数组保留它之后永远不会释放它。试试这个:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.layerArray != nil) {
        NSLog(@"Saving layer");
        [self.layerArray addObject: [NSValue valueWithPointer: layerRef]];
        CGLayerRelease(layerRef);
    }
    NSLog(@"%d",[layerArray count]);
}

CGLayerRef是一个指针。这意味着在redrawViewButton:中你可以简单地做到这一点:

CGLayerRef* layerToShow = (CGLayerRef) [[layerArray objectAtIndex: 0] pointerValue];

答案 2 :(得分:1)

最简单的解释是图层或上下文未正确形成。你在使用前测试两者都是零。 IIRC,如果您使用“打印描述到控制台”上下文菜单,调试器可以显示核心图形结构的值。

可能不相关但我建议改变......

CGPointMake(00, 00)

...为:

CGPointMake(0.0f, 0.0f)

只是为了确保。

无论如何,我认为你需要放弃这种实现撤销的方法。它看起来简单而整洁,但实际上它会变得繁琐,复杂和不可靠。

撤消和重做是数据模型的正确功能,而不是视图或其控制器。您应该保存用户输入,然后从该数据中提取,而不是保存用户输入的结果,即图纸。

在这种情况下,您可以存储触摸点,触摸的时间/顺序以及相关的操作。视图和视图控制器根本没有“内存”。他们只需绘制当前所需绘制的数据模型。您将在数据模型中实现撤消和重做。要撤消,您可以将所有数据绘制到撤消点。要重做,您需要绘制最后的数据。

尽管学习曲线陡峭,但核心数据对此非常有利。它会自动为您实现撤消和重做。如果您的数据模型相对简单,您可以使用一个存储自定义类的数组来实现它,该类用于存储单个绘图事件的数据。

如果您尝试在视图或视图控制器中执行此操作,最终会得到一个脆弱代码的怪物球。