애플사이다의 iOS 개발 일지

[PTR] RefreshControl 및 ImageView에 Gif 이미지 넣기 본문

iOS

[PTR] RefreshControl 및 ImageView에 Gif 이미지 넣기

Applecider 2022. 12. 10. 15:51

PTR (Pull To Refresh) 기능을 구현할 때 기본 RefreshControl을 사용할 수도 있지만 이번에는 Gif 파일을 넣어서 만들어봤다.

 

Gif 이미지 처리에 대한 포스팅이 별로 없어서 기록해본다.


이건 Apple에서 제공하는 default RefreshControl이다.

Gif를 적용한 RefreshControl을 아래처럼 만들어봤다.

RefreshControl의 목적은 "지금 Loading 중임. 잠시만 기다려" 이므로 좀 더 Loading 효과스러운 Gif를 써도 된다.

이번에는 애니메이션을 설명하려고 호머 심슨 Gif를 가져왔다.

3단계만 있으면 된다. 매우 간단하다.

1. Gif를 넣어서 ImageView 초기화

2. RefreshControl에다가 Gif 올리기

3. CollectionView에다가 RefreshControl 넣기

 

1. Gif를 넣어서 ImageView 초기화

먼저 Gif 파일을 드래그해서 Bundle에 넣는다.

이제 UIImageView initializer를 생성한다.

 

Asset 이미지를 활용할 때 UIImageView(named:) 형태로 초기화하니까

네이밍을 비슷하게 UIImageView(gifNamed: "Simpson")로 하는 게 이쁠 것 같다.

extension UIImageView {
    convenience init?(gifNamed: String, animationDuration: Double = 1.0) { 
        // ✅ gifURL = 사용할 gif 파일의 경로
        // ✅ CGImageSourceCreateWithData = gif 데이터를 통해 CGImageSource를 만들겠다
        guard let gifURL = Bundle.main.url(forResource: gifNamed, withExtension: "gif"), 
              let gifData = try? Data(contentsOf: gifURL),
              let source =  CGImageSourceCreateWithData(gifData as CFData, nil) else { 
            return nil
        }
        
        var images = [UIImage]()
        // Finder에서 gif 파일을 열면 여러 장의 이미지가 들어있다
        // ✅ gif 파일을 구성하는 이 이미지들의 개수 == CGImageSourceGetCount
        let imageCount = CGImageSourceGetCount(source)
        
        for index in 0..<imageCount {
            // ✅ for문을 통해 개별 이미지에 접근할 거다 (타입 변환 CGImage => UIImage)
            if let image = CGImageSourceCreateImageAtIndex(source, index, nil) {
                images.append(UIImage(cgImage: image))
            }
        }
        
        // ✅ gif 이미지들을 담고 있는 UIImageView를 만든다
        // duration == gif 애니메이션이 지속될 시간 (1초가 적당한듯)
        let animatedImage = UIImage.animatedImage(with: images, duration: animationDuration)
        self.init(image: animatedImage)
    }
}
  • UIImageView extension으로 initializer를 추가한다.
    초기화 과정에서 gif 파일이 유효하지 않은 등 초기화에 실패할 수 있으므로 실패가능한 이니셜라이저로 만들었다.
  • 맨 위의 guard문부터 보자.
    사용할 gif 파일의 경로를 받아서 -> gif 파일을 Data 타입으로 만들고 -> 그걸로 CGImageSource를 만든다.
  • 보통 Finder에서 gif 파일을 열면 애니미이션을 구성하는 여러 장의 이미지가 들어있다.
    그 이미지들이 CGImageSource라고 보면 된다.
  • CGImageSource의 개수 (imageCount)만큼 for문을 돌린다.
    그래서 개별 이미지에 접근해서 CGImage를 우리가 사용할 UIImage로 바꾸고, images 배열에 넣어준다.
  • 그래서 만든 images 배열을 UIImage의 animatedImage에 넣어서 애니메이션으로 만들고,
    그걸 UIImageView의 image 프로퍼티에 넣어주면 된다.
    네이밍만 보면 image 프로퍼티에 1개 이미지만 넣어야할 것 같은데 아니었음 🫤
  • animatedImage를 만들 때 duration을 지정해주는데, "애니메이션 1 cycle이 지속될 시간"이라고 보면 된다.
    즉, 첫번째 이미지부터 마지막 이미지까지를 몇 초 이내에 보여줄지이다. (해보니까 완전히 딱 떨어지지는 않음)
    RefreshControl에 넣을 거면 1초가 적당하다.

