Вращающийся UIControl с CAGradientLayer не обновляется правильно Swift - PullRequest
3 голосов
/ 10 апреля 2019

Вместо использования обычной кнопки я вложил в подкласс UIControl, потому что мне нужно было добавить к нему градиент.У меня также есть способ добавить тень и индикатор активности (не виден на изображении ниже) в качестве кнопки с отслеживанием состояния, чтобы пользователи не нажимали кнопку, если (например) выполняется вызов API.

Этобыло действительно сложно попытаться заставить UIControl вращаться, и чтобы сделать это, я добавил тень как отдельное представление к представлению контейнера, содержащему UIControl, чтобы тень могла быть добавлена.

Теперь проблема в том, что элемент управления не ведет себя совсем как представление о вращении - позвольте мне показать вам скриншот для контекста: enter image description here

Это середина вращения, но это простоо видимом для глаза - изображение показывает, что Градиент составляет 75% от длины синего UIView на изображении.

https://github.com/stevencurtis/statefulbutton

ВЧтобы выполнить это вращение, я удаляю shadowview, а затем изменяю рамку градиентной рамки на ее границы, и в этом проблема.

func viewRotated() {
    CATransaction.setDisableActions(true)

    shadowView!.removeFromSuperview()
    shadowView!.frame = self.frame
    shadowView!.layer.masksToBounds = false
    shadowView!.layer.shadowOffset = CGSize(width: 0, height: 3)
    shadowView!.layer.shadowRadius = 3
    shadowView!.layer.shadowOpacity = 0.3
    shadowView!.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 20, height: 20)).cgPath
    shadowView!.layer.shouldRasterize = true
    shadowView!.layer.rasterizationScale = UIScreen.main.scale

    self.gradientViewLayer.frame = self.bounds
    self.selectedViewLayer.frame = self.bounds

    CATransaction.commit()

    self.insertSubview(shadowView!, at: 0)

}

Так что этот метод вращения вызывается через родительский контроллер представления:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { context in
        context.viewController(forKey: UITransitionContextViewControllerKey.from)
        //inform the loginButton that it is being rotated
        self.loginButton.viewRotated()
    }, completion: { context in
        //  can call here when completed the transition
    })
}

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

Бесполезно предлагать использовать UIButton, чтобы использоватьизображение для градиента (пожалуйста, не предлагайте использовать изображение градиента в качестве фона для UIButton, я пробовал это) или стороннюю библиотеку.Это моя работа, она работает, но не работает для меня приемлемо, и я хочу, чтобы она работала так же, как и обычный взгляд (или, по крайней мере, знаю, почему нет).Я пробовал и другие решения выше, и пошел на свой UIControl.Я знаю, что могу заблокировать представление, если есть вызов API, или использовать другие способы, чтобы пользователь не нажимал кнопку слишком много раз.Я пытаюсь исправить свое решение, а не придумывать способы обойти эту проблему с помощью CAGradientLayer.

Проблема : мне нужно сделать UIControlView с CAGradientLayerв качестве фона вращайтесь так же, как UIView, и не демонстрируйте проблему, показанную на изображении выше.

Полный пример: https://github.com/stevencurtis/statefulbutton

1 Ответ

6 голосов
/ 16 апреля 2019

Вот рабочий код:

https://gist.github.com/alldne/22d340b36613ae5870b3472fa1c64654

Вот мои рекомендации к вашему коду:

1.Правильное место для настройки размера и положения подслоев

Размер вида, а именно вашей кнопки, определяется после выполнения макета.Что вам нужно сделать, это просто установить правильный размер подслоев после макета.Поэтому я рекомендую вам установить размер и положение подслоев градиента в layoutSubviews.

    override func layoutSubviews() {
        super.layoutSubviews()

        let center = CGPoint(x: self.bounds.width / 2, y: self.bounds.height / 2)
        selectedViewLayer.bounds = self.bounds
        selectedViewLayer.position = center
        gradientViewLayer.bounds = self.bounds
        gradientViewLayer.position = center
    }

2.Вам не нужно использовать дополнительный вид для рисования тени

Удалить shadowView и просто установить свойства слоя:

layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3
layer.shadowOpacity = 0.3
layer.shadowColor = UIColor.black.cgColor
clipsToBounds = false

Если вам нужно использовать дополнительный вид для рисования тени, затем вы можете добавить представление один раз в init() и установить правильный размер и положение в layoutSubviews, или вы можете просто программно установить автоматические ограничения макета для суперпредставления.

3.Функция продолжительности и времени анимации

После установки правильных размеров анимация слоев градиента и вида контейнера не синхронизируется.

Кажется, что:

  • Во время ротационного перехода координатор (UIViewControllerTransitionCoordinator) имеет собственную продолжительность перехода и функцию замедления.
  • А функция продолжительности и замедления автоматически применяется ко всем подпредставлениям (UIView).
  • Однако эти значения не применяются к CALayer без связанного UIView.Следовательно, он использует функцию синхронизации по умолчанию и длительность CoreAnimation.

Для синхронизации анимаций явно установите продолжительность анимации и функцию синхронизации, как показано ниже:

class ViewController: UIViewController {
    ...

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        CATransaction.setAnimationDuration(coordinator.transitionDuration)
        CATransaction.setAnimationTimingFunction(coordinator.completionCurve.timingFunction)
    }

    ...
}

// Swift 4
extension UIView.AnimationCurve {
    var timingFunction: CAMediaTimingFunction {
        let functionName: CAMediaTimingFunctionName
        switch self {
        case .easeIn:
            functionName = kCAMediaTimingFunctionEaseIn as CAMediaTimingFunctionName
        case .easeInOut:
            functionName = kCAMediaTimingFunctionEaseInEaseOut as CAMediaTimingFunctionName
        case .easeOut:
            functionName = kCAMediaTimingFunctionEaseOut as CAMediaTimingFunctionName
        case .linear:
            functionName = kCAMediaTimingFunctionLinear as CAMediaTimingFunctionName
        }
        return CAMediaTimingFunction(name: functionName as String)
    }
}
...