Animate a change in part of an NSMutableAttributedString

核能气质少年 提交于 2019-12-01 08:10:51

问题


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.


回答1:


Another solution which works for me (Swift 4) is to generate multiple attributed strings, assign one to label and then replace the content (attributedText) inside transition animation block. For example:

// MARK: Extension util which generates NSAttributedString by text,font,color,backgroundColor
extension NSAttributedString {
    class func generate(from text: String, font: UIFont = UIFont.systemFont(ofSize: 16), color: UIColor = .black, backgroundColor: UIColor = .clear) -> NSAttributedString {
        let atts: [NSAttributedStringKey : Any] = [.foregroundColor : color, .font : font, .backgroundColor : backgroundColor]
        return NSAttributedString(string: text, attributes: atts)
    }
}

// MARK: Sentence
let string1 = "Hi, i'm "
let string2 = "Daniel"

// MARK: Generate highlighted string
let prefixAttString = NSAttributedString.generate(from: string1)
let highlightedSuffixAttString = NSAttributedString.generate(from: string2, backgroundColor: .red)
let highlightedString = NSMutableAttributedString()
highlightedString.append(prefixAttString)
highlightedString.append(highlightedSuffixAttString)


// MARK: Generate regular string (Same prefix, different suffix)enter image description here
let regularSuffixAttString = NSAttributedString.generate(from: string2)
let regularString = NSMutableAttributedString()
regularString.append(prefixAttString)
regularString.append(regularSuffixAttString)

self.view.addSubview(label)
label.attributedText = regularString

// UIViewAnimationOptions.transitionCrossDissolve is necessary.
UIView.transition(with: self.label, duration: 4, options: [.transitionCrossDissolve], animations: {
    self.label.attributedText = highlightedString
}, completion: nil)

}

Don't forget to use .transitionCrossDissolve in the animation options.




回答2:


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
        }
    }

}


来源:https://stackoverflow.com/questions/28034317/animate-a-change-in-part-of-an-nsmutableattributedstring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!