-
Notifications
You must be signed in to change notification settings - Fork 0
Text Animation
Song edited this page Aug 4, 2021
·
6 revisions
by Song
- 다른 View 변경으로 인해 멈추지 않는 애니메이션 구현
- Main Thread 외의 Thread 활용
기존 사용하던 GhostTypeWriter 라이브러리는 Main Thread에서 실행됨
- 콜렉션 뷰 스크롤 시 멈추어 개선 필요
- CATextLayer를 활용하여 Custom하게 제작
- UIView를 subclassing한 TypeWriterView에 CATextLayer를 활용한 애니메이션 메소드 구현
- 각 글자마다 타이머 딜레이를 다르게 주어 1글자씩 나타나게 함
func startTyping(text fullText: String, duration: Double) {
let totalCount = fullText.count
let delayPerLetter = duration / Double(totalCount)
let letters = fullText.map { String($0) }
for count in 0..<totalCount {
let currentDelay = delayPerLetter * Double(count)
Timer.scheduledTimer(withTimeInterval: currentDelay, repeats: false) { [weak self] _ in
self?.changeText(for: count, with: letters)
}
}
}
private func changeText(for count: Int, with letters: [String]) {
let currentText = text(for: count, with: letters)
textLayer.string = currentText
}
private func text(for currentCount: Int, with letters: [String]) -> String {
return (0...currentCount).map { letters[$0] }.joined()
}- CATextLayer를 View의 중앙에 위치시키는 메소드 구현
func startTyping(text fullText: String, duration: Double) {
adjustTextLayerFrameToCenter(for: fullText)
// 애니메이션 코드 생략
}
private func adjustTextLayerFrameToCenter(for fullText: String) {
let lineCount = fullText.components(separatedBy: "\n").count
let fontHeight = font.capHeight
let totalTextHeight = fontHeight * CGFloat(lineCount)
let yPosition = (layer.bounds.size.height-totalTextHeight*1.5)/2 // Position이 아닌 Origin이기 때문에 전체 텍스트 높이의 반만큼 더 위로 올려 줌
let newOrigin = CGPoint(x: 0, y: yPosition)
textLayer.frame = CGRect(origin: newOrigin, size: textLayer.frame.size)
}결과
- CATextLayer의 String Property는 Animatable하지 않다
- Layer의 텍스트 변경을 위해서는 Main Thread를 거쳐갈 수밖에 없음 -> String 프로퍼티 변경해서..? 명확한 원인 파악 필요 <<<
- Timer를 background에서 돌리더라도 String 변경 시 Main thread를 거쳐야 함
애니메이션과 동시에 유저 인터랙션이 발생할 가능성이 있는 ItemViewController에 한해 Animatable한 속성을 활용한 것으로 변경
- Opacity 변경 애니메이션 구현
func show(text fullText: String, duration: Double=0.8) {
textLayer.add(newFadeInAnimation(for: duration), forKey: opacityKey)
}
private func newFadeInAnimation(for duration: Double) -> CAKeyframeAnimation {
let animation = CAKeyframeAnimation(keyPath: opacityKey)
animation.duration = duration
animation.values = [0.0, 1.0]
animation.timingFunction = CAMediaTimingFunction.init(name: .easeOut)
return animation
}- 텍스트 길이에 따라 font size 조정
func show(text fullText: String, duration: Double=0.8) {
setTextLayer(with: fullText)
// 애니메이션 코드 생략
}
private func setTextLayer(with text: String) {
textLayer.string = text
textLayer.fontSize = newFontSize(for: text)
}
private func newFontSize(for text: String) -> CGFloat {
let letterCountsPerLine = text.components(separatedBy: "\n").map { $0.count }
let maxLetterCount = letterCountsPerLine.max() ?? 15
let newFontSize = textLayer.bounds.width / CGFloat(maxLetterCount)
return newFontSize
}결과
Text Layer Setting을 담당하는 TextPresentView에서 각 class 분화
- 하나의 CATextLayer를 가지고 있는
TextPresentView - 파라미터로 받은 텍스트를 Layer를 통해 보여주고, 글자 크기 및 레이어 위치를 조정함
class TextPresentView: UIView {
private(set) lazy var textLayer: CATextLayer = {
let textLayer = CATextLayer()
textLayer.font = UIFont(name: Font.joystix, size: 16)
textLayer.alignmentMode = .center
textLayer.frame = CGRect(origin: .zero, size: layer.bounds.size)
textLayer.foregroundColor = defaultTextColor.cgColor
layer.addSublayer(textLayer)
return textLayer
}()
private let defaultTextColor = UIColor(named: "digitalgreen") ?? UIColor.green
private let defaultLetterCount = 15
private let lineSeparator = "\n"
func show(text fullText: String) {
setTextLayer(with: fullText)
}
private func setTextLayer(with text: String) {
textLayer.string = text
textLayer.fontSize = newFontSize(for: text)
adjustTextLayerFrameToCenter(for: text)
}
private func newFontSize(for text: String) -> CGFloat {
// 상세 코드 생략
}
private func adjustTextLayerFrameToCenter(for fullText: String) {
// 상세 코드 생략
}
}- 위의 class를 subclassing하여 각 애니메이션 뷰 생성
final class TypeWriterView: TextPresentView {
private let delayPerLetter: Double = 0.08
override func show(text fullText: String) {
super.show(text: fullText)
startTyping(text: fullText)
}
private func startTyping(text fullText: String) {
// 상세 코드 생략
}
}final class FadeInTextView: TextPresentView {
private let duration: Double = 0.8
private let opacityKey = #keyPath(CALayer.opacity)
override func show(text fullText: String) {
super.show(text: fullText)
textLayer.add(newFadeInAnimation(for: duration), forKey: opacityKey)
}
private func newFadeInAnimation(for duration: Double) -> CAKeyframeAnimation {
// 상세 코드 생략
}
}created by 우송