2. RefreshControl에다가 Gif 올리기

위에서 만든 ImageView를 RefreshControl에다가 올려주자.

final class SimpleRefreshControl: UIRefreshControl { // UIRefreshControl 상속
    override init() {
        super.init()
        
        configureLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureLayout() {
        // ✅ 위에서 만든 UIImageView init 활용
        // bundle에 넣은 gif 파일이름 입력 ("simpson")
        guard let loadingImageView = UIImageView(gifNamed: "simpson") else { return }
        
        // ✅ RefreshControl의 subview로 추가
        addSubview(loadingImageView) 
        loadingImageView.snp.makeConstraints { make in
            make.center.equalToSuperview()
            make.width.equalTo(63.0)
            make.height.equalTo(45.0)
        }
    }
}
  • Custom RefreshControl 클래스를 만든다.
  • 위에서 만든 UIImageView initializer를 활용한다.
    gif 파일이름 "simpson"을 넣어서 loadingImageView를 초기화했다.
  • 그다음 RefreshControl 위에 addSubview를 하고, constraints를 설정한다.
    imageView 크기가 너무 크지 않으면 center 정렬로 맞추면 된다.

3. CollectionView에다가 RefreshControl 넣기

ScrollView 또는 CollectionView에 이미 refreshControl 프로퍼티가 있다.

그래서 refreshControl을 만들어서 거기에 넣어주면 끝! 매우 간단하다.

 

ActivityControl과 다르게 startAnimating 같은 걸 호출할 필요도 없다.

사용자가 최상단에서 스크롤을 내리면 자동으로 실행된다.

실행을 멈출 때 refreshControl.endRefreshing() 만 호출하면 끝!

final class MainViewController: UIViewController {
    // ✅ 아까 만든 custom refreshControl
    private lazy var refreshControl: SimpleRefreshControl = {
        let refreshControl = SimpleRefreshControl()
        refreshControl.addTarget(self, action: #selector(pulledToRefresh), for: .valueChanged) 
        return refreshControl
    }()
    
    private lazy var mainCollectionView: UICollectionView = {
        let flowLayout = createListFlowLayout()
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
        collectionView.refreshControl = refreshControl // ✅ 참 쉽죠?
        collectionView.dataSource = self
        collectionView.register(MainCell.self, forCellWithReuseIdentifier: "MainCell")
        return collectionView
    }()
    
    // ✅ pull 했을 때 할 일
    @objc private func pulledToRefresh() {
        print("!!!Refresh!!!")
        
//        mainViewModel.reset() // ViewModel 통해 API 재요청 (코드 생략)
        didReceiveResponse()    // API 호출 완료됨 (가정)
    }
    
    func didReceiveResponse() {
        // ViewModel 통해 API response 확인한 시점에 숨기기 (코드 생략)
        // ✅ API 요청 후 3초 뒤 response가 도착함 (가정)
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in 
            // 애니메이션 1cycle을 1초로 설정했으므로 약 3cycle이 실행됨
            self?.refreshControl.endRefreshing() 
        }
    }
}
  • RefreshControl을 CollectionView에 넣고, PTR에 할 일을 pulledToRefresh()에 구현하면 된다.
  • 보통 PTR할 때 ViewModel을 통해 API를 재요청하고,
    API Response가 도착하면 refreshControl.endRefreshing()을 호출해서 숨겨준다.
  • didReceiveResponse 메서드를 보자.
    예제에서 API를 구현하지 않았기 때문에 DispatchQueue의 asyncAfter 메서드를 임시로 호출했다.
    deadline 매개변수로 .now() + 3.0을 넣어줬는데
    UIImageView에서 animationDuration을 1초 (애니메이션 1cycle = 1초)로 설정했으므로
    refreshControl이 1번 나타날 때 애니메이션은 약 3cycle이 실행된다. (해보니까 실제로는 3.5 cycle 정도 실행됨)

🍎전체 코드는 GitHub Repo를 참고

 

만약 imageURL로 Gif 띄우기 등을 하고 싶다면

kaishin/Gifu 라이브러리가 유명한듯

 

구글링 키워드 : load gif image on refreshControl

Stack overflow - Add animated Gif image in Iphone UIImageView

Stack overflow - How to load GIF image in Swift?

 

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments