ОС iOS: использование наследник UIView это drawRect:' против делегировать своего слоя 'drawLayer:контекстными:'
у меня есть класс, который является подклассом UIView
. Я могу рисовать вещи внутри представления либо путем реализации drawRect
метод, или путем реализации drawLayer:inContext:
который является делегат метод CALayer
.
у меня есть два вопроса:
- как решить, какой подход использовать? Есть ли прецедент для каждого из них?
-
если я реализую
drawLayer:inContext:
, это называется (иdrawRect
нет, по крайней мере, насколько можно определить точку останова), даже если я не назначу свое представление какCALayer
делегата с помощью:[[self layer] setDelegate:self];
как вызвать метод делегата, если мой экземпляр не определен как делегат слоя? а какой механизм мешает
drawRect
от того, чтобы называться еслиdrawLayer:inContext:
называется?
6 ответов:
как решить, какой подход использовать? Есть ли прецедент для каждого из них?
всегда использовать
drawRect:
, и никогда не использоватьUIView
как делегат чертежа для любогоCALayer
.как вызвать метод делегата, если мой экземпляр не определен как делегат слоя? и какой механизм предотвращает вызов drawRect, если
drawLayer:inContext:
называется?каждый
UIView
экземпляр является делегатом чертежа для его поддержкаCALayer
. Вот почему[[self layer] setDelegate:self];
казалось, ничего не делать. Это излишне. ЭлементdrawRect:
метод фактически является методом делегата чертежа для слоя вида. Внутренне,UIView
осуществляетdrawLayer:inContext:
где он делает свои вещи, а затем называетdrawRect:
. Вы можете увидеть это в отладчике:вот почему
drawRect:
никогда не вызывался, когда вы реализовалиdrawLayer:inContext:
. Именно поэтому вы никогда не должны реализовывать ни один изCALayer
рисунок делегат методы в пользовательскомUIView
подкласс. Также никогда не следует создавать вид делегата чертежа для другого слоя. Это вызовет всевозможные глупости.если вы реализуете
drawLayer:inContext:
потому что вам нужно получить доступ кCGContextRef
, вы можете получить это изнутри вашегоdrawRect:
по телефонуUIGraphicsGetCurrentContext()
.
drawRect
должны быть реализованы только тогда, когда это абсолютно необходимо. Реализация по умолчаниюdrawRect
включает в себя ряд интеллектуальных оптимизаций, таких как интеллектуальное кэширование рендеринга представления. Переопределение его обходит все эти оптимизации. Это плохо. Использование методов рисования слоев эффективно почти всегда будет превосходить пользовательскийdrawRect
. Apple используетUIView
как делегат для ACALayer
часто - на самом деле, каждый UIView является делегатом этого слоя. Вы можете см. раздел настройка чертежа слоя внутри UIView в нескольких образцах Apple, включая (в настоящее время) ZoomingPDFViewer.в то время как использование
drawRect
Это распространенная практика, которая была обескуражена, по крайней мере, с 2002/2003 года, IIRC. Осталось не так много веских причин идти по этому пути.Расширенная оптимизация производительности на iPhone OS (слайд 15)
Понимание Программирования С Использованием UIKit Перевода
технические вопросы и ответы QA1708: повышение производительности рисования изображений на iOS
Просмотр Руководства По Программированию: Оптимизация Вида Чертежа
вот коды образца ZoomingPDFViewer от Apple:
-(void)drawRect:(CGRect)r { // UIView uses the existence of -drawRect: to determine if it should allow its CALayer // to be invalidated, which would then lead to the layer creating a backing store and // -drawLayer:inContext: being called. // By implementing an empty -drawRect: method, we allow UIKit to continue to implement // this logic, while doing our real drawing work inside of -drawLayer:inContext: } -(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context { ... }
если вы используете
drawLayer(_:inContext:)
илиdrawRect(_:)
(или оба) для пользовательского кода чертежа зависит от того, нужен ли вам доступ к текущему значению свойства слоя во время его анимации.я боролся сегодня с различными проблемами рендеринга, связанными с этими двумя функциями при реализации мой собственный класс метка. После проверки документации, выполнения некоторых проб и ошибок, декомпиляции UIKit и проверки Apple Custom Animatable Пример свойств у меня есть хорошее чувство о том, как это работает.
drawRect(_:)
Если вам не нужно получать доступ к текущему значению свойства слоя/представления во время его анимации, вы можете просто использовать
drawRect(_:)
для выполнения пользовательского чертежа. Все будет работать просто отлично.override func drawRect(rect: CGRect) { // your custom drawing code }
drawLayer(_:inContext:)
допустим, например, вы хотите использовать
backgroundColor
в вашем пользовательском коде чертежа:override func drawRect(rect: CGRect) { let colorForCustomDrawing = self.layer.backgroundColor // your custom drawing code }
при тестировании кода Вы будете обратите внимание, что
backgroundColor
не возвращает правильное (т. е. текущее) значение, пока анимация находится в полете. Вместо этого он возвращает конечное значение (т. е. значение, когда анимация завершена).для получения настоящее значение во время анимации, вы должны получить доступ к
backgroundColor
наlayer
параметр перешло кdrawLayer(_:inContext:)
. И вы также должны привлечь кcontext
параметр.это очень важно знать, что вид
self.layer
иlayer
параметр передан вdrawLayer(_:inContext:)
не всегда один и тот же слой! Последний может быть копией первого с частичной анимацией, уже примененной к его свойствам. таким образом, вы можете получить доступ к правильным значениям свойств анимации в полете.теперь рисунок работает так, как ожидалось:
override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code }
но есть два новых вопроса:
setNeedsDisplay()
и несколько свойств, таких какbackgroundColor
иopaque
больше не работают ваш взгляд.UIView
больше не перенаправляет вызовы и изменения на свой собственный слой.
setNeedsDisplay()
делает только что-то, если ваш взгляд реализуетdrawRect(_:)
. Это не имеет значения, если функция на самом деле делает что-то, но UIKit использует его, чтобы определить, делаете ли вы пользовательский рисунок или нет.свойства, вероятно, больше не работают, потому что
UIView
собственная реализацияdrawLayer(_:inContext:)
больше не звонил.так что решение довольно простое. Просто позвоните реализация суперкласса
drawLayer(_:inContext:)
и создать пустойdrawRect(_:)
:override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. }
резюме
использовать
drawRect(_:)
пока у вас нет проблемы, что свойства возвращают неправильные значения во время анимации:override func drawRect(rect: CGRect) { // your custom drawing code }
использовать
drawLayer(_:inContext:)
иdrawRect(_:)
если вам нужно получить доступ к текущему значению свойств вида / слоя во время их анимации:override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. }
на iOS перекрытие между видом и его слоем очень велико. По умолчанию представление является делегатом своего слоя и реализует слой
drawLayer:inContext:
метод. Как я понимаю,drawRect:
иdrawLayer:inContext:
более или менее эквивалентны в этом случае. Возможно, реализация по умолчаниюdrawLayer:inContext:
звонкиdrawRect:
илиdrawRect:
вызывается только еслиdrawLayer:inContext:
не реализуется вашим подклассом.как решить, какой подход использовать? Есть ли прецедент для каждого один?
это действительно не имеет значения. Чтобы следовать конвенции, я обычно использую
drawRect:
и резервные использованиеdrawLayer:inContext:
когда мне нужно было нарисовать подслои, которые не являются частью представления.
The Документация Apple имеет это сказать: "есть также другие способы предоставления содержимого представления, такие как установка содержимого базового слоя напрямую, но переопределение метода drawRect: является наиболее распространенным методом."
но он не вдается в детали, так что это должно быть подсказкой: не делайте этого, если вы действительно не хотите испачкать руки.
делегат слоя UIView указывает на UIView. Однако UIView действительно ведет себя по-разному в зависимости от того, реализован ли drawRect:. Например, если вы устанавливаете свойства непосредственно на слое (например, его цвет фона или его радиус угла), эти значения перезаписываются, если у вас есть метод drawRect: даже если он полностью пуст (т. е. даже не вызывает super).