swift/swift 공부

UIImageView에 GIF 넣기 [Lottie 사용X]

isak(이삭) 2024. 12. 12. 23:14

How to use a GIF file in UIKit's UIImageView 
개발을 하다보면 이미지보다는 움직이는 화면을 보여주는게 사용자에게 더 와닿을 때가 있다고 생각하는데,

그 상황이 보통 용량이 큰 파일을 다운로드 받거나, 네트워크 통신시간이 길어질 때 움직이는 화면을 통해 진행중임을 알려주는 상황이라고 생각한다.

 

그렇담 하나하나 Animation을 적용해야할까?

그렇게도 가능하겠지만, 많은 시간을 쏟을 수 없었고, 그렇다고 써드파티 라이브러리를 추가하고 싶지 않았다.

(Lottie를 통해서 구현하면 더 빠르고 쉽게 적용할 수 있었지만, 이 뷰를 위해 의존성을 또 추가하는게 마음에 들지 않았다.)

 

해결할 수 있는 방법을 찾다가, UIImageView에 애니메이션을 걸어줄 수 있는 프로퍼티와 메서드가 있음을 확인했다.

(맨 아래 적용한 토탈 코드가 있다!)

 

1. GIF 파일 준비

Progress View를 보여줘야해서 gif 파일을 준비.

각자 원하는 gif를 준비하면 된다!

2. 프로젝트 파일에 .gif 파일 추가

⭐️ 여기서 주의

gif 파일은 Assets catalog에 넣을게 아니라 외부에 따로 넣어줘야함.

(Assets에 넣어서 파일 path를 찾으니 계속해서 path를 찾지 못하는 이슈가 있었음. 애플 도큐먼츠 어디에서도 asset에 gif파일을 넣지 말라는 글은 확인못해서 찾는 중)


Assets에 넣으려고 했던 이유는, data file로 분류가 될 수 있지 않나 .. ? 라고 생각했기 때문!

 

[2024.12.13 수정]

여기 리스트에는 gif 파일이 포함되어있지 않음 .. 하 ^_^..  HEIF, PNG, JPG, PNG 는 있는데 GIF는 없네..

https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AssetTypes.html

 

Asset Catalog Format Reference: Types Overview

Asset Catalog Format Reference

developer.apple.com

애플 도큐먼츠를 열심히 찾자..!

 

 

3. 화면에 gif 나타내기

우선 해당 gif를 보여줄 UIImageView를 준비하고

private let networkProgress: UIImageView = {
    let view = UIImageView()
    view.clipsToBounds = true
    view.contentMode = .scaleAspectFill
    return view
}()

 

 

만들어둔 UIImageView에 준비한 gif 파일을 준비하는 코드는 아래와 같다.

 

1. 프로젝트에 추가한 gif 파일을 data로 변환해 필요한 데이터를 추출할 수 있는 형태로 변경

guard let gifURL = Bundle.main.url(
    forResource: "ProgressAnimation",
    withExtension: "gif"
),
      let gifData = try? Data(contentsOf: gifURL), // url을 기반으로 Data 타입으로 가져옴
      let source = CGImageSourceCreateWithData(
        gifData as CFData,
        nil
      ) // 
else {
    print("Error with gifURL, gifData, Source in \(#file)")
    return
}

여기서 CGImageSourceCreateWithData() 메서드는 애플의 ImageI/O 프레임워크에서 제공하는 메소드이다.

해당 메서드를 통해 이미지 데이터에 접근할 수 있는데, 해당 이미지 데이터를 이루는 이미지 개수에 대한 정보나 속성, 이미지 상태 등등에 접근할 수 있다.

https://developer.apple.com/documentation/imageio/cgimagesourcecreatewithdata(_:_:)

 

CGImageSourceCreateWithData(_:_:) | Apple Developer Documentation

Creates an image source that reads from a Core Foundation data object.

developer.apple.com

 

2. 화면에 나타낼 데이터

let frameCount = CGImageSourceGetCount(source) // 1.
var images = [UIImage]()

// 2.
(0..<frameCount).compactMap {
    CGImageSourceCreateImageAtIndex(
        source,
        $0,
        nil
    )
}.forEach {
    images.append(UIImage(cgImage: $0))
}

1. gif가 몇 프레임으로 구성되어있는지 추출

source의 타입은 CGImageSource로 이미지 정보에 접근할 수 있는 타입인데, 이 이미지 데이터를 통해서 준비한 gif 파일이 몇 개의 이미지 파일로 구성되어있는지 Int로 반환받음. (각 프레임은 GIF의 개별 이미지를 의미함)

 

2. 프레임 수만큼 이미지를 추출 및 변환

 

  • 프레임 추출:
    CGImageSourceCreateImageAtIndex를 사용해 CGImageSource에서 각 프레임을 CGImage 형태로 추출
  • UIImage로 변환:
    추출된 CGImage를 UIImage로 변환
  • 배열에 저장:
    변환된 UIImage 객체를 배열(images)에 추가

 

https://developer.apple.com/documentation/imageio/cgimagesourcecreateimageatindex(_:_:_:)

 

CGImageSourceCreateImageAtIndex(_:_:_:) | Apple Developer Documentation

Creates an image object from the data at the specified index in an image source.

developer.apple.com

 

3. 애니메이션 적용

networkProgress.animationImages = images // [UIImage]
// frameCount는 GIF의 총 프레임 수를 나타내고, 각 프레임이 0.05초 동안 재생되도록 설정
networkProgress.animationDuration = TimeInterval(frameCount) * 0.05
networkProgress.animationRepeatCount = 0 // 무한 재생
networkProgress.startAnimating() // 애니메이션 시작

 

 

gif 파일을 넣어 적용한 UI

 

 

 

 

 토탈 코드

private func setProgressView() {
	// 번들에(Resource에) 넣어둔 파일 url path 추출
    guard let gifURL = Bundle.main.url(
        forResource: "ProgressAnimation",
        withExtension: "gif"
    ),
          let gifData = try? Data(contentsOf: gifURL), // url을 기반으로 Data 타입으로 가져옴
          let source = CGImageSourceCreateWithData(
            gifData as CFData,
            nil
          ) // 
    else {
        print("Error with gifURL, gifData, Source in \(#file)")
        return
    }

    let frameCount = CGImageSourceGetCount(source)
    var images = [UIImage]()

    (0..<frameCount).compactMap {
        CGImageSourceCreateImageAtIndex(
            source,
            $0,
            nil
        )
    }.forEach {
        images.append(UIImage(cgImage: $0))
    }

    networkProgress.animationImages = images
    networkProgress.animationDuration = TimeInterval(frameCount) * 0.05
    networkProgress.animationRepeatCount = 0
    networkProgress.startAnimating()
}