뀨웅?
gif 파일을 넣어보는 기능은 처음 해봤는데 공부할겸 기록해보려한다.
기본적으로 iOS앱에서는 GIF파일을 사용하려면 몇가지 단계를 거쳐야한다고 한다.
먼저 GIF 파일을 프로젝트에 추가하고, UIImage 확장을 통해서 GIF를 애니메이션 이미지로 변환시켜준 뒤에, 뷰컨트롤러에서 이걸 사용하는 방법으로 했다.
사실 라이브러리를 사용하고 싶었다 (완전 간단해보여서)
GIF 라이브러리 설치는 SPM으로 되지 않는다고 해서 코코아팟 이라는걸 도전했다가 프로젝트에 오류가 떠서 급하게 iOS가 지원하는 방법으로 했다^^ 코코아팟 무섭다.
우선 사용할 gif를 번들에 추가해준다.
처음에 모르고 에셋에 넣었는데 에셋에 넣으면 gif를 읽을 수가 없어서 무조건 번들로 폴더 하나 만들어서 넣으라고 한다.
그것도 모르고 에셋에 넣고 왜 안되지 하며 시간을 버렸다..
그냥 드래그 앤 드랍으로 끌고 놔주기만 하면 잘 들어가는 거였는데.....
아무튼 파일을 드래그해서 놓게 되면 "Copy items if needed"
옵션으로 해놓고 "Finish"
를 누르면 된다.
그렇게 되면 프로젝트에 잘 추가가 되었고,
static func animatedImage(withGIFNamed name: String) -> UIImage?
gif파일 이름인 name을 입력받아서 애니메이션 이미지를 생성하도록 메서드를 정의해준다.
성공하면 UIImage 타입의 애니메이션 이미지를 반환할 것이고 실패하면 nil을 반환한다.
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다.
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을 반환하게 된다.
guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else {
print("GIF 소스를 생성할 수 없습니다: \(name)")
return nil
}
이 부분은 gif 파일 데이터를 CGImageSource로 변환하는 과정이다.
굳이 이게 왜 필요한가 했는데 gif 파일이 여러 프레임으로 구성된 애니메이션 이미지라서 이런 멀티프레임 이미지를 처리하는데 사용되는 Core Graphics의 기능이라고 한다.
저 과정을 거치면 gif 파일의 각 프레임을 추출하고 애니메이션을 생성할 수 있게 된다.
여기서 CGImageSource가 생소해서 알아보니 위에 설명한 내용과 함께 다음과 같은 작업들을 할 수 있다고 한다.
이미지 프레임 추출
이미지 속성 읽기
이미지 변환
아무튼 단계별로 다시 보면
CGImageSourceCreateWithData
는 CFData
타입을 입력으로 받기 때문에 imageData
를 CFData
로 변환하고,
다음으로 GImageSourceCreateWithData
를 호출해 CGImageSource
객체를 생성해준다.
그리고 만약 CGImageSource
생성에 실패해서 nil
이 반환되면, 오류 메시지를 출력하고 함수를 종료시킨다.
CFData란?
CFData는 Core Foundation의 일부로 바이너리 데이터를 저장하고 관리하는 데 사용된다.
Swift의 Data 타입과 매우 유사하지만 CFData는 C 언어 기반의 API이기 때문에, Swift에서 사용할 때는 약간의 변환이 필요하다.
let count = CGImageSourceGetCount(source)
이 코드는 gif 파일의 프레임 수를 가져오는건데 count는 gif 파일의 총 프레임 수라고 보면 된다.
var images = [UIImage]() // 애니메이션에 사용될 이미지 배열
var duration = 0.0 // 애니메이션 총 지속 시간
images
는 각 프레임의 이미지를 저장할 배열로 보면 되고 주석에 써진대로 duration
은 애니메이션의 총 지속 시간을 저장할 변수다.
duration = 0.0
으로 설정하면, 애니메이션의 총 지속 시간이 0
이 되는데 이 경우 UIImage.animatedImage(with:duration:)
은 기본적으로 애니메이션을 무한 반복을 하게 된다고 한다.
하지만 이건 UIImageView
의 animationRepeatCount
설정에 따라 달라질 수 있다는걸 알고 있어야한다.
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
UIImage(cgImage:)
images.append(image)
프레임 속성 읽기 (CGImageSourceCopyPropertiesAtIndex)
CGImageSourceCopyPropertiesAtIndex
kCGImagePropertyGIFDictionary
지연 시간 계산 (kCGImagePropertyGIFDelayTime)
kCGImagePropertyGIFDelayTime
duration += delayTime
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)
}
}