IOS: использование UIView 'drawRect:' против своего делегата слоя 'drawLayer: inContext:'
У меня есть класс, который является подклассом UIView
. Я могу нарисовать материал внутри представления либо путем реализации метода drawRect
, либо путем реализации drawLayer:inContext:
, который является методом делегирования CALayer
.
У меня есть два вопроса:
- Как решить, какой подход использовать? Есть ли вариант использования для каждого из них?
-
Если я реализую drawLayer:inContext:
, он вызывается (и drawRect
не является, по крайней мере, насколько может сказать точка останова), даже если я не назначаю свое представление как CALayer
делегат, используя:
[[self layer] setDelegate:self];
Как вызывается метод делегата, если мой экземпляр не определен как делегат слоя? и какой механизм предотвращает вызов drawRect
при вызове drawLayer:inContext:
?
Ответы
Ответ 1
Как решить, какой подход использовать? Есть ли вариант использования для каждого из них?
Всегда используйте drawRect:
и никогда не используйте UIView
в качестве делегата чертежа для любого CALayer
.
Как вызывается метод делегата, если мой экземпляр не определен как делегат слоя? и какой механизм запрещает вызов drawRect при вызове drawLayer:inContext:
?
Каждый экземпляр UIView
является делегатом чертежа для его поддержки CALayer
. Вот почему [[self layer] setDelegate:self];
, казалось, ничего не делал. Это избыточно. Метод drawRect:
является эффективным методом делегирования чертежа для слоя представления. Внутри UIView
реализует drawLayer:inContext:
, где он выполняет некоторые свои действия, а затем вызывает drawRect:
. Вы можете увидеть его в отладчике:
![drawRect: stacktrace]()
Вот почему drawRect:
никогда не вызывался при реализации drawLayer:inContext:
. Также почему вы никогда не должны реализовывать какой-либо из методов делегата CALayer
в пользовательском подклассе UIView
. Вы также никогда не должны делать вид делегата чертежа для другого слоя. Это вызовет всевозможные глупости.
Если вы реализуете drawLayer:inContext:
, потому что вам нужно получить доступ к CGContextRef
, вы можете получить это изнутри своего drawRect:
, вызвав UIGraphicsGetCurrentContext()
.
Ответ 2
drawRect
должен быть реализован только при необходимости. Реализация по умолчанию drawRect
по умолчанию включает в себя ряд интеллектуальных оптимизаций, таких как интеллектуальное кэширование рендеринга представления. Преодоление его обходит все эти оптимизации. Это плохо. Использование методов рисования слоев практически всегда превосходит пользовательский drawRect
. Apple часто использует UIView
в качестве делегата для CALayer
- на самом деле, каждый UIView является делегатом этого слоя. Вы можете увидеть, как настроить чертеж слоя внутри UIView в нескольких образцах Apple, включая (в это время) ZoomingPDFViewer.
Хотя использование drawRect
является общим, это практика, которая была обескуражена, по крайней мере, с 2002/2003 года, IIRC. Не так много причин, чтобы пойти по этому пути.
Расширенная оптимизация производительности на iPhone OS (слайд 15)
Основные основы анимации
Понимание рендеринга UIKit
Техническое Q & A QA1708: Улучшение производительности рисования изображений на iOS
Просмотр руководства по программированию: оптимизация чертежа чертежей
Ответ 3
Вот коды примера 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
{
...
}
Ответ 4
Используете ли вы drawLayer(_:inContext:)
или drawRect(_:)
(или оба) для пользовательского кода чертежа, зависит от того, нужен ли вам доступ к текущему значению свойства слоя во время его анимации.
Сегодня я боролся с различными проблемами рендеринга, связанными с этими двумя функциями при реализации моего собственного класса Label. После проверки документации, выполнения пробных ошибок, декомпиляции UIKit и проверки примера Apple Custom Animatable Properties я получил хорошее представление о том, как он работает.
drawRect(_:)
Если вам не нужно получать доступ к текущему значению свойства layer/view во время его анимации, вы можете просто использовать 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
не возвращает правильное (то есть текущее) значение, пока анимация находится в полете. Вместо этого он возвращает окончательное значение (т.е. Значение для завершения анимации).
Чтобы получить значение current во время анимации, вы должны получить доступ к 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.
}
Ответ 5
В iOS перекрытие между представлением и его слоем очень велико. По умолчанию представление является делегатом его уровня и реализует метод layer drawLayer:inContext:
. Насколько я понимаю, drawRect:
и drawLayer:inContext:
более или менее эквивалентны в этом случае. Возможно, реализация по умолчанию drawLayer:inContext:
вызывает drawRect:
или drawRect:
вызывается только в том случае, если drawLayer:inContext:
не реализован вашим подклассом.
Как решить, какой подход использовать? Есть ли вариант использования для каждого из них?
Это не имеет большого значения. Чтобы следовать соглашению, я обычно использовал бы drawRect:
и зарезервировал использование drawLayer:inContext:
, когда мне действительно нужно нарисовать пользовательские подслои, которые не являются частью представления.
Ответ 6
Apple Documentation говорит следующее:" Существуют также другие способы предоставления контента просмотров, например, установка содержимого базового слой, но переопределение метода drawRect: самый распространенный метод.
Но это не входит в подробности, так что это должно быть ключом: не делайте этого, если вы действительно не хотите, чтобы ваши руки были грязными.
Делитель уровня UIView указывает на UIView. Тем не менее, UIView ведет себя по-разному в зависимости от того, реализована ли drawRect:. Например, если вы устанавливаете свойства на слое напрямую (например, его цвет фона или его радиус угла), эти значения перезаписываются, если у вас есть метод drawRect: даже если он полностью пуст (т.е. Даже не вызывает супер).
Ответ 7
Для слоистых представлений с пользовательским содержимым вы должны продолжать переопределять методы представлений для рисования. Представление на уровне слоя автоматически делает себя делегатом своего уровня и реализует необходимые методы делегата, и вы не должны изменять эту конфигурацию. Вместо этого вы должны реализовать свои представления drawRect: метод для рисования вашего контента. Руководство по программированию основной анимации