ОС iOS: использование наследник UIView это drawRect:' против делегировать своего слоя 'drawLayer:контекстными:'


у меня есть класс, который является подклассом UIView. Я могу рисовать вещи внутри представления либо путем реализации drawRect метод, или путем реализации drawLayer:inContext: который является делегат метод CALayer.

у меня есть два вопроса:

  1. как решить, какой подход использовать? Есть ли прецедент для каждого из них?
  2. если я реализую drawLayer:inContext:, это называется (и drawRect нет, по крайней мере, насколько можно определить точку останова), даже если я не назначу свое представление как CALayer делегата с помощью:

    [[self layer] setDelegate:self];

    как вызвать метод делегата, если мой экземпляр не определен как делегат слоя? а какой механизм мешает drawRect от того, чтобы называться если drawLayer:inContext: называется?

6 70

6 ответов:

как решить, какой подход использовать? Есть ли прецедент для каждого из них?

всегда использовать 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().

drawRect должны быть реализованы только тогда, когда это абсолютно необходимо. Реализация по умолчанию drawRect включает в себя ряд интеллектуальных оптимизаций, таких как интеллектуальное кэширование рендеринга представления. Переопределение его обходит все эти оптимизации. Это плохо. Использование методов рисования слоев эффективно почти всегда будет превосходить пользовательский drawRect. Apple использует UIView как делегат для A CALayer часто - на самом деле, каждый 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).