[ swift ] GIF 파일 이미지뷰에 넣어보기

sonny·2025년 1월 23일
0

TIL

목록 보기
111/133

뀨웅?

gif 파일을 넣어보는 기능은 처음 해봤는데 공부할겸 기록해보려한다.

기본적으로 iOS앱에서는 GIF파일을 사용하려면 몇가지 단계를 거쳐야한다고 한다.

먼저 GIF 파일을 프로젝트에 추가하고, UIImage 확장을 통해서 GIF를 애니메이션 이미지로 변환시켜준 뒤에, 뷰컨트롤러에서 이걸 사용하는 방법으로 했다.

사실 라이브러리를 사용하고 싶었다 (완전 간단해보여서)

GIF 라이브러리 설치는 SPM으로 되지 않는다고 해서 코코아팟 이라는걸 도전했다가 프로젝트에 오류가 떠서 급하게 iOS가 지원하는 방법으로 했다^^ 코코아팟 무섭다.

GIF 파일 프로젝트에 추가하기

우선 사용할 gif를 번들에 추가해준다.

처음에 모르고 에셋에 넣었는데 에셋에 넣으면 gif를 읽을 수가 없어서 무조건 번들로 폴더 하나 만들어서 넣으라고 한다.

그것도 모르고 에셋에 넣고 왜 안되지 하며 시간을 버렸다..

그냥 드래그 앤 드랍으로 끌고 놔주기만 하면 잘 들어가는 거였는데.....

아무튼 파일을 드래그해서 놓게 되면 "Copy items if needed" 옵션으로 해놓고 "Finish" 를 누르면 된다.

그렇게 되면 프로젝트에 잘 추가가 되었고,

GIF파일을 애니메이션 이미지로 변환하기

메서드 정의

static func animatedImage(withGIFNamed name: String) -> UIImage?

gif파일 이름인 name을 입력받아서 애니메이션 이미지를 생성하도록 메서드를 정의해준다.

성공하면 UIImage 타입의 애니메이션 이미지를 반환할 것이고 실패하면 nil을 반환한다.

GIF 파일의 URL 가져오기

guard let bundleURL = Bundle.main.url(forResource: name, withExtension: "gif") else {
    print("GIF 파일을 찾을 수 없습니다: \(name)")
    return nil
}

번들 안에 있는 특정 파일을 찾기 위해서 (gif파일의 URL을 가져오기위해) 가드문을 이용했는데

Bundle.main.url(forResource:withExtension:) 메서드는 gif 파일이 존재하지 않으면 nil을 반환하고 nil을 반환하지 않을 때는 bundleURL에 값을 할당해준다.

forResource는 GIF 파일의 이름을 지정하고, withEtension은 파일 확장자를 지정한다.

그래서 gif다.

GIF 파일을 데이터로 변환

guard let imageData = try? Data(contentsOf: bundleURL) else {
    print("GIF 파일을 데이터로 변환할 수 없습니다: \(name)")
    return nil
}

다음으로 gif파일을 데이터로 변환해줘야한다.

위 가드문으로 가져온 gif파일의 url을 사용해서 파일을 data 객체로 변환을 시켜주는데 실패하면 nil을 반환하면 된다.

에러메세지는 쓰는거 너무 귀찮은데 없으면 어디서 에러나는지 몰라서 습관처럼 해야한다.

Data(contentsOf:)는 Swift에서 제공하는 초기화 메서드인데, URL이나 파일 경로를 통해서 데이터를 읽어와 Data 객체로 변환한다고 한다.

try? 메서드는 파일을 읽는 도중에 오류가 발생할 수 있기때문에 try?를 이용해 오류처리를한 것이고 만약 오류가 나면 nil을 반환하게 된다.

CGImageSource 생성

guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else {
    print("GIF 소스를 생성할 수 없습니다: \(name)")
    return nil
}

이 부분은 gif 파일 데이터를 CGImageSource로 변환하는 과정이다.

굳이 이게 왜 필요한가 했는데 gif 파일이 여러 프레임으로 구성된 애니메이션 이미지라서 이런 멀티프레임 이미지를 처리하는데 사용되는 Core Graphics의 기능이라고 한다.

저 과정을 거치면 gif 파일의 각 프레임을 추출하고 애니메이션을 생성할 수 있게 된다.


[ CGImageSource? ]

여기서 CGImageSource가 생소해서 알아보니 위에 설명한 내용과 함께 다음과 같은 작업들을 할 수 있다고 한다.

이미지 프레임 추출

  • GIF 파일은 여러 프레임으로 구성되어 있습니다. CGImageSource를 사용하면 각 프레임을 개별적으로 추출할 수 있다.

이미지 속성 읽기

  • GIF 파일의 속성(예: 지연 시간, 반복 횟수 등)을 읽을 수 있다.

이미지 변환

  • 이미지를 다른 형식으로 변환하거나, 크기를 조정하는 등의 작업을 할 수 있다.

아무튼 단계별로 다시 보면

CGImageSourceCreateWithDataCFData 타입을 입력으로 받기 때문에 imageDataCFData로 변환하고,

다음으로 GImageSourceCreateWithData를 호출해 CGImageSource 객체를 생성해준다.

그리고 만약 CGImageSource 생성에 실패해서 nil이 반환되면, 오류 메시지를 출력하고 함수를 종료시킨다.

CFData란?
CFData는 Core Foundation의 일부로 바이너리 데이터를 저장하고 관리하는 데 사용된다.
Swift의 Data 타입과 매우 유사하지만 CFData는 C 언어 기반의 API이기 때문에, Swift에서 사용할 때는 약간의 변환이 필요하다.

GIF 프레임 수 가져오기

let count = CGImageSourceGetCount(source)

이 코드는 gif 파일의 프레임 수를 가져오는건데 count는 gif 파일의 총 프레임 수라고 보면 된다.

애니메이션에 사용될 변수 초기화

var images = [UIImage]() // 애니메이션에 사용될 이미지 배열
var duration = 0.0 // 애니메이션 총 지속 시간

images는 각 프레임의 이미지를 저장할 배열로 보면 되고 주석에 써진대로 duration은 애니메이션의 총 지속 시간을 저장할 변수다.

duration = 0.0으로 설정하면, 애니메이션의 총 지속 시간이 0이 되는데 이 경우 UIImage.animatedImage(with:duration:)은 기본적으로 애니메이션을 무한 반복을 하게 된다고 한다.

하지만 이건 UIImageViewanimationRepeatCount 설정에 따라 달라질 수 있다는걸 알고 있어야한다.

각 프레임을 순회하며 이미지와 지속 시간 계산

for i in 0..<count {
    if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
        let image = UIImage(cgImage: cgImage)
        images.append(image)
        
        if let properties = CGImageSourceCopyPropertiesAtIndex(source, i, nil) as? [String: Any],
           let gifProperties = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] {
            if let delayTime = gifProperties[kCGImagePropertyGIFDelayTime as String] as? Double {
                duration += delayTime
            }
        }
    }
}

CGImageSourceCreateImageAtIndex
각 프레임의 CGImage를 생성하고

UIImage(cgImage:)
CGImage를 UIImage로 변환하여 images 배열에 추가한다.

CGImageSourceCopyPropertiesAtIndex
프레임의 속성을 가져오고

kCGImagePropertyGIFDictionary
GIF 속성 딕셔너리를 가져온다.

kCGImagePropertyGIFDelayTime
각 프레임의 지연 시간(delayTime)을 가져와 duration에 누적한다.

결과적으로 images 배열에는 모든 프레임의 이미지가 저장되고, duration에는 애니메이션의 총 지속 시간이 계산된다.

.
.
이게 왜 필요하지 싶었다 이 코드 봤을 때.

그래서 ai에게 물어봤다 이게 왜 굳이 필요한지. 근데 답변을 보니 좀 이해가 가긴했다.

위 코드는 gif파일의 각 프레임을 추출하고 애니메이션의 총 지속 시간을 계산하는데 필요하다고 한다.

