Linh Tạ

JellyGif

iOS

Trong bài viết này mình muốn giới thiệu qua về JellyGif, một thư viện nhỏ do mình tạo ra nhằm animate ảnh Gif. Tuy đã có nhiều thư viện Gif cho iOS nhưng mình chưa thực sự hài lòng vì chúng đều không tiết kiệm CPU và bộ nhớ như mình mong muốn. Một lí do khác rất quan trọng là không có thư viện nào thực sự được xây dựng để tương thích với UITableViewCellUICollectionViewCell.

JellyGif sử dụng nhiều kĩ thuật tối ưu ảnh đầu ra để đảm bảo chất lượng của Gif trong khi vẫn tiết kiệm bộ nhớ một cách tối đa. Mình cũng thiết kế JellyGif một cách mở nhất có thể để người dùng có thể tham gia vào quá trình chuẩn bị ảnh Gif và quyết định chất lượng ảnh, độ mượt của animation, hay hiệu năng của việc xử lí ảnh.

JellyGifImageView

Cách đơn giản nhất để load ảnh Gif từ file local hay data là sử dụng JellyGifImageView


import JellyGif

let imageView = JellyGifImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

//Animate ảnh Gif trong main bundle
imageView.startGif(with: .name("Gif name"))

//Animate ảnh Gif tại local path
let url = URL(string: "Gif path")!
imageView.startGif(with: .localPath(url))

//Animate ảnh Gif từ data
imageView.startGif(with: .data(Data))


JellyGifAnimator

JellyGifImageView chỉ là class nền để làm quen với JellyGif và là thứ duy nhất bạn cần để hiện thị một ảnh Gif. Tuy nhiên, nó không thực sự hữu dụng trong các trường hợp phức tạp như quản lí quá trình tạo ảnh Gif, hay hiển thị Gif trong UITableViewCell & UICollectionViewCell.

Để sử dụng hết sức mạnh của JellyGif và tối ưu hiệu năng CPU cũng như memory bạn nên dùng JellyGifAnimator và conform JellyGifAnimatorDelegate.


import JellyGif

let imageView = UIImageView(CGRect(x: 0, y: 0, width: 100, height: 100))
let animator = JellyGifAnimator(imageInfo: .name("Gif name"), pixelSize: .custom(350), animationQuality: .best)
animator.delegate = self

//JellyGifAnimatorDelegate
func gifAnimatorIsReady(_ sender: JellyGifAnimator) {
  sender.startAnimation()
}

func imageViewForAnimator(_ sender: JellyGifAnimator) -> UIImageView? {
  return imageView
}

func gifAnimatorDidChangeImage(_ image: UIImage, sender: JellyGifAnimator) {
  //Sử dụng hàm này nếu bạn muốn trực tiếp quản lí từng frame của ảnh Gif
}

JellyGifAnimator cần ba thuộc tính để khởi tạo là:

UICollectionView & UITableView

JellyGifAnimator được tạo ra để có thể sử dụng với UICollectionViewUITableView một cách dễ dàng


import JellyGif

class ViewController: UIViewController {
  var gifNames: [String] = []
  var animators: [IndexPath: JellyGifAnimator] = [:]
    
  //Code của bạn
  //...
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  //Code của bạn
  //...
  
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    //dequeue cell
    //...
    
    let gifName = gifNames[indexPath.item]
    
    if animators[indexPath] == nil {
        let animator = JellyGifAnimator(imageInfo: .name(gifName), pixelSize: .custom(350), animationQuality: .best)
        animators[indexPath] = animator
    }

    //Sử dụng placeholder được cung cấp bởi JellyGifAnimator trong khi chờ ảnh Gif được xử lý
    if animators[indexPath]?.isReady != true {
        cell.imageView.image = animators[indexPath]?.placeholder
    }

    animators[indexPath]?.delegate = self

    return cell
  }
  
  func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if animators[indexPath]?.isReady == true {
      animators[indexPath]?.startAnimation()
    } else {
      animators[indexPath]?.prepareAnimation()
    }
  }

  func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    //Dừng mọi xử lí liên quan đến ảnh Gif khi cell không còn được hiển thị
    animators[indexPath]?.pauseAnimation()
    animators[indexPath]?.stopPreparingAnimation()
  }
}

extension ViewController: JellyGifAnimatorDelegate {
  func gifAnimatorIsReady(_ sender: JellyGifAnimator) {
    sender.startAnimation()
  }

  func imageViewForAnimator(_ sender: JellyGifAnimator) -> UIImageView? {
    for indexPath in collectionView.indexPathsForVisibleItems {
      if animators[indexPath] === sender {
        return (collectionView.cellForItem(at: indexPath) as? YourCustomCell)?.imageView
      }
    }
    return nil
  }
}

Mình mong rằng các bạn sẽ thấy thư viện nhỏ này hữu dụng. Nếu bạn gặp bất kì bug gì liên quan đến JellyGif hay đơn giản là bạn có góp ý về thư viện thì đừng ngại liên lạc với mình nhé.

Source code: JellyGif