【Swift 3】UICollectionViewでreloadDataをする時にアニメーションを追加する【CATransition】

どもども。Reoです。

UICollectionViewでreloadData()をする際にアニメーションを追加する方法をようやく見つけたので、紹介します。トランジションといったほうがいいのか。

ページング等での切り替えではなくコレクションビューをリロードした際にページが変わったように見せるといった感じです。以下のような感じ。

上下に何かviewを上から置いていれば、いい感じに見えるはず。

今回は紹介のためにわりと適当にデモを作ってみましたが、実際はここにカレンダーをおいて上下スワイプで月を切り替える際に用いました。

 

先に全体コードを載せてからちょろっと説明します。

結構雑にかいたのであんまりいい書き方ではないかもしれませんがご了承ください・・・(´・ω・`)

全体コード

ViewController.swift

//
//  ViewController.swift
//  CollectionViewAnimation
//
//  Created by Reo on 2017/04/14.
//  Copyright © 2017年 Reo. All rights reserved.
//

import UIKit

class ViewController: UIViewController ,UICollectionViewDelegate,UICollectionViewDataSource{
    
    private var collectionView:UICollectionView!
    private var backgroundColor = UIColor.white
    private var reloadCount = 0


    override func viewDidLoad() {
        super.viewDidLoad()
        
        let width = self.view.frame.width
        
        //適当にコレクションビューを配置
        let layout = UICollectionViewFlowLayout()
        layout.itemSize =  CGSize(width:50,height:50)
        layout.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0)
        
        collectionView = UICollectionView(frame: CGRect(x:0,y:40,width:width,height:320),
                                          collectionViewLayout: layout)
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.delegate = self
        collectionView.dataSource = self
        self.view.addSubview(collectionView)
        
        
        //ジェスチャーをつける
        let swipeUp = UISwipeGestureRecognizer()
        swipeUp.direction = .up
        swipeUp.addTarget(self, action: #selector(self.swipeAction(sender:)))
        self.view.addGestureRecognizer(swipeUp)
        
        let swipeDown = UISwipeGestureRecognizer()
        swipeDown.direction = .down
        swipeDown.addTarget(self,action:#selector(self.swipeAction(sender:)))
        self.view.addGestureRecognizer(swipeDown)
        
        let swipeLeft = UISwipeGestureRecognizer()
        swipeLeft.direction = .left
        swipeLeft.addTarget(self,action:#selector(self.swipeAction(sender:)))
        self.view.addGestureRecognizer(swipeLeft)
        
        let swipeRight = UISwipeGestureRecognizer()
        swipeRight.direction = .right
        swipeRight.addTarget(self,action:#selector(self.swipeAction(sender:)))
        self.view.addGestureRecognizer(swipeRight)

    }
    
    //スワイプした時のアクション
    func swipeAction(sender:UISwipeGestureRecognizer){
        //向きごとに変える
        switch sender.direction{
        case UISwipeGestureRecognizerDirection.up:
            reloadCount += 1
            self.collectionView.layer.add(swipeTransition(tag: 0), forKey: nil)
        case UISwipeGestureRecognizerDirection.down:
            reloadCount -= 1
            self.collectionView.layer.add(swipeTransition(tag: 1), forKey: nil)
        case UISwipeGestureRecognizerDirection.left:
            backgroundColor = UIColor.red
            self.collectionView.layer.add(swipeTransition(tag: 2), forKey: nil)
        case UISwipeGestureRecognizerDirection.right:
            backgroundColor = UIColor.yellow
            self.collectionView.layer.add(swipeTransition(tag: 3), forKey: nil)
        default:break
        }
        
        self.collectionView.reloadData()
    }
    
    //更新時のアニメーション
    func swipeTransition(tag:Int) -> CATransition{
        let transition:CATransition = CATransition()
        transition.startProgress = 0
        transition.endProgress = 1.0
        transition.type = kCATransitionPush
        switch tag{
        case 0:
            transition.subtype = kCATransitionFromTop
        case 1:
            transition.subtype = kCATransitionFromBottom
        case 2:
            transition.subtype = kCATransitionFromRight
        case 3:
            transition.subtype = kCATransitionFromLeft
        default:
            transition.subtype = kCATransitionFromTop
        }
        transition.duration = 0.3
        return transition
        
    }
    
    //セルの数
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 30
    }
    
    //セルの内容
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
        
        cell.textLabel.text = "\(reloadCount)"
        cell.contentView.backgroundColor = backgroundColor
        
        return cell
    }
    
    
    //セルを選択した時
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

CustomCell.swift

import UIKit

class CustomCell: UICollectionViewCell {
    
    public var textLabel:UILabel!
    
    override init(frame:CGRect){
        super.init(frame:frame)
        
        //日付ラベル
        textLabel = UILabel()
        textLabel.frame = CGRect(x:0,y:0,width:self.frame.height,height:self.frame.height)
        textLabel.center = CGPoint(x:self.frame.width / 2,y:self.frame.height / 2)
        textLabel.textAlignment = .center
        textLabel.font = UIFont.systemFont(ofSize: 14)
        textLabel.textColor = UIColor.blue
        self.contentView.addSubview(textLabel)
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

 

上にスワイプしたときには、数字を1プラス

下にスワイプしたときには、数字を1マイナス

左右はセルの背景色が変わるようにしました。

 

コレクションビューをリロードする際にアニメーションをつける

上記はとりあえず全方向にアニメーションをつけてみましたが、場合分けしてるだけなので一方向部分だけ簡単に紹介したいと思います。

上にスワイプした場合

        //ジェスチャーをつける
        let swipeUp = UISwipeGestureRecognizer()
        swipeUp.direction = .up
        swipeUp.addTarget(self, action: #selector(self.swipeAction(sender:)))
        self.view.addGestureRecognizer(swipeUp)

まずはジェスチャーをつけます。

そしてトランジションを作成します。

    //更新時のアニメーションを作成
    func swipeTransition(tag:Int) -> CATransition{
        let transition:CATransition = CATransition()
        transition.startProgress = 0
        transition.endProgress = 1.0

        //push・moveIn・Reveal・Fadeから選択
        transition.type = kCATransitionPush

        //どちら側からトランジションするかを選択
        transition.subtype = kCATransitionFromTop

        //トランジションにどれだけ時間をかけるか
        transition.duration = 0.3
        return transition
    }

 

アニメーション部分は以前書いたこちらの記事とほぼ同様な感じです。

【Swift 3】UINavigationControllerを用いた画面遷移のアニメーション変更【CATransition】

 

上にスワイプした際のアクションを書いていきます。

    //スワイプした時のアクション
    func swipeAction(sender:UISwipeGestureRecognizer){
        //適当にセルの中身を変える処理(カレンダーだと次の月に変えるとかとか)
        reloadCount += 1

        //コレクションビューのレイヤーにアニメーションを追加する
        self.collectionView.layer.add(swipeTransition(tag: 0), forKey: nil)

        //コレクションビューをリロードする
        self.collectionView.reloadData()
    }

 

先ほど作成したアニメーションをコレクションビューのレイヤーに追加してやります。

 

こんな感じです+:。ヾ(o・ω・)ノ゜.+

意外と簡単・・・!

 

もうちょっとアニメーションの速度に緩急つけたりした方がいい感じになるかもしれないですが、とりあえずはこんな感じで実装しました。

 

私がやりたかったのはコレクションビューを用いたカレンダーの無限(?)切り替えで、前後の月のカレンダーが見えて欲しかったんですね。reloadDataだとパッと切り替わってしまい、スワイプで移動している感覚もなくて正直めちゃくちゃ悩みました。

スクロールビューの上に3つのせてどうやらとかも考えましたが、あんまりview自体も増やしたくなかったので、どうにかアニメーションで誤魔化したかったのです。

本来はスクロールビューを使ったやり方のが一般的なのかなぁ・・・。わからない。

 

にしてもリロード前のセルとリロード後のセルの両方が見えているのが、アニメーションすげえ・・・って感じです。

もっとカスタマイズとかして使えるようになりたいところですね。

 

それではでは!

めちゃくちゃ色々探し回ってたどり着いたので本当に感謝・・・

 

2018/04/14追記 Gistにあげました。
Swift4に対応してgistにあげときましたん。



Swift3からSwift4に対応するより、昔の書き方から今の書き方にする方が骨が折れる。(。-ω-。) 
コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS
more