Подтвердить что ты не робот

IOS: использование UIView 'drawRect:' против своего делегата слоя 'drawLayer: inContext:'

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

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

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

    [[self layer] setDelegate:self];

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

4b9b3361

Ответ 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: метод для рисования вашего контента. Руководство по программированию основной анимации