Search code examples
iosswiftstringanimationuitextview

Animating strings fading in/out in Swift


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!


Solution

  • 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:

    grow

    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:

    red

    For previous versions of Swift, see prior revision of this answer.