在NSMutableAttributedString的一部分中实现动画效果

6
我正在制作一个具有UITextView的iOS应用程序。当在该UITextView中关闭括号时,我希望突出显示与之配对的开放括号给用户看。目前,我使用NSMutableAttributedString并更改成对括号的字体大小来实现这一点,这虽然有效但不太美观。我真正想要的是通过动画来完成这个功能,就像Xcode在我关闭代码中的括号时所做的那样。是否有任何方法可以实现这一点呢?
非常感谢您的任何帮助,尽管我对此还比较陌生,请不要认为我知道得太多 :)
以下是我的代码:
    @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
        }
    }
}

你可以看到我尝试使用UIView.animateWithDuration来实现这个,但是如我所料,它没有起作用。


请提供您收到的确切错误。 - Stefan Falk
这段代码没有出现错误,但是没有动画效果。它直接跳转到了更大的字体大小。 - Andreas
3个回答

4

对于我来说,另一个可行的解决方案(Swift 4)是生成多个属性字符串,将其中一个赋值给标签,然后在转换动画块内替换内容(attributedText)。例如:

// 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)

不要忘记在动画选项中使用.transitionCrossDissolve


4

有一个更简单的方法可以完成这个操作,你可以在这个回答中看到: Animate text change in UILabel

你只需要设置label.attributedText 属性即可:

UIView.transition(
    with: label,
    duration: 0.15,
    options: .transitionCrossDissolve,
    animations: {
        label.attributedText = NSAttributedString(string: text, attributes: attributes)
    },
    completion: nil
)

3
我通过获取实际括号的框架并在UITextView上创建新的UILabel,并对这些标签进行动画处理,达到了我想要的效果。
    @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
        }
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接