[Swift3] Cách tạo hiệu ứng viết chữ chuyên nghiệp

Chào các bạn, hôm nay mình sẽ trở lại với chủ đề tạo hiệu ứng viết chữ chuyện nghiệp, được sử dụng trong các app học hán tự, tiếng nhật... trước hết mời các bạn xem topic này sẽ hướng dẫn bạn làm được gì:

Vậy chúng ta cần phân tích 1 chút: theo như hình ảnh trên thì chúng ta sẽ cần 2 layer, 1 layer dùng để tạo các nét chữ mờ bên dưới, layer còn lại sẽ sử dụng làm hiệu ứng, kết hợp với animation, mỗi nét cách nhau 1s tiếp theo sẽ có yêu cầu nữa là khi tap vào chữ nó sẽ replay lại 1 lần nữa animation này. Let's Start:

Bước 1: Khởi tạo project TextAnimationExample và khởi tạo pod thông qua câu lệnh pod init add thêm pod SVGPath, trường hợp này chúng ta sẽ sử dụng lib SVGPath để xử lý các nét vẽ: pod "SVGPath" tiếp gõ lệnh pod install và mở file xcode workspace, ok sang bước 2

Bước 2: Tạo file SVGAnimationView.swift và khởi tạo các dòng code ban đầu như sau:

var animatedLayers: [CAShapeLayer] = []
var animationPaths: [UIBezierPath] = []
lazy var shapeLayer: CAShapeLayer = {
    let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 2)
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = path.cgPath
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.lineWidth = 1.0
    return shapeLayer
}()
var animationCount = 0
override func awakeFromNib() {
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(replayAnimation))
    self.addGestureRecognizer(tapGesture)
    self.clipsToBounds = false
    self.layer.shadowColor = UIColor.black.cgColor
    self.layer.shadowOffset = CGSize(width: 1, height: 1)
    self.layer.shadowRadius = 5
}

Chúng ta sẽ sử dụng phương thức awakeFromNib hỗ trợ cho việc kéo thả trong nib hoặc storyboard

Bước 3: Xử lý animation và animation path

func replayAnimation() {
       if animationCount == animatedLayers.count {
           playAnimation(0)
       }
   }

 func playAnimation(_ delay: TimeInterval) {
       var delay = delay
       
       if animationPaths.count == animatedLayers.count {
           animationCount = 0
           
           for index in 0 ..< animatedLayers.count {
               let layer = animatedLayers[index]
               let path = animationPaths[index]
               layer.removeFromSuperlayer()
               self.perform(#selector(startAnimationLayer(_:)), with: [layer, path], afterDelay: delay, inModes: [RunLoopMode.commonModes])
               delay += 0.8
           }
       }  
   }
   func stopAnimations() {
       self.layer.removeAllAnimations()
       NSObject.cancelPreviousPerformRequests(withTarget: self) 
   }
   func playAnimationFromPath(_ s_Paths: String?) {
       if let s_Paths = s_Paths {
           let paths = s_Paths.components(separatedBy: "|")
           for path in paths {
               let layer = CAShapeLayer()
               
               let bezierPath = UIBezierPath(svgPath: path)
               let layerBG = CAShapeLayer() 
               layerBG.fillColor = UIColor.clear.cgColor
               layerBG.strokeColor = UIColor.black.withAlphaComponent(0.2).cgColor
               layerBG.path = bezierPath.cgPath
               layerBG.lineWidth = 6
               self.layer.addSublayer(layerBG)
               layer.lineWidth = 6
               layer.path = bezierPath.cgPath
               layer.fillColor = UIColor.clear.cgColor
               layer.strokeColor = UIColor.black.cgColor
               animatedLayers.append(layer)
               animationPaths.append(bezierPath)
           }
       }
       
       playAnimation(1)
   }

playAnimationFromPath: Phương thức này sẽ tạo ra 2 layer, 1 cái nằm bên dưới mờ, không animation, và 1 layer animation ở bên trên playAnimation: Phương thức này sẽ xử lý animation trên main thread, các path sẽ được đọc từ mảng ở phương thức trên và sử dụng 1 layer để vẽ

Bước 4: Thêm CAAnimationDelegate và implement funtion bên dưới để có thể xử lý vẽ cho từng nét đặt value từ 0 đến 1 nhé

 func addAnimationToLayer(_ layer: CAShapeLayer, duration: TimeInterval) {
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.delegate = self
        animation.fromValue = 0
        animation.toValue = 1
        animation.duration = duration
        layer.add(animation, forKey: nil)
    }

Vậy là xong, đây là phương pháp khá đơn giản nhưng cũng cực kỳ hiệu quả, các bạn có thể sáng tạo thêm nữa nhé, chúc các bạn tạo được nhiều app học tiếng nhật, kanji hay từ sự hỗ trợ của topic này.

Source code cho các bạn tham khảo https://github.com/vanthanh88/TextAnimationExample


All Rights Reserved