Swift:インスタグラムのいいね機能っぽいのをIGListKitで実装する。

iosでの開発で、いいね機能（のようなもの）を実装する際に
`collectionView.reloadData()`
を利用しておりましたが、再生中の動画も止まってしまうためcell内の差分のみ抽出し更新を行う`IGListKit`を今回使用しました。備忘録を含め記録として残したいと思います。

#今回の記事でできること。

動画再生中にButtonクラスなどのイベントを発生した際に差分のみ抽出し、collectionViewの更新を行うことができます。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.amazonaws.com/0/226787/394c46ba-e181-9249-073a-016e2c712eff.gif)



今回使用するIGListKitのライブラリです。
https://github.com/instagram/IGListKit

ドキュメントです。
https://instagram.github.io/IGListKit/index.html

また、公式のサンプルコードもあります。
https://github.com/Instagram/IGListKit/tree/master/Examples


私のような初心者向けの方に対して記録として残しております。
拙い文章で恐縮ですから、コードをご覧いただく方が早い方は、本記事のサンプルコートをGitHubにまとめております。

https://github.com/Atsushi-Koyama/AVPlaterIGListKit
ご参照いただければ幸いです。

また今回下記の素晴らしい記事を参考に実装しております。
https://qiita.com/_atsushisakai/items/524f2a48e7f671ef61d8#model
本当にありがとうございます。

#サンプル実装

##Feedの表示

###Modelの定義

https://github.com/Atsushi-Koyama/AVPlaterIGListKit/blob/master/SampleIGListKit/PostData.swift

```Swift:Model
import UIKit
import IGListKit

final class PostData: NSObject {
    var name: String?
    var image: UIImage?
    var movieURL: String?
    var isLiked: Bool = false
    
    init(name: String, image: UIImage, movieURL: String) {
        self.name = name
        self.image = image
        self.movieURL = movieURL
    }
}

//オブジェクトはListDiffableに準拠する必要があります。
extension NSObject: ListDiffable {
    public func diffIdentifier() -> NSObjectProtocol {
        return self
    }
    
    public func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        return isEqual(object)
    }
}
```

###SectionController

https://github.com/Atsushi-Koyama/AVPlaterIGListKit/blob/master/SampleIGListKit/AVSectionController.swift

```Swift:SectionController
import UIKit
import IGListKit

class AVSectionController: ListSectionController, ListDisplayDelegate {
    

    override init() {
        super.init()
        displayDelegate = self
    }
    
    var postData: PostData?
    
    override func numberOfItems() -> Int {
        return 1
    }
    
    //セルのサイズの定義
    override func sizeForItem(at index: Int) -> CGSize {
        return CGSize(width: UIScreen.main.bounds.size.width / 1.0, height: UIScreen.main.bounds.size.width / 0.6)
    }
    
    override func cellForItem(at index: Int) -> UICollectionViewCell {
        if let cell = collectionContext!.dequeueReusableCellFromStoryboard(withIdentifier: "Cell", for: self, at: index) as? PostTableViewCell {
            
            //セルのお気に入りボタンを押下した際の処理
            cell.likeButton.addTarget(self, action: #selector(handleLikeButton(_:forEvent:)), for: .touchUpInside)
            
            cell.setAVPostData(postData!)
            
            return cell
        }
        
        return UICollectionViewCell(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
    }
    
    override func didUpdate(to object: Any) {
        postData = object as? PostData
    }
    
    @objc func handleLikeButton(_ sender: UIButton, forEvent event: UIEvent) {
        
        if let likeButton = sender as? UIButton {
            
            if postData?.isLiked == false {
                let buttonImage = UIImage(named: "star_exist")
                likeButton.setImage(buttonImage, for: .normal)
                postData?.isLiked = true
            }else{
                let buttonImage = UIImage(named: "star_none")
                likeButton.setImage(buttonImage, for: .normal)
                postData?.isLiked = false
            }
        }
    }
    
    //セルの選択時の処理
    override func didSelectItem(at index: Int) {
    }
    
    //セルの表示される時の実装
    func listAdapter(_ listAdapter: ListAdapter, willDisplay sectionController: ListSectionController, cell: UICollectionViewCell, at index: Int) {
    }
    //セルの表示が終わる時の実装
    func listAdapter(_ listAdapter: ListAdapter, didEndDisplaying sectionController: ListSectionController, cell: UICollectionViewCell, at index: Int) {
    }
    
    func listAdapter(_ listAdapter: ListAdapter, willDisplay sectionController: ListSectionController) {
    }
    
    func listAdapter(_ listAdapter: ListAdapter, didEndDisplaying sectionController: ListSectionController) {
        
    }
}
```

