Почему ранее не был вызван метод рисования пользовательского CALayer?

Проблема

Я запускаю случайную анимацию для выбора UIViews с помощью таймера. Все анимации работают, как задумано, за исключением одной, в которой метод рисования CALayer вызывается после выхода из селектора таймера.

Подробное описание

Для ясности и упрощения позвольте мне схематизировать выполняемые действия.

Цикл

Все созданные мной анимации работают так, как задумано: они представляют собой комбинацию CABasicAnimations на существующих подпредставлениях / подслоях или новых, добавленных в выбранную иерархию представлений. Они закодированы как расширение UIView, поэтому их можно вызывать в любых представлениях, независимо от представления или контроллера представления, в котором оно находится. Что ж, все работают, кроме одного.

Я действительно создал собственный класс CALayer, который состоит в рисовании шаблонов на CALayer. В расширении этого настраиваемого класса есть метод для анимации этих шаблонов (см. Ниже код). В общем, когда я дойду до шага / метода animate selected view и запускаю эту конкретную анимацию, вот что должно произойти:

  • вызывается метод с именем animatePattern
  • этот метод добавляет настраиваемый класс CALayer к выбранному представлению, а затем вызывает расширение анимации этого слоя

Проблема: если со всеми другими анимациями все рисунки выполняются до выхода animate selected view шага / метода, в этом конкретном случае метод рисования настраиваемого класса CALayer вызывается после выход из метода performAnimation, что, в свою очередь, приводит к сбою анимации.

Я должен добавить, что я пробовал анимацию пользовательского класса CALayer на отдельной упрощенной игровой площадке, и она хорошо работает (я добавляю пользовательский слой к UIView в методе viewDidLoad UIViewController, а затем вызываю анимацию слоя в методе viewDidAppear UIViewController.)

Код

  • метод, вызываемый animate selected view шагом / методом:

    func animatePattern(for duration: TimeInterval = 1) {
    
    let patternLayer        = PatternLayer(effectType: .dark)
    patternLayer.frame      = self.bounds
    self.layer.addSublayer(patternLayer)
    patternLayer.play(for: duration)
    
    }
    

