Каков наилучший подход для рисования линий между видами? - PullRequest
17 голосов
/ 01 мая 2011

Справочная информация: у меня есть пользовательский вид прокрутки (в подклассе), в котором есть изображения uiimage, которые можно перетаскивать, основываясь на перетаскивании, мне нужно динамически рисовать некоторые линии в подвиде uiscrollview.(Обратите внимание, что они нужны мне в подпредставлении, так как позже мне нужно изменить непрозрачность представления.)

Итак, прежде чем я потрачу целую вечность на разработку кода (я новичок, поэтому мне понадобитсявремя) Я посмотрел на то, что мне нужно сделать, и нашел несколько возможных путей.Просто интересно, как правильно это сделать.

  1. Создайте подкласс UIView и используйте метод drawRect, чтобы нарисовать нужную мне линию (но не знаете, как сделать так, чтобы она динамически читалась в значениях)
  2. В подпредставлении используйте CALayers и рисуйте там
  3. Создайте метод рисования линии, используя функции CGContext
  4. Что-то еще?

Приветствия за помощь

Ответы [ 2 ]

43 голосов
/ 01 мая 2011

Концептуально все ваши предложения похожи.Все они приведут к следующим шагам (некоторые из них незаметно выполняются UIKit):

  1. Настройка растрового контекста в памяти.
  2. Использование Core Graphics для рисования линии вРастровое изображение.
  3. Скопируйте это растровое изображение в буфер графического процессора (текстуру).
  4. Составьте иерархию слоя (представления), используя графический процессор.

Дорогая частьвышеупомянутые шаги - первые три пункта.Они приводят к повторному выделению памяти, копированию памяти и обмену данными между процессором и графическим процессором.С другой стороны, то, что вы действительно хотите сделать, это легкий: нарисуйте линию, возможно, анимацию начальной / конечной точек, ширину, цвет, альфа, ...

Есть простой способ сделать это, полностью избегаяописанные накладные расходы: используйте CALayer для вашей линии, но вместо перерисовки содержимого на CPU просто заполните его цветом линии (присвойте свойству backgroundColor цвет линии. Затем измените свойства слоя для position, bounds,трансформировать, чтобы 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);
}

2nd Edit: Вот простой тестовый проект который показывает резкое различие в производительности между Core Graphics и рендерингом на основе Core Animation.

3rd Edit: Результаты весьма впечатляющие: рендеринг 30 перетаскиваемых видов, каждый из которых связан друг с другом (что приводит к435 линий) плавно рендерится на частоте 60 Гц на iPad 2 с использованием Core Animation.При использовании классического подхода частота кадров падает до 5 Гц, и со временем появляются предупреждения о памяти.

Performance comparison Core Graphics vs. Core Animation

5 голосов
/ 01 мая 2011

Во-первых, для рисования на iOS вам нужен контекст, а при рисовании на экране вы не можете получить контекст за пределами drawRect: (UIView) или drawLayer:inContext: (CALayer). Это означает, что вариант 3 отсутствует (если вы намеревались сделать это вне метода drawRect:).

Вы могли бы пойти на CALayer, но я бы пошел на UIView здесь. Насколько я понял ваши настройки, у вас есть это:

    UIScrollView
    |     |    |
ViewA   ViewB  LineView

Таким образом, LineView является родственным элементом ViewA и ViewB, он должен быть достаточно большим, чтобы охватить как ViewA, так и ViewB, и должен располагаться перед обоими (и имеет setOpaque:NO set).

Реализация 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);
}

Используя NSBezierPath, это выглядело бы очень похоже:

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

Магия теперь получает правильные координаты для 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
}

Поскольку я не знаю, как вы реализовали перетаскивание, я не могу сказать вам, как вызвать вызов этого метода на контроллере. Если это сделано полностью инкапсулированным в ваших представлениях, а контроллер не задействован, я бы использовал NSNotification, которое вы публикуете каждый раз, когда представление перетаскивается в новую координату. Контроллер прослушивает уведомление и вызывает вышеупомянутый метод для обновления LineView.

Последнее замечание: вы можете вызвать setUserInteractionEnabled:NO для вашего LineView в его методе initWithFrame:, чтобы прикосновение к линии проходило до вида под линией.

Удачного кодирования!

...