Кажется, что когда пользователь нажимает второй раз, в основном одновременно начинают происходить 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(...
, как и сейчас. И вы расширяете свой класс для поддержки делегата для аниматора, и в обоих методах просто обновляете свое текстовое поле или любой используемый вами текстовый контейнер. В качестве альтернативы вы можете просто поместить этот код в свой класс и использовать его напрямую, как вы, кажется, делали до сих пор.