(обратите внимание, что этот метод находится в расширении UIView, поэтому self здесь представляет UIView, на котором была вызвана анимация)

  • упрощенный пользовательский класс CALayer:

    override var bounds: CGRect {
        didSet {
            self.setNeedsDisplay()
        }
    
    
    // Initializers
    override init() {
        super.init()
        self.setNeedsDisplay()
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    init(effectType: EffectType){
        super.init()
        // sets various properties
        self.setNeedsDisplay()
    }
    
    
    // Drawing
    override func draw(in ctx: CGContext) {
        super.draw(in: ctx)
        guard self.frame        != CGRect.zero else { return }
    
        self.masksToBounds      = true
    
        // do the drawings
    }
    
  • расширение анимации пользовательского класса CALayer:

     func play(for duration: Double, removeAfterCompletion: RemoveAfterCompletion = .no) {
    
        guard self.bounds != CGRect.zero else {
            print("Cannot animate nil layer")
            return }
    
        CATransaction.begin()
    
        CATransaction.setCompletionBlock {
    
            if removeAfterCompletion.removeStatus {
                if case RemoveAfterCompletion.yes = removeAfterCompletion { self.fadeOutDuration = 0.0 }
                self.fadeToDisappear(duration: &self.fadeOutDuration)
            }
        }
    
    
        // perform animations
    
        CATransaction.commit()
    }
    

Попыток на данный момент

Я попытался принудительно нарисовать слой, вставив setNeedsDisplay / setNeedsLayout в разные места кода, но это не работает: отладка метода draw пользовательского класса CALayer постоянно достигается после выхода из метода performAnimation, в то время как его следует вызывать, когда рамка слоя модифицируется методом animatePattern. Я должен пропустить что-то совершенно очевидное, но в настоящее время я бегаю по кругу, и я был бы признателен за свежий взгляд на это.

Заранее благодарим вас за то, что уделили время рассмотрению этого вопроса! Лучший,


person Chris    schedule 11.02.2019    source источник
comment
Для слоя рисования вы можете привести пример анимации, которую вы выполняете, где вы // выполняете анимацию   -  person agibson007    schedule 12.02.2019


Ответы (2)


Вы переопределяете рисование (_ context), поскольку UIView может быть делегатом CALayer.

 UIView: {

    var patternLayer :  PatternLayer

     func animatePattern(for duration: TimeInterval = 1) {

      patternLayer      = PatternLayer(effectType: .dark)
       patternLayer.frame      = self.bounds
       self.layer.addSublayer(patternLayer)
     }

      func draw(_ layer: CALayer, in ctx: CGContext){
        layer.draw(ctx)
         if  NECESSARY { patternLayer.play(for: duration)}

        }

      }
person E.Coms    schedule 11.02.2019
comment
Спасибо, что нашли время предложить решение! Насколько я понимаю вашу идею, я отменяю draw(_context:) в представлении, на которое распространяется анимация. Если это так, то это решение не может работать, потому что анимация применяется к случайному выбору представлений, а не к одному в частности. Когда таймер достигает определенного времени, он запускает некоторую анимацию в некоторых представлениях, одно из которых patternLayer. - person Chris; 11.02.2019
comment
Это НЕОБХОДИМО исходить из. Идея состоит в том, чтобы гарантировать вызов анимации после рисования слоя с некоторыми условиями. displayIfNeeded () здесь тоже хорош. - person E.Coms; 11.02.2019
comment
Еще раз спасибо :) но я не могу полностью понять вашу идею ... Вы предлагаете добавить этот метод в расширение UIView? Кроме того, эта анимация может быть не той, которая выбрана для запуска в первую очередь (существует случайный выбор вида и применяемой анимации). - person Chris; 11.02.2019
comment
Нет. Я имею в виду, что твой ответ patternLayer.displayIfNeeded() прямо здесь. - person E.Coms; 11.02.2019

Как это часто бывает, когда вы находите время, чтобы записать свою проблему, начинают появляться новые идеи. Фактически, я вспомнил, что self.setNeedsDisplay() информирует систему о том, что слой необходимо (пере) отрисовать, но не (пере) отрисовывает его сразу: это будет сделано во время следующего обновления. Кажется, что обновление происходит после окончания цикла для этой конкретной анимации. Чтобы решить эту проблему, я сначала добавил вызов метода display сразу после установки границ patternLayer, и он сработал, но, учитывая комментарий @Matt, я изменил решение, вызвав вместо этого метод displayIfNeeded. Это тоже работает.

func animatePattern(for duration: TimeInterval = 1) {

let patternLayer        = PatternLayer(effectType: .dark)
patternLayer.frame      = self.bounds
self.layer.addSublayer(patternLayer)

// asks the system to draw the layer now
patternLayer.displayIfNeeded()

patternLayer.play(for: duration)

}

Опять же, если кто-то предложит другое более элегантное решение / объяснение, пожалуйста, не воздерживайтесь от того, чтобы поделиться!

person Chris    schedule 11.02.2019
comment
Первое предложение в документации для display: «Не вызывайте этот метод напрямую». - person matt; 11.02.2019
comment
Я вызываю не метод display CALayer, а метод display подкласса. Но я бы хотел услышать вашу идею, как решить проблему иначе :) - person Chris; 11.02.2019
comment
Я не читал ваш вопрос глубоко, но, если я правильно понимаю, я справляюсь с подобными вещами, вводя небольшую задержку, чтобы позволить циклу выполнения завершиться. - person matt; 11.02.2019
comment
Я просто очень кратко изложу DispatchQueue.main.asyncAfter последовательность событий. - person matt; 11.02.2019