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

发布于 2024-08-20 12:44:46 字数 2411 浏览 7 评论 0原文

我正在尝试构建一个具有重做和撤消功能的绘图应用程序。 我的想法是在“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();
}

I'm trying to build a drawing app with redo and undo functionality.
My idea is to draw lines in a layer in "touchMoved", then saving the layer in "touchEnded".

I'm not shure that I am drawing to the layer correct, everything works fine though, until I clear the image I'm drawing in on and try to redraw the layers in the array.

- (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]);
}

Here is the method I'm trying to redraw the layer in.
The app crashes when it reaches the the 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();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

祁梦 2024-08-27 12:44:46

我认为 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);

这样做的要点是将 ivar 的所有内存管理放在 setLayer: 中。 ivar 访问崩溃的最常见原因是您对其内存管理管理不当。访问器可以保护您免受这种情况的影响。

其他一些值得注意的点:

  • 如果某些内容处于上下文中,请不要在没有立即将其设置为 nil 的情况下发布它。在你的情况下,你正在释放layerRef,但你没有清除ivar。这意味着如果您在获得另一个 touchesMoved: 之前再次获得 TouchesEnded:,您将双重释放该层。这可能是您问题的真正原因。访问器可以保护您免受这种情况的影响。

  • 你的touchesMoved:代码似乎非常错误。每次移动时都会创建一个新图层。对于单个 touchesEnd:,您可以获得数十个 touchesMoved:。或者你可能根本得不到 touchesMoved: 。我认为您打算将此代码放入 touchesBegan: 中?

I take it that layerRef is an ivar that you've mapped to self.layer? You seem to be moving between accessors and direct ivar access, which is very confusing and error-prone. Make sure to always access your ivars through accessors. This will go a long way towards saving you memory management troubles. You'd implement the layerproperty something like this:

@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);

The point of this is to put all of your memory management of the ivar inside of setLayer:. The most common cause of crashes on ivar access is that you have mismanaged the memory management on it. Accessors protect you from that.

A copule of other noteworthy points:

  • Never release something without immediately setting it to nil if it's staying in context. In your case you're releasing layerRef, but you don't clear the ivar. That means if you get touchesEnded: again before you get another touchesMoved:, you'll double-release the layer. That's probably the actual cause of your problem. Accessors protect you from this.

  • Your touchesMoved: code seems very wrong. You're creating a new layer every time you get a move. You can get dozens of touchesMoved: for a single touchesEnd:. Or you could get no touchesMoved: at all. I think you meant to put this code in touchesBegan:?

长途伴 2024-08-27 12:44:46

一些随机的事情:

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];

Some random things:

There is a memory leak in touchedEnded:withEvent:, you are adding a retained object to self.llayerArray but never release it after the array has also retained it. Try this instead:

- (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]);
}

A CGLayerRef is a pointer. This means that in redrawViewButton: you can simpy do this:

CGLayerRef* layerToShow = (CGLayerRef) [[layerArray objectAtIndex: 0] pointerValue];
堇色安年 2024-08-27 12:44:46

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

可能无关,但我建议将...更改

CGPointMake(00, 00)

为:

CGPointMake(0.0f, 0.0f)

只是为了确定。

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

撤消和重做是数据模型的正确功能,而不是视图或其控制器。您不应该保存用户输入的结果(即绘图),而应该保存用户输入,然后根据该数据进行绘图。

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

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

如果您尝试在视图或视图控制器中完成这一切,您最终会得到一个由脆弱代码组成的怪物球。

The simplest explanation is that either the layer or the context is not formed properly. You test both for nil before using. IIRC, the debugger can display values for Core Graphic structures if you use the "Print Description to Console" contextual menu.

Probably unrelated but I would recommend changing...

CGPointMake(00, 00)

...to:

CGPointMake(0.0f, 0.0f)

Just to make sure.

In any case, I think you need to abandon this method of implementing undo. It looks simple and neat but in reality it will grow cumbersome, complex and unreliable.

Undo and redo are properly functions of the data model and not a view or it's controller. Instead of saving the results of the user's inputs i.e. the drawings, you should be saving the users inputs and then drawing from that data.

In this case you store the points of the touches, the time/sequences of touches and whatever operations are relevant. The view and the view controller would have no "memory" at all. They would simply draw whatever the data model indicated needed to drawn at the moment. You would implement undo and redo in the data model. To undo you would draw all the data up to the undo point. To redo you draw up to the last data.

Core Data is very good for this although the learning curve is steep. It will implement undo and redo for you automatically. If your data model is relatively simple you could implement it with just an array that stores a custom class designed to store the data for a single drawing event.

If you try to do this all in the view or the view controller, you end up with a monster ball of fragile code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文