I'm new to programming - but I've made strides learning Swift for iOS in the last two months. I'm making a simple typing game - and the way I've structured my project is that I have a hidden UITextView
that detects the character pressed by the player, and I match that character string with a visible UITextView
's character string.
What I'm looking to do now is to add some sort of animation - I'd like the individual letters to fade in/out
I've created an attributed string, added it to a UITextView
, and I just can't figure out a way to animate a specific range in the string. I've tried using something along the lines of this:
UIView.animateWithDuration(1, delay: 0.5, options: .CurveEaseOut, animations: {
self.stringForPlayer.addAttribute(
NSForegroundColorAttributeName,
value: UIColor.greenColor(),
range: NSRange(location: self.rangeOfText, length: 1))
self.textViewForPlayer.attributedText = self.textForPlayer
}, completion: { finished in
println("FINISHED")
})
with no luck. I figure maybe UIView
animations are only on the view object itself and can't be used on the attributed string. Any ideas or even workarounds to make this happen? Any help is appreciated, thanks a lot!
You can use transition(with:...)
to do an animation. In this case, fading the word ipsum
into green. E.g. in Swift 3 and later:
let range = (textView.text as NSString).range(of: "ipsum")
if range.location == NSNotFound { return }
let string = textView.attributedText.mutableCopy() as! NSMutableAttributedString
string.addAttribute(.foregroundColor, value: UIColor.green, range: range)
UIView.transition(with: textView, duration: 1.0, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
})
Originally, you also asked about having the text grow and shrink during this animation and that’s more complicated. But you can search for the text, find the selectionRects
, take snapshots of these views, and animate their transform
. For example:
func growAndShrink(_ searchText: String) {
let beginning = textView.beginningOfDocument
guard
let string = textView.text,
let range = string.range(of: searchText),
let start = textView.position(from: beginning, offset: string.distance(from: string.startIndex, to: range.lowerBound)),
let end = textView.position(from: beginning, offset: string.distance(from: string.startIndex, to: range.upperBound)),
let textRange = textView.textRange(from: start, to: end)
else {
return
}
textView.selectionRects(for: textRange)
.forEach { selectionRect in
guard let snapshotView = textView.resizableSnapshotView(from: selectionRect.rect, afterScreenUpdates: false, withCapInsets: .zero) else { return }
snapshotView.frame = view.convert(selectionRect.rect, from: textView)
view.addSubview(snapshotView)
UIView.animate(withDuration: 1, delay: 0, options: .autoreverse, animations: {
snapshotView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}, completion: { _ in
snapshotView.removeFromSuperview()
})
}
}
And
growAndShrink("consectetaur cillium”)
Will result in:
If you are animating just the color of the text, you may want to fade it to clear
before fading it to the desired color (making it "pop" a little more), you could use the completion
block:
func animateColor(of searchText: String) {
let range = (textView.text as NSString).range(of: searchText)
if range.location == NSNotFound { return }
let string = textView.attributedText.mutableCopy() as! NSMutableAttributedString
string.addAttribute(.foregroundColor, value: UIColor.clear, range: range)
UIView.transition(with: textView, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
}, completion: { _ in
string.addAttribute(.foregroundColor, value: UIColor.red, range: range)
UIView.transition(with: self.textView, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.textView.attributedText = string
})
})
}
Resulting in:
For previous versions of Swift, see prior revision of this answer.