UIView Layer Mask Анимировать

Я пытаюсь анимировать слой маски на UIView.

В основном этот код отображает изображение ниже:

let bounds: CGRect = self.manualWBMaskView!.bounds
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.fillColor = UIColor.blackColor().CGColor
let screenWith  : CGFloat = UIScreen.mainScreen().bounds.width
let roundedRectFrame : CGRect = CGRectMake(self.manualWBMaskView!.bounds.midX - (screenWith/4), self.manualWBMaskView!.bounds.midY - (screenWith/4), screenWith/2, screenWith/2)
let path: UIBezierPath = UIBezierPath(roundedRect:roundedRectFrame, cornerRadius:10  )
path.appendPath(UIBezierPath(rect: bounds))
maskLayer.path = path.CGPath
maskLayer.fillRule = kCAFillRuleEvenOdd
self.manualWBMaskView!.layer.mask = maskLayer

Я хочу, чтобы он анимировал эту позицию выреза из полноэкранного в верхнее положение:

Я попробовал анимацию UIView, и не повезло. Так как у меня уже есть CAShapeLayer, я должен быть в состоянии оживить это?

РЕДАКТИРОВАТЬ**

Вот код анимации, который я пробовал, который не работал:

 //Before Animation Make the Mask fill the whole view:
    self.manualWBMaskView!.layer.mask = self.manualWBMaskView!.bounds

    var duration: NSTimeInterval = 0.8

    CATransaction.begin()
    CATransaction[kCATransactionAnimationDuration] = Int(duration)
    self.manualWBMaskView!.layer.mask = CGRectMake(self.manualWBMaskView!.bounds.midX - (screenWith/4), self.manualWBMaskView!.bounds.midY - (screenWith/4), screenWith/2, screenWith/2)
    CATransaction.commit()

Согласно ответу 'originaluser2 ниже, у меня есть это:

func presentMaskScreenWithAnimation () {
                let bounds: CGRect = self.manualWBMaskView!.bounds
                let maskLayer: CAShapeLayer = CAShapeLayer()
                maskLayer.frame = bounds
                maskLayer.fillColor = UIColor.blackColor().CGColor
                let screenWith  : CGFloat = UIScreen.mainScreen().bounds.width
                let roundedRectFrame : CGRect = self.manualWBMaskView.bounds
                let path: UIBezierPath = UIBezierPath(roundedRect:roundedRectFrame, cornerRadius:0  )
                path.appendPath(UIBezierPath(rect: bounds))
                maskLayer.path = path.CGPath
                maskLayer.fillRule = kCAFillRuleEvenOdd
                self.manualWBMaskView!.layer.mask = maskLayer

       // define your new path to animate the mask layer to
        let screenWith2  : CGFloat = UIScreen.mainScreen().bounds.width
        let roundedRectFrame2 : CGRect = CGRectMake(self.manualWBMaskView!.bounds.midX - (screenWith2/4), self.manualWBMaskView!.bounds.midY - (screenWith2/4), screenWith2/2, screenWith2/2)

        let path2 = UIBezierPath(roundedRect:roundedRectFrame2, cornerRadius:10  )

        // create new animation
        let anim = CABasicAnimation(keyPath: "path")    

        // from value is the current mask path
        anim.fromValue = maskLayer.path

        // to value is the new path
        anim.toValue = path2.CGPath

        // duration of your animation
        anim.duration = 5.0

        // custom timing function to make it look smooth
        anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

        // add animation
        maskLayer.addAnimation(anim, forKey: nil)

        // update the path property on the mask layer, using a CATransaction to prevent an implicit animation
        CATransaction.begin()
        CATransaction.setDisableActions(true)
           maskLayer.path = path2.CGPath
           maskLayer.fillRule = kCAFillRuleEvenOdd
        CATransaction.commit()

}

Делает анимацию; Тем не менее, у меня есть две проблемы: 1. Форма отключена во время анимации, но все же в конце анимации. (Скриншот ниже)

  1. Маска перевернута. Она видна внутри фигуры, но прозрачна вокруг нее. Мне нужно наоборот. Внутри форма прозрачная.

1 ответ

Решение

Вы хотите использовать CABasicAnimation на path свойство вашего слоя маски

Это позволит вам анимировать вашу маску с данного пути на другой данный путь. Базовая анимация автоматически интерполирует промежуточные шаги для вас.

Нечто подобное должно сработать:

// define your new path to animate the mask layer to
let path = UIBezierPath(roundedRect: CGRectInset(view.bounds, 100, 100), cornerRadius: 20.0)
path.appendPath(UIBezierPath(rect: view.bounds))

// create new animation
let anim = CABasicAnimation(keyPath: "path")

// from value is the current mask path
anim.fromValue = maskLayer.path

// to value is the new path
anim.toValue = path.CGPath

// duration of your animation
anim.duration = 5.0

// custom timing function to make it look smooth
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

// add animation
maskLayer.addAnimation(anim, forKey: nil)

// update the path property on the mask layer, using a CATransaction to prevent an implicit animation
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.path = path.CGPath
CATransaction.commit()

Результат:

введите описание изображения здесь

Стоит отметить, что в Core Animation есть ошибка, из-за которой невозможно правильно анимировать путь, включающий прямоугольник, в путь, включающий скругленный прямоугольник с радиусом угла. Я подал отчет об ошибке в Apple для этого.

Для этого есть простое исправление, заключающееся в использовании закругленного прямоугольника с угловым радиусом пренебрежимо малого значения на исходном пути (для меня работает 0,0001). Немного хакерский - но вы не можете заметить разницу при использовании приложения.


Сообщение об ошибке от Apple

К сожалению, Apple считает это "ожидаемым поведением". Они сказали следующее:

Чтобы анимация отображалась правильно, анимированные пути должны иметь одинаковое количество элементов / сегментов (в идеале, одного типа). В противном случае, нет разумного способа интерполировать между ними.

Например, если первый путь имеет четыре элемента, а второй путь имеет шесть элементов, мы можем легко создать первые четыре точки, усредняя первые четыре начальные и конечные точки с весом для текущего времени, но невозможно придумать универсальный способ расчета промежуточных позиций для пятого и шестого баллов. Вот почему анимация прекрасно работает для углового радиуса 0,001 (такое же количество элементов), но не работает для простого прямоугольного пути (различное количество элементов).

Лично я не согласен, если вы укажете скругленный прямоугольник с радиусом угла 0, а затем анимируете с закругленным прямоугольником с радиусом угла> 0 - неправильная анимация все равно получается, даже если оба пути должны иметь "одинаковое количество элементов". Я подозреваю, что причина, по которой это не работает, заключается в том, что внутренняя реализация проверяет радиус 0 и пытается оптимизировать, используя вместо этого прямоугольный путь с 4 элементами, поэтому он не работает.

Я предоставлю еще одно сообщение об ошибке, объясняющее это, когда у меня будет время.


Пара других вещей...

Ты силой разворачиваешь свой manualWBMaskView немного. Не. Вам нужно научиться правильно обращаться с опциями. Каждый раз, когда вы заставляете что-то развернуть, ваше приложение может зависнуть.

У вас также есть несколько строк, как это:

let maskLayer: CAShapeLayer = CAShapeLayer()

: CAShapeLayer здесь не только нет необходимости, но и идет вразрез с хорошей практикой. Вы должны всегда позволять Swift выводить тип значения, где это возможно.

Другие вопросы по тегам