각 프레임은 독립적인 이미지고 프레임간의 지연시간이 지정되어있는데 이 지연시간이 프레임이 화면에 표시되는 시간을 결정한다고 한다.

이 코드는 gif 파일을 애니메이션 이미지로 변환하기 위해 필수적인 코드라고 한다.
.
.

프레임 추출 (CGImageSourceCreateImageAtIndex)

CGImageSourceCreateImageAtIndex

  • CGImageSource에서 특정 인덱스(i)의 프레임을 CGImage로 추출한다.
    UIImage(cgImage:)
  • CGImage를 UIImage로 변환합니다. 이 UIImage는 GIF 파일의 한 프레임을 나타낸다.
    images.append(image)
  • 변환된 UIImage를 images 배열에 추가합니다. 이 배열은 모든 프레임을 저장하는 데 사용된다.

프레임 속성 읽기 (CGImageSourceCopyPropertiesAtIndex)

CGImageSourceCopyPropertiesAtIndex

  • 특정 인덱스(i)의 프레임 속성을 읽어오는데 이 속성에는 지연 시간, 프레임 크기 등의 정보가 포함되어 있다.

kCGImagePropertyGIFDictionary

  • GIF 파일의 속성 딕셔너리를 가져온다. 이 딕셔너리에는 GIF 파일의 지연 시간, 반복 횟수 등의 정보가 포함되어 있다.

지연 시간 계산 (kCGImagePropertyGIFDelayTime)

kCGImagePropertyGIFDelayTime

  • 현재 프레임의 지연 시간을 가져오는데 이 값은 Double 타입으로, 초 단위로 표현된다.

duration += delayTime

  • 각 프레임의 지연 시간을 duration에 누적한다. 이렇게 하면 애니메이션의 총 지속 시간을 계산할 수 있다.

애니메이션 이미지 생성

return UIImage.animatedImage(with: images, duration: duration)

images배열과 duration을 사용해서 애니메이션 이미지를 생성한다.
.
.
.
.

총 코드

import UIKit

extension UIImage {
    // GIF 파일 이름을 받아서 애니메이션 이미지를 생성하는 메서드
    static func animatedImage(withGIFNamed name: String) -> UIImage? {
        
        // 메인 번들에서 GIF 파일의 URL을 가져옴
        guard let bundleURL = Bundle.main.url(forResource: name, withExtension: "gif") else {
            print("GIF 파일을 찾을 수 없습니다: \(name)")
            return nil
        }
        
        // GIF 파일을 데이터로 변환
        guard let imageData = try? Data(contentsOf: bundleURL) else {
            print("GIF 파일을 데이터로 변환할 수 없습니다: \(name)")
            return nil
        }
        
        // 데이터를 사용하여 CGImageSource 생성
        guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else {
            print("GIF 소스를 생성할 수 없습니다: \(name)")
            return nil
        }
        
        // GIF 프레임 수를 가져옴
        let count = CGImageSourceGetCount(source)
        var images = [UIImage]() // 애니메이션에 사용될 이미지 배열
        var duration = 0.0 // 애니메이션 총 지속 시간 (0으로 하면 반복됨)
        
        // 각 프레임을 순회하며 이미지와 지속 시간을 계산
        for i in 0..<count {
            if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
                
                // CGImage를 UIImage로 변환하여 배열에 추가
                let image = UIImage(cgImage: cgImage)
                images.append(image)
                
                // GIF 속성에서 지연 시간을 가져와 총 지속 시간에 추가
                if let properties = CGImageSourceCopyPropertiesAtIndex(source, i, nil) as? [String: Any],
                   let gifProperties = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any] {
                    if let delayTime = gifProperties[kCGImagePropertyGIFDelayTime as String] as? Double {
                        duration += delayTime
                    }
                }
            }
        }
        
        // 계산된 이미지 배열과 지속 시간을 사용하여 애니메이션 이미지 생성
        return UIImage.animatedImage(with: images, duration: duration)
    }
}
profile
iOS 좋아. swift 좋아.

0개의 댓글

관련 채용 정보