cell内の処理などをは通常通りです。

```Swift:SectionController
//セルのお気に入りボタンを押下した際の処理
cell.likeButton.addTarget(self, action: #selector(handleLikeButton(_:forEvent:)), for: .touchUpInside)
```

collectionView.reloadData()などを使用しなくとも、以下のイベントがFeed上で変更されます。

```Swift:SectionController
@objc func handleLikeButton(_ sender: UIButton, forEvent event: UIEvent) {
        
        if let likeButton = sender as? UIButton {
            
            if postData?.isLiked == false {
                let buttonImage = UIImage(named: "star_exist")
                likeButton.setImage(buttonImage, for: .normal)
                postData?.isLiked = true
            }else{
                let buttonImage = UIImage(named: "star_none")
                likeButton.setImage(buttonImage, for: .normal)
                postData?.isLiked = false
            }
        }
    }
}
```


セルの表示時等にイベントを加えたい場合は以下に定義します。

```Swift:SectionController
//セルの表示される時の実装
func listAdapter(_ listAdapter: ListAdapter, willDisplay sectionController: ListSectionController, cell: UICollectionViewCell, at index: Int) {
}
//セルの表示が終わる時の実装
func listAdapter(_ listAdapter: ListAdapter, didEndDisplaying sectionController: ListSectionController, cell: UICollectionViewCell, at index: Int) {
}
```


###ViewController

https://github.com/Atsushi-Koyama/AVPlaterIGListKit/blob/master/SampleIGListKit/ViewController.swift

```Swift:ViewController

import UIKit
import IGListKit

class ViewController: UIViewController, ListAdapterDataSource {
    
    @IBOutlet weak var collectionView: UICollectionView!
   
    var postArray: [PostData] = []
   
    var postData: PostData!
    
    var adapter: ListAdapter?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let image1:UIImage = UIImage(named:"あ.jpg")!
        let image2:UIImage = UIImage(named:"い.jpg")!
        let image3:UIImage = UIImage(named:"う.jpg")!
        
        let postData1 = PostData(name: "Sample1", image: image1, movieURL: "https://s3-ap-northeast-1.amazonaws.com/bimojiapp-deployments-mobilehub-1052823102/%E3%81%82.mp4")
    
        let postData2 = PostData(name: "Sample2", image: image2, movieURL: "https://s3-ap-northeast-1.amazonaws.com/bimojiapp-deployments-mobilehub-1052823102/%E3%81%84.mp4")
        
        let postData3 = PostData(name: "Sample3", image: image3, movieURL: "https://s3-ap-northeast-1.amazonaws.com/bimojiapp-deployments-mobilehub-1052823102/%E3%81%86.mp4")
        
        postArray.append(postData1)
        postArray.append(postData2)
        postArray.append(postData3)
        
        //workingRangeSizeはcollectionViewに表示準備の範囲。数字が大きいほど負担がかかると思われます。
        self.adapter = ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 1)
        self.adapter?.collectionView = self.collectionView
        self.adapter?.dataSource = self
        
        nibSet()

    }
    
    func nibSet(){
        collectionView.register(UINib(nibName: "PostTableViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
    }
    

    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        return postArray
    }
    
    
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        return AVSectionController()
    }
    
    func emptyView(for listAdapter: ListAdapter) -> UIView? {
        return nil
    }
}
```

また、以下のコードの部分をif文で分岐させることで、複数のSectionControllerの指定も可能です。（画像と動画で分けたい場合など）


```Swift:ViewController
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        return AVSectionController()
}
```

#まとめ
今回はButtonクラスの変更を反映させましたが
コメントなどの追加を反映させる際にも利用ができそうです。

何かご不明な点がある場合はご連絡いただけると幸いです。

また、今回IGListKitを使用して実装したアプリとなります。
https://itunes.apple.com/us/app/id1415484408

最後までありがとうございました。