Подтвердить что ты не робот

Какой лучший подход для рисования линий между представлениями?

Фон: у меня есть пользовательский scrollview (subclassed), который имеет uiimageviews на нем, который перетаскивается, на основе перетаскивания мне нужно нарисовать некоторые строки динамически в подпункте uiscrollview. (Заметьте, мне нужны они в подвью, так как в более поздней точке мне нужно изменить непрозрачность вида.)

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

  • Создайте подкласс UIView и используйте метод drawRect для рисования нужной строки (но не знаете, как сделать ее динамически считанной в значениях)
  • В подзаголовке используйте CALayers и нарисуйте там
  • Создайте метод рисования с использованием функций CGContext
  • Что-то еще?

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

4b9b3361

Ответ 1

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

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

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

Там есть простой способ сделать это, полностью избегая описанных накладных расходов: используйте 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);
}

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

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

Performance comparison Core Graphics vs. Core Animation

Ответ 2

Во-первых, для рисования на 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:, чтобы прикоснуться к строке перейдут к представлению под строкой.

Счастливое кодирование!