【Swift × MVVM × RxSwift】Kingfisherで画像の切り替えの実装

# Kingfisherについて
Kingfisherは、URLから画像を簡単かつ高速にダウンロードし、キャッシュ機能を備えた柔軟な画像読み込みライブラリです。

 ## 参考文献
 https://github.com/onevcat/Kingfisher

 https://zenn.dev/shouheikuwata/articles/653acddcfc123a

 ## 実際に使ってみる

 #### 完成イメージ
写真① | 写真②
:--: | :--:
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1789173/39b60ab0-cda9-4144-8daf-740b753ab3bf.png" width="300" /> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1789173/fd1d4f2b-5a6c-4f07-b04f-1affd2b3e10b.png" width="300" />


ImageViewとButtonを用意します。
ボタンをタップしたら画像を切り替えるようにしてます。写真にはフリー素材のURLを使用しました。
※アプリ内で使用している画像は [Unsplash](https://unsplash.com/) から取得しています。

MVVM × RxSwiftでリアクティブに切り替えを行うよう書込みました。



### 1️⃣ImageURL(Model)
画像URLを列挙型で管理してます。
 ```Swift: ImageURL
 // 画像URLを管理するためのEnum
enum ImageURL {
    case firstImage
    case secondImage

    var url: String {
        switch self {
        case .firstImage:
            return "画像１のURL"
        case .secondImage:
            return "画像２のURL"
        }
    }
}
```

### 2️⃣ImageURLViewModel(ViewModel)
View側でのボタンタップ検知と、画像URLの取得・反映を担う橋渡し役です。

```Swift: ImageURLViewModel
import Foundation
import RxSwift
import RxCocoa

protocol ImageURLViewModelInputs: AnyObject {
    var buttonTap: PublishRelay<Void> { get }
}

protocol ImageURLViewModelOutputs: AnyObject {
    var imageViewURLString: Driver<String> { get }
}

protocol ImageURLViewModelType: AnyObject {
    var inputs: ImageURLViewModelInputs { get }
    var outputs: ImageURLViewModelOutputs { get }
}


class ImageURLViewModel: ImageURLViewModelInputs, ImageURLViewModelOutputs, ImageURLViewModelType {
    
    // MARK: - Properties
    var inputs: ImageURLViewModelInputs { return self }
    var outputs: ImageURLViewModelOutputs { return self }
    
    // MARK: - Input Sources
    let buttonTap = PublishRelay<Void>()
    
    // MARK: - Output Sources
    let imageViewURLString: Driver<String>
    
    private let _imageViewURLString = BehaviorRelay<String>(value: ImageURL.firstImage.url)
    private let disposeBag = DisposeBag()
    
    // MARK: - Initialize
    init() {
        self.imageViewURLString = _imageViewURLString.asDriver(onErrorDriveWith: .empty())
        
        buttonTap.asObservable()
            .withLatestFrom(_imageViewURLString)
            .map { currentURL in
                return currentURL == ImageURL.firstImage.url ? ImageURL.secondImage.url : ImageURL.firstImage.url
            }
            .bind(to: _imageViewURLString)
            .disposed(by: disposeBag)
    }
}

```

### 3️⃣ImageURLViewController(View)
実際に画像を反映させる場所です。こちらでKingfisherを使って非同期に画像を表示させるようにしてます。

```Swift: ImageURLViewController
import UIKit
import RxSwift
import RxCocoa
import Kingfisher

class ViewController: UIViewController {
    
    // MARK: - Properties
    @IBOutlet private weak var imageView: UIImageView!
    @IBOutlet private weak var toggleImageButton: UIButton!
    let viewModel = ImageURLViewModel()
    private let disposeBag = DisposeBag()
    
    // MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        bind()
    }
}

// MARK: - Binding
extension ViewController {
    func bind() {
        
        toggleImageButton.rx.tap
            .subscribe(onNext: {
                self.viewModel.inputs.buttonTap.accept(())
            })
            .disposed(by: disposeBag)
        
        viewModel.outputs.imageViewURLString
            .drive(onNext: { imageURL in
                guard let url = URL(string: imageURL) else { return }
                self.imageView.kf.setImage(with: url)
            })
            .disposed(by: disposeBag)
    }
}

```

### プレースホルダーとは？
またKingfisherの一つの機能でプレースホルダーを指定することができます。
プレースホルダーとは**画像を読み込んでいる間に一時的に表示しておく仮の画像**のことです。

```
[表示の流れ]

① ボタンタップ
  ↓
② 画像読み込み中...（ここでプレースホルダー画像を表示）
  ↓
③ ネットから画像が届く
  ↓
④ 本物の画像に差し替え！
```

### プレースホルダーの指定方法
プレースホルダーはsetImage引数にplaceholderでURLを指定してあげることで簡単に使用できます。

```Swift
 viewModel.outputs.imageViewURLString
            .drive(onNext: { imageURL in
                guard let url = URL(string: imageURL) else { return }
                self.imageView.kf.setImage(with: url,
                                           placeholder: UIImage(named: "placeholder_image"))
            })
            .disposed(by: disposeBag)
```

## まとめ
Kingfisherを使うことでURLSessionを使用した非同期の画像取得に比べて手軽に実装ができます。
また様々な機能を備えているので使い勝手のいいライブラリだと思いました。