2011-05-01 67 views
16

背景:我有一个自定义滚动视图(subclassed),它具有可拖动的uiimageviews,基于拖动我需要在uiscrollview的子视图中动态绘制一些行。 (注意我在子视图中需要它们,因为在稍后的时间点,我需要更改视图的不透明度。)在视图之间绘制线条的最佳方法是什么?

因此,在开发代码之前(我是新手,所以需要我一段时间)我研究了我需要做的事情,并找到了一些可能的方法。只是想知道什么是正确的做法。

  1. 创建的UIView的子类和使用drawRect方法绘制我需要的线(但不确定如何让它在数值动态读取)
  2. 在子视图使用CALayers和借鉴有
  3. 使用CGContext函数创建一个画线方法
  4. 还有其他的东西吗?

干杯的帮助

回答

40

从概念上讲,所有的命题都是相似的。所有这些将导致以下步骤(其中一些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); 
} 

第二编辑:Here's a simple test project其示出了在核心图形和核心动画的渲染之间的性能的显着的差异。结果非常令人印象深刻:渲染30个可拖动的视图,每个视图相互连接(导致435行)使用Core Animation在iPad 2上以60Hz平滑渲染。当使用经典方法时,帧速率降至5 Hz,并最终出现内存警告。

Performance comparison Core Graphics vs. Core Animation

+0

+1是最有效的方法,但计算正确的位置和转换比使用简单的绘图解决方案(UIView或CALayer)更难。给定两点* p1 *和* p2 *,如何正确定位/旋转图层?我有一些想法,但都没有感觉“正确”。 – DarkDust 2011-05-01 11:56:07

+2

@DarkDust添加代码以显示如何设置图层属性。 – 2011-05-01 14:26:25

+0

@Niklai Ruhe:谢谢,这比我想象的更简单。 – DarkDust 2011-05-01 14:43:10

5

首先,在iOS上绘制,你需要一个框架,并且在屏幕上绘制时,你不能得到drawRect:(UIView的)或drawLayer:inContext:(CALayer的)外的环境。这意味着选项3不存在(如果您打算在drawRect:方法之外执行此操作)。

你可以去CALayer,但我会去这里的UIView。至于我明白你的设置,你有这样的:

UIScrollView 
    |  | | 
ViewA ViewB LineView 

所以LineView是ViewA和ViewB的兄弟姐妹,就需要足够大,以涵盖ViewA和ViewB并且设置成在两个前(并有setOpaque:NO集)。

LineView的实现将非常简单:给它两个CGPoint类型的属性point1point2。也可以自己实施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); 
} 

使用NSBezierPath,它会看上去非常相似:

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

现在的魔术越来越为点1和点2的正确坐标。我假设你有一个可以看到所有视图的控制器。 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 
} 

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

最后一个注意事项:您可能需要在您的LineView的initWithFrame:方法中调用setUserInteractionEnabled:NO,以便线上的触摸将通过该线下的视图。

快乐编码!

+0

+1对于很好解释的直接方法。 – 2011-05-01 14:30:34

相关问题