Отправка семафора для пользовательского интерфейса

#ios #swift #asynchronous #dispatchsemaphore

Вопрос:

Я хотел оживить альфу uiviews и, похоже, не могу этого сделать. Он ведет себя странно и сообщает об ошибке, что запуск изменений пользовательского интерфейса не рекомендуется запускать в фоновом потоке, но я не знаю, как заставить его работать в основном потоке. Кто-нибудь может мне помочь? Я считаю, что он пропускает первый блок UIView.animate и выполняет то, что во втором, без какой-либо анимации вообще.

функция аниматесемафора() {

 circleRed.alpha = 0.2
circleOrange.alpha = 0.2
circleGreen.alpha = 1

let dispatchSemaphore = DispatchSemaphore(value: 0)
let dispatchQueue = DispatchQueue.global(qos: .background)

dispatchQueue.async {
    UIView.animate(withDuration: 0.5, delay: 5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 1
        self.circleGreen.alpha = 0.2
    } completion: { (_) in
        print("1")
        dispatchSemaphore.signal()
    }
    
    dispatchSemaphore.wait()
    UIView.animate(withDuration: 0.5, delay: 3, options: .curveEaseInOut) {
        self.circleOrange.alpha = 0.2
        self.circleRed.alpha = 1
    } completion: { (_) in
        dispatchSemaphore.signal()
    }
    
    dispatchSemaphore.wait()
    UIView.animate(withDuration: 0.5, delay: 5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 1
    } completion: { (_) in
        dispatchSemaphore.signal()
    }
    
    dispatchSemaphore.wait()
    UIView.animate(withDuration: 0.5, delay: 1, options: .curveEaseInOut) {
        self.circleOrange.alpha = 0.2
        self.circleRed.alpha = 0.2
        self.circleGreen.alpha = 1
    } completion: { (_) in
        self.animateSemaphore()
    }
}
 

}

Ответ №1:

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

 func animateSemaphore() {
    circleRed.alpha = 0.2
    circleOrange.alpha = 0.2
    circleGreen.alpha = 1
        
    UIView.animate(withDuration: 0.5, delay: 5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 1
        self.circleGreen.alpha = 0.2
    } completion: { (_) in
        UIView.animate(withDuration: 0.5, delay: 3, options: .curveEaseInOut) {
            self.circleOrange.alpha = 0.2
            self.circleRed.alpha = 1
        } completion: { (_) in
            UIView.animate(withDuration: 0.5, delay: 5, options: .curveEaseInOut) {
                self.circleOrange.alpha = 1
            } completion: { (_) in
                UIView.animate(withDuration: 0.5, delay: 1, options: .curveEaseInOut) {
                    self.circleOrange.alpha = 0.2
                    self.circleRed.alpha = 0.2
                    self.circleGreen.alpha = 1
                } completion: { (_) in 
                }
            }
        }
    }
 }
 

ИЛИ играйте с delay анимацией вместо вложенности

 func animateSemaphore() {

    circleRed.alpha = 0.2
    circleOrange.alpha = 0.2
    circleGreen.alpha = 1

    UIView.animate(withDuration: 0.5, delay: 5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 1
        self.circleGreen.alpha = 0.2
    } completion: { (_) in
        print("1")
      
    }
    
   
    UIView.animate(withDuration: 0.5, delay: 8.5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 0.2
        self.circleRed.alpha = 1
    } completion: { (_) in
        
    }
    
   
    UIView.animate(withDuration: 0.5, delay: 14, options: .curveEaseInOut) {
        self.circleOrange.alpha = 1
    } completion: { (_) in
       
    }
    
  
    UIView.animate(withDuration: 0.5, delay: 15.5, options: .curveEaseInOut) {
        self.circleOrange.alpha = 0.2
        self.circleRed.alpha = 0.2
        self.circleGreen.alpha = 1
    } completion: { (_) in
        
    }
}
 

Комментарии:

1. Я понимаю вашу точку зрения, но я хотел, чтобы это было как-то правильно написано. Нет ли другого способа не записывать все в блок завершения других частей кода? Когда это написано таким образом, это просто ошеломляет и не так легко читается.

2. Из предыдущей анимации. Один завершается, второй запускает 5-секундный таймер, а затем воспроизводится.

3. Я попробовал это, но он пропускает строку 43 до строки 59, а затем 50.

4. Я понимаю, но могу ли я как-то избежать вставки всего в обработчики завершения, или другого пути нет?

Ответ №2:

Вы также можете создать конфигурацию для создания анимации (черновик можно найти здесь). https://gist.github.com/maximkrouk/76163b3f2775dafc73c5633d155368cb И добавьте немного сглаживания

 public struct UIViewAnimation {
    private init(
        provider: UIViewAnimatiorProvider?,
        animations: @escaping () -> Void,
        completion: ((Bool) -> Void)? = nil
    ) {
        self.provider = provider
        self.animations = animations
        self.completion = completion
    }
    
    public init(
        config: UIViewAnimatiorProvider,
        animations: @escaping () -> Void,
        completion: ((Bool) -> Void)? = nil
    ) {
        self.init(
            provider: config,
            animations: animations,
            completion: completion
        )
    }
    
    let provider: UIViewAnimatiorProvider?
    let animations: () -> Void
    let completion: ((Bool) -> Void)?
    
    public func run() {
        if let provider = provider {
            provider.makeAnimator(
                for: animations,
                completion: completion ?? { _ in }
            ).animate()
        } else {
            animations()
            completion?(true)
        }
    }
    
    public func appendingCompletion(_ completion: @escaping (Bool) -> Void) -> UIViewAnimation {
        UIViewAnimation(
            provider: provider,
            animations: animations,
            completion: { isFinished in
                self.completion?(isFinished)
                completion(isFinished)
            }
        )
    }
    
    public static let empty: UIViewAnimation = .init(
        provider: nil,
        animations: {},
        completion: nil
    )
}

extension UIViewAnimation {
    public static func sequence(_ animations: UIViewAnimation...) -> UIViewAnimation {
        guard var animation = animations.last else { return .empty }
        animations.dropLast().reversed().forEach { prevAnimation in
            let animate = animation.run
            animation = prevAnimation.appendingCompletion { _ in animate() }
        }
        return animation
    }
}
 

И используйте его, как

 extension UIView {
    func animateSemaphore() {
        let initialBackground = backgroundColor
        UIViewAnimation.sequence(
            UIViewAnimation(config: .init(duration: 2)) {
                self.backgroundColor = .red
            },
            UIViewAnimation(config: .init(duration: 2)) {
                self.backgroundColor = .green
            },
            UIViewAnimation(config: .init(duration: 2)) {
                self.backgroundColor = .red
            },
            UIViewAnimation(config: .init(duration: 2)) {
                self.backgroundColor = .green
            },
            UIViewAnimation(config: .init(duration: 2)) {
                self.backgroundColor = initialBackground
            }
        ).run()
    }
}

UIView().animateSemaphore()