Кварцевый метод 2D drawRect (iPhone) - PullRequest
32 голосов
/ 04 апреля 2009

Передо мной 4 разные книги iPhone / Cocoa / Core Animation / Objective-C , а также многочисленные примеры кода из Интернета. Тем не менее, я все еще чувствую, что мне не хватает фундаментального понимания того, как рисование работает в Quartz 2D .

Является ли drawRect() просто крючком для выполнения вашего кода рисования? Или этот метод должен также перерисовать регионы, которые «повреждены» и нуждаются в перерисовке? Могу ли я просто нарисовать свой материал один раз, а потом он «залипнет», или я должен перекрасить всю сцену в любое время с помощью drawRect()? Java-объект Graphics2D работает таким образом - вы должны рисовать все свое «изображение» каждый раз, когда вызывается paint (), поэтому вы должны быть готовы к его повторному построению в любое время (или кешированию).

Как бы вы реализовали простую программу для рисования? Придется ли вам «запоминать» каждую линию / точку / обводку, которую нарисовал пользователь, и повторять ее каждый раз, когда вызывается drawRect()? Как насчет рендеринга за кадром? Можете ли вы сделать все свои рисунки, а затем позвонить [self setNeedsDisplay], чтобы ваши записи были сброшены на экран?

Допустим, в ответ на прикосновение пользователя я хочу поставить «Х» на экране, где он коснулся. Х должен оставаться там, и каждое новое прикосновение производит еще один Х. Нужно ли мне помнить все эти координаты касания и затем рисовать их все в drawRect()?

EDIT:

Если только я не понял, ответы Джоконора и Гектора Рамоса, приведенные ниже, противоречат друг другу. И это хорошая демонстрация моего замешательства по этому вопросу. : -)

Ответы [ 4 ]

38 голосов
/ 04 апреля 2009

Некоторая путаница между различными ссылками на Какао происходит из-за представления в Leopard слоистых представлений. На iPhone все UIViews имеют слоистую поддержку, тогда как в представлениях Leopard необходимо вручную включить поддержку слоев.

Для представления на основе слоя содержимое отрисовывается один раз с использованием того, что вы указали в drawRect(), но затем буферизуется в слой. Слой действует как прямоугольная текстура, поэтому, когда вы перемещаете представление на основе слоя или закрываете его, перерисовка не требуется, текстура просто перемещается в это место через графический процессор. Если вы не установите needsDisplayOnBoundsChange property на YES для слоя, изменение размера слоя (или содержащего его вида) будет просто масштабировать содержимое. Это может привести к размытости графики в вашем представлении или слое, поэтому в этом случае вы можете вызвать перерисовку. setNeedsDisplay вызовет ручную перерисовку содержимого представления или слоя и последующее повторное получение этого содержимого в слое.

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

Ссылки на основе какао, которые вы видели и относящиеся к рабочему столу, могут предполагать представления без слоя, которые вызывают drawRect: в любое время, когда необходимо обновить представление, будь то движение, масштабирование или наличие детали зрения скрыты. Как я уже сказал, все UIViews имеют многослойную поддержку, поэтому на iPhone это не так.

Тем не менее, для вашего приложения для рисования один из способов сделать это - сохранить массив нарисованных объектов и вызвать drawRect: каждый раз, когда пользователь добавляет что-то новое, повторяя все ранее нарисованные объекты по порядку. Я мог бы предложить альтернативный подход, при котором вы создаете новый UIView или CALayer для каждой операции рисования. Содержимое этой операции рисования (линия, дуга, X и т. Д.) Будет нарисовано отдельным видом или слоем. Таким образом, вам не придется перерисовывать все при новом прикосновении, и вы могли бы сделать некоторое аккуратное редактирование в векторном стиле, перемещая каждый из нарисованных элементов независимо друг от друга. Для сложных рисунков в этом может быть некоторая компромиссность памяти, но я бы поспорил, что она будет иметь гораздо лучшую производительность рисования (минимальное использование процессора и мерцание).

