Search code examples
iosanimationswiftnsmutableattributedstring

Animate a change in part of an NSMutableAttributedString


I'm making an iOS app that has a UITextView. When closing a parenthesis in that UITextView, I want to highlight to the user which opening parenthesis it pairs to. So far I've done this using an NSMutableAttributedString and changing the font size of the paired parentheses, which works but is kind of ugly. What I really want is to animate this similarly to the way xcode does the same thing when I close a parenthesis in my code. Is there any way of doing this?

Any help is greatly appreciated, though I'm fairly new to this, so please don't assume I know too much :)

Here's my code:

    @IBAction func didPressClosingParentheses(sender: AnyObject) {
    appendStringToInputTextView(")")
    var count = 1
    let currentString = inputTextView.attributedText.string
    let characterArray = Array(currentString)
    let closingIndex = characterArray.count - 1
    for i in reverse(0...closingIndex-1) {
        if characterArray[i] == "(" {
            count--
        }
        else if characterArray[i] == ")" {
            count++
        }
        if count == 0 {
            let startingIndex = i
            var newString = NSMutableAttributedString(string: currentString)
            newString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue-Thin", size: 28)!, range: NSMakeRange(0, newString.length))
            newString.addAttribute(NSForegroundColorAttributeName, value: UIColor(red: 243, green: 243, blue: 243, alpha: 1), range: NSMakeRange(0, newString.length))
            newString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue-Thin", size: 35)!, range: NSMakeRange(startingIndex, 1))
            newString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue-Thin", size: 35)!, range: NSMakeRange(closingIndex, 1))


            UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: nil, animations: {
                self.inputTextView.attributedText = newString

            }, nil)

            break
        }
    }
}

As you can see I've tried using the UIView.animateWithDuration to do this, which as I suspected, didn't work.


Solution

  • I achieved what I wanted to by getting the frames for the actual parentheses and creating new UILabels on top of my UITextView and animating those labels.

        @IBAction func didPressClosingParentheses(sender: AnyObject) {
    
        inputTextView.text = inputTextView.text + ")"
        var count = 1
        let currentString = inputTextView.attributedText.string
        let characterArray = Array(currentString)
        let closingIndex = characterArray.count - 1
        for i in reverse(0...closingIndex-1) {
            if characterArray[i] == "(" {
                count--
            }
            else if characterArray[i] == ")" {
                count++
            }
            if count == 0 {
                let startingIndex = i
    
                let openingRange = NSMakeRange(startingIndex, 1)
                let closingRange = NSMakeRange(closingIndex, 1)
    
                var openingFrame = inputTextView.layoutManager.boundingRectForGlyphRange(openingRange, inTextContainer: inputTextView.textContainer)
    
                openingFrame.origin.y += inputTextView.textContainerInset.top
                var openingLabel = UILabel(frame: openingFrame)
                openingLabel.text = "("
                openingLabel.font = UIFont(name: "HelveticaNeue-Thin", size: 28)
                openingLabel.textColor = whiteishColor
                openingLabel.backgroundColor = bluishColor
    
                var closingFrame = inputTextView.layoutManager.boundingRectForGlyphRange(closingRange, inTextContainer: inputTextView.textContainer)
                closingFrame.origin.y += inputTextView.textContainerInset.top
                var closingLabel = UILabel(frame: closingFrame)
                closingLabel.text = ")"
                closingLabel.font = UIFont(name: "HelveticaNeue-Thin", size: 28)
                closingLabel.textColor = whiteishColor
                closingLabel.backgroundColor = bluishColor
    
                inputTextView.addSubview(openingLabel)
                inputTextView.addSubview(closingLabel)
    
                UIView.animateWithDuration(0.4, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: nil, animations: {
                    openingLabel.transform = CGAffineTransformMakeScale(1.25, 1.25)
                    closingLabel.transform = CGAffineTransformMakeScale(1.25, 1.25)
    
                    }, nil)
    
                UIView.animateWithDuration(0.4, delay: 0.2, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.8, options: nil, animations: {
                    openingLabel.transform = CGAffineTransformMakeScale(1.0, 1.0)
                    closingLabel.transform = CGAffineTransformMakeScale(1.0, 1.0)
    
                    }, nil)
                UIView.animateWithDuration(0.25, delay: 0.4, options: nil, animations: {
                    openingLabel.alpha = 0
                    closingLabel.alpha = 0
    
                    }, completion: { finished in
                        openingLabel.removeFromSuperview()
                        closingLabel.removeFromSuperview()
                })
    
                break
            }
        }
    
    }