Как правильно использовать setNeedsDisplayInRect для приложений iOS?

Я нахожусь на Yosemite 10.10.5 и Xcode 7, используя Swift, чтобы сделать игру с таргетингом на iOS 8 и выше.

РЕДАКТИРОВАТЬ: Более подробная информация, которая может быть полезна: это 2D-игра-головоломка/аркада, в которой игрок перемещает камни, чтобы соответствовать им. 3D-рендеринга вообще нет. Рисование уже слишком медленное, и я еще не дошел до взрывов с мусором. Существует также уровень затухания, очень важный. Но это все на симуляторе. У меня пока нет реального iPhone для тестирования, и я уверен, что фактическое устройство будет, по крайней мере, немного быстрее.

У меня есть собственный Draw2D-класс, который является типом UIView, настроенным как этот учебник. У меня есть один NSTimer, который инициирует следующую цепочку вызовов в Draw2D:

[setNeedsDisplay]; // which calls drawRect, which is the master draw function of Draw2D

drawRect(rect: CGRect)
{
  scr_step(); // the master update function, which loops thru all objects and calls their individual update functions. I put it here so that updating and drawing are always in sync

  CNT = UIGraphicsGetCurrentContext(); // get the curret drawing context

  switch (Realm) // based on what realm im in, call the draw function for that realm
  {
    case rlm.intro: scr_draw_intro();
    case rlm.mm: scr_draw_mm();
    case rlm.level: scr_draw_level(); // this in particular loops thru all objects and calls their individual draw functions

    default: return;
  }

  var i = AARR.count - 1; // loop thru my own animation objects and draw them too, note it iterating backwards because sometimes they destroy themselves
  while (i >= 0)
  {
    let A = AARR[i];
    A.scr_draw();

    i -= 1;
  }
}

И весь рисунок работает нормально, но медленно.

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

Я не мог найти для этого учебники или хороший пример кода. Самое близкое, что я нашел, это документация с документацией здесь, но она не объясняет, среди прочего, как получить список всех грязных прямоугольников. Он также явно не указывает , если список грязных прямоугольников автоматически очищается в конце каждого вызова drawRect?

Он также не объясняет, нужно ли вручную обрезать весь рисунок на основе прямоугольников. Я нашел противоречивую информацию об этом в Интернете, по-видимому, разные версии iOS делают это по-другому. В частности, если я собираюсь вручную закрепить вещи, я не вижу в первую очередь сути функции ядра яблока. Я мог бы просто сохранить свой собственный список прямоугольников и вручную сравнить каждый прямоугольник назначения чертежа с грязным прямоугольником, чтобы увидеть, нужно ли рисовать что угодно. Это было бы огромной болью, однако, потому что у меня есть фоновое изображение на каждом уровне, и я бы нарисовал часть его за каждым движущимся объектом. На самом деле я надеюсь, что это правильный способ использования setNeedsDisplayInRect, чтобы позволить основной структуре делать автоматическое отсечение для всего, что нарисовано в следующем цикле рисования, так что оно автоматически рисует только ту часть фон плюс движущийся объект сверху.

Итак, я попробовал несколько экспериментов: сначала в моем массиве камней:

func scr_draw_stone()
{
  // the following 3 lines are new, I added them to try to draw in only dirty rectangles
  if (xvp != xv || yvp != yv) // if the stone coordinates have changed from its previous coordinates
  {
    MyD.setNeedsDisplayInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // MyD.swc is Draw2D current square width in points, maintained to softcode things for different screen sizes.
  }

  MyD.img_stone?.drawInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // draw the plain stone
  img?.drawInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // draw the stone icon
}

Это ничего не меняет. Все делалось так же медленно, как раньше. Итак, я положил его в скобки:

[MyD.setNeedsDisplayInRect(CGRectMake(x, y, MyD.swc, MyD.shc))];

Я понятия не имею, что делают скобки, но мой оригинальный setNeedsDisplay был в скобках точно так же, как они сказали сделать в учебнике. Поэтому я попробовал это в своем каменном объекте, но это тоже не повлияло.

Итак, что мне нужно сделать для правильной работы setNeedsDisplayInRect?

Прямо сейчас, я подозреваю, что мне нужна специальная проверка в моей мастер-схеме, например:

if (ListOfDirtyRectangles.count == 0)
{
  [setNeedsDisplay]; // just redraw the whole view
}
else
{
  [setNeedsDisplayInRect(ListOfDirtyRecangles)];
}

Однако я не знаю названия встроенного списка грязных прямоугольников. Я нашел этот, указав, что имя метода getRectsBeingDrawn, но это для Mac OSX. Он не существует в iOS.

Может ли кто-нибудь помочь мне? Я на правильном пути с этим? Я до сих пор довольно новичок в Mac и iOS.

Ответ 1

Вы действительно должны избегать переопределения drawRect, если это вообще возможно. Существующие представления/технологии используют любые аппаратные возможности, чтобы сделать вещи намного быстрее, чем вручную рисовать в графическом контексте, включая буферизацию содержимого представлений, использование графического процессора и т.д. Это повторяется много раз в "Руководстве по программированию на просмотр для IOS".

Если у вас есть фон и другие объекты поверх этого, вы, вероятно, должны использовать отдельные виды или слои для них, а не перерисовывать их.

Вы также можете рассмотреть такие технологии, как SpriteKit, SceneKit, OpenGL ES и т.д.

Помимо этого, я не совсем уверен, что понимаю ваш вопрос. Когда вы вызываете setNeedsDisplayInRect, он будет добавлять этот прямоугольник к тем, которые нужно перерисовать (возможно, слияние с прямоугольниками, которые уже есть в списке). drawRect: затем будет называться бит позже, чтобы рисовать эти прямоугольники по одному за раз.

Вся точка разделения setNeedsDisplayInRect/drawRect: заключается в том, чтобы убедиться, что несколько запросов перерисовать определенную часть представления объединены вместе, а рисование выполняется только один раз за цикл перерисовывания.

Вы не должны вызывать свой метод scr_step в drawRect:, так как он может быть вызван несколько раз в цикле циклического цикла. Это четко указано в "Руководстве по программированию для iOS" (выделено мной):

Реализация вашего метода drawRect: должна делать ровно один вещь: нарисуйте свой контент. Этот метод не является местом для обновления ваши структуры данных приложений или выполнение каких-либо задач, не связанных для рисования. Он должен настроить среду рисования, нарисовать контента и выхода как можно быстрее. И если ваш метод drawRect: часто можно называть, вы должны делать все возможное, чтобы оптимизируйте свой код чертежа и рисуйте как можно меньше каждый раз вызывается метод.

Что касается отсечения, в документации drawRect указано, что:

Вы должны ограничить любой чертеж прямоугольником, указанным в прямоугольнике параметр. Кроме того, если непрозрачное свойство вашего представления установлено на YES, ваш метод drawRect: должен полностью заполнить указанный прямоугольник с непрозрачным контентом.

Не имея представления о том, что показывает ваш взгляд, какой метод вы называете, что на самом деле требует времени, трудно обеспечить гораздо более глубокое понимание того, что вы могли бы сделать. Предоставьте более подробную информацию о ваших реальных потребностях, и мы сможем помочь.