Буквенная анимация с полем uitext при выполнении жеста касания выполняется одновременно - PullRequest
0 голосов
/ 29 августа 2018

новый для переполнения стека и относительно новый для быстрой работы Я пытаюсь выполнить посимвольную анимацию при касании экрана, но буквы появляются зашифрованными при многократном касании экрана.

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

   @objc func handleTap(sender: UITapGestureRecognizer? = nil) {



    if counter < textArray.count {
        storyTextView.text = textArray[counter] //
        storyTextView.animate(newText: storyTextView.text ?? textArray[counter], characterDelay: 0.1)
    counter += 1


    }
}

extension UITextView {

    func animate(newText: String, characterDelay: TimeInterval) {

    DispatchQueue.main.sync {

        self.text = ""

        for (index, character) in newText.characters.enumerated() {

            DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
                self.text?.append(character) // animation function is running at same time

                print("characterDelay \(characterDelay) index \(index)")
            }
        }
    }
}

1 Ответ

0 голосов
/ 29 августа 2018

Кажется, что когда пользователь нажимает второй раз, в основном одновременно начинают происходить 2 анимации. Поскольку вы используете 2 разных текста в качестве источника и добавляете символ за символом для создания новой строки, вы, вероятно, получите из нее чередующуюся строку.

Вы не указываете, каким будет желаемый результат, когда пользователь нажимает второй раз. Проще всего было бы просто завершить текущую анимацию и заполнить всю текущую строку. Сложнее всего было бы оживить оба одновременно, о чем я даже не хотел бы углубляться в размышления в данный момент.

Я считаю, что в вашем случае было бы лучше использовать таймер, чем отправку. Вы можете создать что-то вроде:

let timer = Timer.scheduledTimer(withTimeInterval: characterDelay, repeats: true, block: {{ _ in
     // Append the character here
}})

Было бы также лучше, вместо индексации, исчерпать вашу строку, выполнив что-то вроде:

let newText = oldText + String(textToAppend.remove(at: textToAppend.startIndex))

Таким образом, это удалит первый символ из исходной строки и в то же время добавит его в целевую строку.

Теперь, чтобы собрать все воедино, я бы сделал следующее:

protocol StringAnimatorDelegate: class {
    func stringAnimator(_ sender: StringAnimator, didUpdateStringTo string: String)
    func stringAnimator(_ sender: StringAnimator, didFinishWithString string: String)
}

class StringAnimator {

    var delegate: StringAnimatorDelegate?

    private(set) var text: String = ""
    private var timerWithString: (timer: Timer, stringToAppend: String)?

    func animateText(_ stringToAppend: String, intervalDuration: TimeInterval) {
        if let timerWithString = timerWithString {
            self.timerWithString = nil
            // We have a string already to append. Finish it
            timerWithString.timer.invalidate() // Stop the previous timer
            self.text = self.text + timerWithString.stringToAppend // Append whatever is left to append
            self.delegate?.stringAnimator(self, didFinishWithString: self.text)
        }

        // Create a new timer
        let timer = Timer.scheduledTimer(withTimeInterval: intervalDuration, repeats: true) { timer in
            guard let string = self.timerWithString?.stringToAppend, string.count > 0 else {
                // String is either nil or depleted. Finish timer.
                self.timerWithString?.timer.invalidate()
                self.timerWithString = nil
                self.delegate?.stringAnimator(self, didFinishWithString: self.text)
                return
            }
            var depletingString = string // Make a mutable copy of string to deplete so we may modify it
            self.text = self.text + String(depletingString.remove(at: depletingString.startIndex)) // Modify both strings
            self.timerWithString = (timer, depletingString) // Assign new values
            self.delegate?.stringAnimator(self, didUpdateStringTo: self.text)
        }
        self.timerWithString = (timer, stringToAppend)
    }

}

Теперь это целая система для анимации строки. Вы можете создать экземпляр как:

let animator = StringAnimator()
animator.delegate = self

Затем, например, при нажатии вы просто набираете animator.animateText(..., как и сейчас. И вы расширяете свой класс для поддержки делегата для аниматора, и в обоих методах просто обновляете свое текстовое поле или любой используемый вами текстовый контейнер. В качестве альтернативы вы можете просто поместить этот код в свой класс и использовать его напрямую, как вы, кажется, делали до сих пор.

...