sre*_*mod 17 iphone cocoa-touch core-animation core-graphics calayer
背景:我有一个自定义的scrollview(子类),其上有uiimageviews可拖动,基于我需要在uiscrollview的子视图中动态绘制一些行的拖动.(注意我需要在子视图中使用它们,因为稍后我需要更改视图的不透明度.)
所以在我花了很长时间开发代码之前(我是一个新手所以它需要我一段时间)我研究了我需要做什么并找到了一些可能的方法.只是想知道正确的方法是什么.
干杯求救
Nik*_*uhe 44
从概念上讲,你所有的命题都是相似的.所有这些都将导致以下步骤(其中一些步骤由UIKit无形地完成):
上述步骤的昂贵部分是前三点.它们导致重复的内存分配,内存复制和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,最终出现内存警告.

首先,要在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的实现是非常简单的:给它两个属性point1和point2类型CGPoint的.(可选)自己实现setPoint1:/ setPoint2:方法,以便它始终调用,[self setNeedsDisplay];以便在更改点后重绘自身.
在LineView中drawRect:,您只需要使用CoreGraphics或UIBezierPath绘制线条.使用哪一个或多或少是品味问题.当您想使用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,以便在该行上触摸将进入该行下的视图.
快乐的编码!