在Swift中实现字符串淡入淡出动画

3

我是编程新手 - 但我在过去的两个月里学习了为iOS编写Swift语言。我正在制作一个简单的打字游戏 - 我的项目结构如下:我有一个隐藏的UITextView,它检测玩家按下的字符,然后将该字符与一个可见的UITextView的字符匹配。

现在我想做的是添加某种动画 - 我希望单个字母淡入/淡出。

我创建了一个属性字符串,将其添加到了一个UITextView中,但我无法找到一种动画特定范围字符串的方法。我尝试使用以下方式:

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

没有成功。我想也许 UIView 动画只能在视图对象本身上使用,而不能用于属性字符串。有什么想法或甚至是解决方法可以实现这一点吗?任何帮助都将不胜感激,谢谢!

你可以尝试使用 UIView 对象来遮盖字符串,然后通过动画改变其透明度。 - Dino Tw
@DinoTw 谢谢回复。您能详细说明一下如何掩盖字符串吗?抱歉,我对此还很陌生。您是说我可以创建一个新的 UIView 对象,并将其放置在 UITextView 上方,然后对其进行动画处理,是吗? - Chris
谢谢!transitionWithView对于颜色起作用。我在示例中放置了它,因为我无法弄清如何缩放字符串中字符的大小 - 因此我放置了我能想到的最接近的示例,即颜色。您能否建议我如何使字母“突出”(放大并缩小)? - Chris
这需要相当数量的代码才能实现,大致包括找出要动画显示的相关部分的 CGRect、在外观发生变化之前和之后取快照、动画转换快照等。你当然可以做到,但你应该问问自己,为了一个简单的效果,你想要投入多少工作量。 - Rob
为了日后的读者着想,我在下面的答案中添加了增长/缩小动画的示例。 - Rob
显示剩余2条评论
1个回答

9
您可以使用transition(with:...)来实现动画效果。例如,在Swift 3及更高版本中,将单词ipsum淡化为绿色:
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
})

最初,您还询问了有关在此动画期间使文本增长和缩小的内容,这更加复杂。但是您可以搜索文本,找到selectionRects,对这些视图进行快照,并动画化它们的transform。例如:

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

并且

growAndShrink("consectetaur cillium”)

将产生以下结果:

grow

如果您只是动画文本的颜色,您可能希望先将其渐变为 clear 再将其渐变为所需的颜色(使它更加突出),您可以使用 completion 块:

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

结果为:

red

对于Swift的早期版本,请参见此答案的先前修订版本


太好了!你能发一篇关于如何动画化字符大小以使它们放大和缩小的答案吗?(一种弹出式动画) - Chris
只是在阐述解决方案 - 是否有一种方法可以确保每次运行动画块都完成后再开始下一个?这些动画会快速连续触发。 - Chris
如果需要,可以增加每个if语句的持续时间。并确保第二个if语句在第一个完成块内,如上所示。 - Rob
抱歉,我的意思是 - 如果我想让整个动画在下次调用这两个块之前完成怎么办?也就是说,这两个块都在一个函数中,并且该函数快速连续地被调用。第二次调用总是会打断第一次调用,导致第一次调用的动画立即完成。提前感谢。 - Chris
最简单的解决方案是继续将后续的 transitionWithView 调用放入前一个完成块中。一个更复杂的解决方案将涉及将动画包装在 NSOperation 子类中,如此答案(除了使用 transitionWithView 而不是 animateWithDuration)。 - Rob
太好了!谢谢分享。 - Senocico Stelian

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