4 голосов
/ 04 апреля 2009

drawRect () будет рисовать в буфер за пределами экрана. Вам не нужно перерисовывать каждый раз, когда регионы «повреждены» как таковые, так как iPhone OS заботится о наложении слоев. Вы просто записываете один раз в буфер и позволяете ОС обрабатывать все остальное. Это не похоже на другие среды программирования, где вам нужно продолжать перерисовывать всякий раз, когда что-то проходит над вашим представлением.

2 голосов
/ 22 октября 2011

Является ли drawRect () просто крючком для выполнения вашего кода рисования?

Он предназначен для перерисовки области (прямоугольника), которая передается вам, используя текущий стек графического контекста. Не более.

Или этот метод должен также перерисовывать области, которые "повреждены" и нуждаются в перерисовке?

Нет. Если грязные регионы не пересекаются, вы можете получить несколько вызовов drawRect: с различными передаваемыми вам ректами. Rects признаны недействительными, используя setNeedsDisplayInRect:. Если необходимо перерисовать только часть поверхности вашего вида, вам будет предложено нарисовать эту часть, когда придет время рисовать.

Могу ли я просто нарисовать свои вещи один раз, а потом они "залипают", или я должен перекрасить всю сцену в любое время с помощью drawRect ()?

Он не «прилипает». Rects становятся недействительными во время выполнения вашего приложения, и вас просят перерисовать эти reect, когда системе представления необходимо обновить экран. Вы перекрашиваете только тот прямоугольник, который запрашивается.

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

Java-объект Graphics2D работает таким образом - вы должны рисовать все свое «изображение» каждый раз, когда вызывается paint (), поэтому вы должны быть готовы к его восстановлению в любое время (или кешированию).

Не так с AppKit или UIKit.

Как бы вы реализовали простую программу для рисования? Придется ли вам «запоминать» каждую линию / точку / обводку, которую рисовал пользователь, и повторять это каждый раз, когда вызывается drawRect ()?

Вы должны будете помнить контекст (например, каждую линию / точку / штрих), необходимый для рисования вашего вида. Вам нужно только нарисовать регион, который запрашивается. Технически графическая система не будет жаловаться, если вы будете рисовать за пределами этого прямоугольника, но это может привести к артефактам.

Для сложного рендеринга может быть проще или эффективнее нарисовать во внешнем буфере (например, растровом изображении), а затем использовать это предварительно представленное растровое представление, чтобы получить изображение на экране в drawrect:. (см. также ответ Брэда для слоев)

Как насчет "закадрового" рендеринга? Вы можете сделать все свои рисунки, а затем вызвать [self setNeedsDisplay], чтобы ваши записи были сброшены на экран?

Да, вы можете сделать это. В частности, вы должны выполнить рендеринг во внешний буфер (например, растровое изображение), когда вы закончите рендеринг в растровое изображение, сделаете недействительным прямоугольник, который вы хотите нарисовать, а затем начнете рисовать на экране, используя данные в растровом изображении при вызове drawRect:.

Допустим, в ответ на прикосновение пользователя я хочу поставить «Х» на экране, где он коснулся. Х должен оставаться там, и каждое новое касание создает еще один Х. Нужно ли мне помнить все эти координаты касания и затем рисовать их все в drawRect ()?

Ну, у вас есть несколько вариантов. Ваше предложение одно (при условии, что вы рисуете в переданном вам прямоугольнике). Другой способ - создать представление «X» и просто запомнить точки, необходимые для восстановления представления, если вам нужно, чтобы эти X сохранялись при запусках. Во многих случаях вы можете легко разделить сложные задачи на слои (простая 2D игра):

  • 1) Фоновое изображение с горизонтом.
  • 2) Некоторые вещи на переднем плане, которые меняются не часто.
  • 3) Персонаж, которого игрок использует для навигации по игре.

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

2 голосов
/ 04 апреля 2009

Всегда будьте готовы нарисовать соответствующую область вашего взгляда, когда вызывается drawRect:.

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

...