在视图之间绘制线条的最佳方法是什么?

sre*_*mod 17 iphone cocoa-touch core-animation core-graphics calayer

背景:我有一个自定义的scrollview(子类),其上有uiimageviews可拖动,基于我需要在uiscrollview的子视图中动态绘制一些行的拖动.(注意我需要在子视图中使用它们,因为稍后我需要更改视图的不透明度.)

所以在我花了很长时间开发代码之前(我是一个新手所以它需要我一段时间)我研究了我需要做什么并找到了一些可能的方法.只是想知道正确的方法是什么.

  1. 创建UIView的子类并使用drawRect方法绘制我需要的行(但不确定如何使其动态读取值)
  2. 在子视图上使用CALayers并在那里画画
  3. 使用CGContext函数创建绘制线方法
  4. 别的什么?

干杯求救

Nik*_*uhe 44

从概念上讲,你所有的命题都是相似的.所有这些都将导致以下步骤(其中一些步骤由UIKit无形地完成):

  1. 在内存中设置位图上下文.
  2. 使用Core Graphics将线条绘制到位图中.
  3. 将此位图复制到GPU缓冲区(纹理).
  4. 使用GPU构建图层(视图)层次结构.

上述步骤的昂贵部分是前三点.它们导致重复的内存分配,内存复制和CPU/GPU通信.另一方面,你真正想做的是轻量级:绘制一条线,可能是动画起点/终点,宽度,颜色,alpha,......

有一种简单的方法可以做到这一点,完全避免所描述的开销:对你的线使用CALayer,但不是重新绘制CPU上的内容,而是用线的颜色完全填充它(将其backgroundColor属性设置为线的颜色.然后修改图层的位置,边界,变换属性,使CALayer覆盖您线条的确切区域.

当然,这种方法只能绘制直线.但它也可以通过将contents属性设置为图像来修改以绘制复杂的视觉效果.例如,您可以使用此技术在线上具有模糊边缘的发光效果.

虽然这种技术有其局限性,但我经常在iPhone和Mac上的不同应用程序中使用它.它总是具有比基于核心图形的绘图显着优越的性能.

编辑:用于计算图层属性的代码:

void setLayerToLineFromAToB(CALayer *layer, CGPoint a, CGPoint b, CGFloat lineWidth)
{
    CGPoint center = { 0.5 * (a.x + b.x), 0.5 * (a.y + b.y) };
    CGFloat length = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    CGFloat angle = atan2(a.y - b.y, a.x - b.x);

    layer.position = center;
    layer.bounds = (CGRect) { {0, 0}, { length + lineWidth, lineWidth } };
    layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
}
Run Code Online (Sandbox Code Playgroud)

2nd Edit: 这是一个简单的测试项目,它显示了Core Graphics和基于Core Animation的渲染之间的性能差异.

第3编辑:结果令人印象深刻:渲染30个可拖动的视图,每个视图相互连接(产生435行),使用Core Animation在iPad 2上以60Hz平滑渲染.使用经典方法时,帧速率降至5 Hz,最终出现内存警告.

性能比较核心图形与核心动画

  • @DarkDust添加了代码以显示如何设置图层属性. (2认同)
  • @pqnet:您可以通过在应用程序的Info.plist中设置`UIViewEdgeAntialiasing`来为核心动画层打开抗锯齿. (2认同)

Dar*_*ust 5

首先,要在iOS上绘图,您需要一个上下文,当在屏幕上绘图时,您无法获得drawRect:(UIView)或drawLayer:inContext:(CALayer)之外的上下文.这意味着选项3已经出局(如果您打算在drawRect:方法之外进行).

你可以去找一个CALayer,但我会在这里找一个UIView.据我了解你的设置,你有这个:

    UIScrollView
    |     |    |
ViewA   ViewB  LineView
Run Code Online (Sandbox Code Playgroud)

因此LineView是ViewA和ViewB的兄弟,需要足够大以覆盖ViewA和ViewB,并且被安排在两者之前(并且已经setOpaque:NO设置).

LineView的实现是非常简单的:给它两个属性point1point2类型CGPoint的.(可选)自己实现setPoint1:/ setPoint2:方法,以便它始终调用,[self setNeedsDisplay];以便在更改点后重绘自身.

在LineView中drawRect:,您只需要使用CoreGraphicsUIBezierPath绘制线条.使用哪一个或多或少是品味问题.当您想使用CoreGraphics时,您可以这样做:

- (void)drawRect:(CGRect)rect
{
     CGContextRef context = UIGraphicsGetCurrentContext();
     // Set up color, line width, etc. first.
     CGContextMoveToPoint(context, point1);
     CGContextAddLineToPoint(context, point2);
     CGContextStrokePath(context);
}
Run Code Online (Sandbox Code Playgroud)

使用NSBezierPath,它看起来非常相似:

- (void)drawRect:(CGRect)rect
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    // Set up color, line width, etc. first.
    [path moveToPoint:point1];
    [path addLineToPoint:point2];
    [path stroke];
}
Run Code Online (Sandbox Code Playgroud)

神奇的是现在获得point1和point2的正确坐标.我假设你有一个可以看到所有视图的控制器.UIView的有两个很好的实用方法,convertPoint:toView:并且convertPoint:fromView:,你需要在这里.这是控制器的虚拟代码,它会导致LineView在ViewA和ViewB的中心之间画一条线:

- (void)connectTheViews
{
    CGPoint p1, p2;
    CGRect frame;
    frame = [viewA frame];
    p1 = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
    frame = [viewB frame];
    p2 = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
    // Convert them to coordinate system of the scrollview
    p1 = [scrollView convertPoint:p1 fromView:viewA];
    p2 = [scrollView convertPoint:p2 fromView:viewB];
    // And now into coordinate system of target view.
    p1 = [scrollView convertPoint:p1 toView:lineView];
    p2 = [scrollView convertPoint:p2 toView:lineView];
    // Set the points.
    [lineView setPoint1:p1];
    [lineView setPoint2:p2];
    [lineView setNeedsDisplay]; // If the properties don't set it already
}
Run Code Online (Sandbox Code Playgroud)

由于我不知道你如何实现拖动,我无法告诉你如何在控制器上触发调用此方法.如果它完全封装在您的视图中并且不涉及控制器,那么每次将视图拖动到新坐标时,我都会发布一个NSNotification.控制器将监听通知并调用上述方法来更新LineView.

最后一个注意事项:您可能希望setUserInteractionEnabled:NO在其initWithFrame:方法中调用LineView,以便在该行上触摸将进入该行下的视图.

快乐的编码!