6

我目前有一个NSView绘制网格图案(基本上是水平和垂直线的指南)的想法是用户可以更改网格的间距和网格的颜色。

网格的目的是在排列对象时充当用户的指南。除了一个例外,一切都很好。当我NSWindow通过拖动调整大小手柄调整大小时,如果我的网格间距特别小(比如 10 像素)。拖动调整大小本质上变得昏昏欲睡。

drawRect的网格代码如下:

-(void)drawRect:(NSRect)dirtyRect {

    NSRect thisViewSize = [self bounds];

    // Set the line color

    [[NSColor colorWithDeviceRed:0 
                           green:(255/255.0) 
                            blue:(255/255.0) 
                           alpha:1] set];

    // Draw the vertical lines first

    NSBezierPath * verticalLinePath = [NSBezierPath bezierPath];

    int gridWidth = thisViewSize.size.width;
    int gridHeight = thisViewSize.size.height;

    int i;

    while (i < gridWidth)
    {
        i = i + [self currentSpacing];

        NSPoint startPoint = {i,0};
        NSPoint endPoint = {i, gridHeight};

        [verticalLinePath setLineWidth:1];
        [verticalLinePath moveToPoint:startPoint];
        [verticalLinePath lineToPoint:endPoint];
        [verticalLinePath stroke];
    }

    // Draw the horizontal lines

    NSBezierPath * horizontalLinePath = [NSBezierPath bezierPath];

    i = 0;

    while (i < gridHeight)
    {
        i = i + [self currentSpacing];

        NSPoint startPoint = {0,i};
        NSPoint endPoint = {gridWidth, i};

        [horizontalLinePath setLineWidth:1];
        [horizontalLinePath moveToPoint:startPoint];
        [horizontalLinePath lineToPoint:endPoint];

        [horizontalLinePath stroke];
    }
}

我怀疑这完全与我绘制网格的方式有关,并且我愿意接受有关如何更好地处理它的建议。

我可以看到效率低下的地方,拖动调整大小在调整大小时NSWindow不断调用drawRect此视图中的 ,并且网格越接近,父窗口的每个像素拖动的计算越多。

我正在考虑在调整窗口大小时隐藏视图,但感觉不那么动态。我希望用户体验非常流畅,没有任何明显的延迟或闪烁。

有没有人对绘制网格的更好或更有效的方法有任何想法?

一如既往,非常感谢所有帮助。

4

3 回答 3

13

您无意中在算法中引入了Schlemiel。每次调用moveToPointlineToPoint循环时,实际上都是在同一路径中添加更多行,每次调用该路径时都会绘制所有这些stroke行。

这意味着你第一次画一条线,第二次画两条线,第三次画三条线,等等......

一个快速的解决方法是每次循环都使用一个新路径,只需执行循环stroke (感谢 Jason Coco 的想法):

path = [NSBezierPath path];
while (...)
{
    ...

    [path setLineWidth:1];
    [path moveToPoint:startPoint];  
    [path lineToPoint:endPoint];
}
[path stroke];

更新:另一种方法是完全避免创建它NSBezierPath,只使用strokeLineFromPoint:toPoint:类方法:

[NSBezierPath setDefaultLineWidth:1];
while (...)
{
    ...
    [NSBezierPath strokeLineFromPoint:startPoint toPoint:endPoint];
}

更新#2:到目前为止,我对这些方法进行了一些基本的基准测试。我使用的窗口大小为 800x600 像素,网格间距为 10 像素,我让可可重绘窗口一千次,从 800x600 缩放到 900x700 并再次缩放。在我的 2GHz Core Duo Intel MacBook 上运行,我看到以下时间:

Original method posted in question:  206.53 seconds  
Calling stroke after the loops:       16.68 seconds  
New path each time through the loop:  16.68 seconds  
Using strokeLineFromPoint:toPoint:    16.68 seconds  

这意味着减速完全是由重复引起的,并且任何几个微改进中的任何一个都对实际加速几乎没有作用。这应该不足为奇,因为在屏幕上实际绘制像素(几乎总是)比简单的循环和数学运算更占用处理器资源。

需要吸取的教训:

  1. 隐藏的 Schlemiels真的可以减慢速度。
  2. 在进行不必要的优化之前始终分析您的代码
于 2010-04-26T22:38:52.180 回答
0

您应该运行 Instruments Cpu Sampler 以确定大部分时间花在哪里,然后根据该信息进行优化。如果是中风,请将其放在循环之外。如果它正在绘制路径,请尝试将渲染卸载到 gpu。看看 CALayer 能不能帮上忙。

于 2010-04-26T23:45:56.533 回答
0

也许聚会迟到了,但是有人会发现这很有帮助。最近,我需要一个客户的自定义组件,以便重新创建一个网格可调整大小的覆盖 UIView。以下内容应该适用于工作,即使尺寸很小也没有问题。

该代码适用于 iPhone (UIView),但可以非常快速地移植到 NSView。

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextClearRect(context, rect);
    CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);

    //corners
    CGContextSetLineWidth(context, 5.0);
    CGContextMoveToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, 15, 0);
    CGContextMoveToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, 0, 15);
    CGContextMoveToPoint(context, rect.size.width, 0);
    CGContextAddLineToPoint(context, rect.size.width-15, 0);
    CGContextMoveToPoint(context, rect.size.width, 0);
    CGContextAddLineToPoint(context, rect.size.width, 15);
    CGContextMoveToPoint(context, 0, rect.size.height);
    CGContextAddLineToPoint(context, 15, rect.size.height);
    CGContextMoveToPoint(context, 0, rect.size.height);
    CGContextAddLineToPoint(context, 0, rect.size.height-15);
    CGContextMoveToPoint(context, rect.size.width, rect.size.height);
    CGContextAddLineToPoint(context, rect.size.width-15, rect.size.height);
    CGContextMoveToPoint(context, rect.size.width, rect.size.height);
    CGContextAddLineToPoint(context, rect.size.width, rect.size.height-15);
    CGContextStrokePath(context);


    //border
    CGFloat correctRatio = 2.0;
    CGContextSetLineWidth(context, correctRatio);
    CGContextAddRect(context, rect);
    CGContextStrokePath(context);

    //grid
    CGContextSetLineWidth(context, 0.5);
    for (int i=0; i<4; i++) {
        //vertical
        CGPoint aPoint = CGPointMake(i*(rect.size.width/4), 0.0);
        CGContextMoveToPoint(context, aPoint.x, aPoint.y);
        CGContextAddLineToPoint(context,aPoint.x, rect.size.height);
        CGContextStrokePath(context);

        //horizontal
        aPoint = CGPointMake(0.0, i*(rect.size.height/4));
        CGContextMoveToPoint(context, aPoint.x, aPoint.y);
        CGContextAddLineToPoint(context,rect.size.width, aPoint.y);
        CGContextStrokePath(context);
    }

}
于 2016-01-07T18:56:47.780 回答