Вращение UIControl с CAGradientLayer неправильно обновляется Swift

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

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

Теперь проблема в том, что элемент управления ведет себя не совсем так, как вид при вращении — позвольте мне показать вам снимок экрана для контекста: Вращение UIControl с CAGradientLayer неправильно обновляется Swift

Это среднее вращение, но его почти видно глазу — изображение показывает, что Градиент составляет 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
    })
}

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

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

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

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

Ваши рассуждения о создании подкласса UIControl довольно странные.

Elhoej 20.04.2019 13:29

Вы можете добавить градиент к UIButton? Есть пример?

stevenpcurtis 21.04.2019 01:36

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

Elhoej 21.04.2019 15:55

пример: gist.github.com/Elhoej/1a46f9f504afb18b35809099aa937af3

Elhoej 21.04.2019 16:04

Вы также можете добавить тени к UIButtons и включить/отключить их, чтобы пользователи не рассылали их спамом.

Elhoej 21.04.2019 16:05

Итак, в чем преимущество использования UIButton над UIControl? Включение/отключение в моей реализации также меняет цвет и метку, а также я реализовал UIActivityControl...

stevenpcurtis 22.04.2019 03:07

Ну, мне кажется, вы пытаетесь создать кнопку (с некоторыми настраиваемыми функциями), так зачем подкласс UIControl? Опять же, ваши рассуждения не имеют смысла... Вы могли бы также переопределить enable/disable и поместить функцию didSet для изменения цвета/метки. Вы также можете добавить UIActivityControl в UIButton.

Elhoej 23.04.2019 08:03

Это не по теме. Я не обязан оправдывать то, что здесь подклассифицировано, поскольку это не является частью вопроса. Вы говорите: «Ваши рассуждения не имеют смысла». Хорошо. У вас есть ответ на актуальный вопрос?

stevenpcurtis 23.04.2019 08:11

Я только что ответил на ваш вопрос в комментариях :)

Elhoej 23.04.2019 08:13

Чтобы быть более ясным, UIButton имеет множество встроенных функций, которых нет у UIControl.

Elhoej 23.04.2019 08:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
10
380
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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)
    }
}

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