[iOS][Swift3.0] グリーティングカードを開くようなエフェクトが素晴らしいFoldingCell
FoldingCellはUITableViewCellを拡張して作られており、紙が折りたたまれるようなコンポーネントを実現することができます。 イメージはグリーティングカードをパタパタ開くような感じです。Ramotion Inc.という企業が作成しており、下記は企業ページに載っていた紹介のGIF画像です。
タップすると紙が開かれ、再度タップすると紙が閉じるようなエフェクトです。FoldingCellのライセンスはMITです。
検証環境
今回は下記環境で試しています。
Xcode | 8.2.1 |
---|---|
Swift | 3.0.2 |
CocoaPods | 1.0.0 |
準備
導入
CocoaPodsで追加します。
use_frameworks! target "ターゲット名" pod 'FoldingCell' end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |configuration| configuration.build_settings['SWIFT_VERSION'] = "3.0" end end end
画面の用意
今回使うTableViewを用意しておきます。StoryboardにUITableViewControllerを配置し、クラスファイルを作成します。また、TableViewのStyleはGroupedにしておきます。
実装
FoldingCell用のCellはStoryboardまたはnibファイル、どちらでも作成できます。(今回はStoryboardの方に作成しました) 簡単に概要を書くと、セルの中に閉じている時のViewと開いている時のViewの両方を作成します。
FoldingCellを継承したクラスの作成
FoldingCellを継承したCellクラスを作成します。func animationDuration(_:type:)はoverrideして、値を返さないとエラーになりますが、他は特に何もしなくても大丈夫でした。下記コードではbackgroundColorを透明にしてますが、見た目(デザイン)の問題で必須ではありません。
import UIKit import FoldingCell class SampleCell: FoldingCell { override func awakeFromNib() { super.awakeFromNib() backgroundColor = UIColor.clear } override func animationDuration(_ itemIndex:NSInteger, type:AnimationType)-> TimeInterval { let durations = [0.26, 0.2, 0.2] return durations[itemIndex] } }
カスタムクラスの設定
上記で作成したクラス(ここではSampleCell)をCellに設定します。
Viewの配置
Cellの高さを適宜ひろげて、閉じた時と開いた時用のViewを配置します。
わかりやすいように、閉じている時のViewを水色、開いている時のViewをピンク色にしています。
閉じている時のViewにカスタムクラスを設定
閉じている時のView(画像の水色の方)のClassにRotatedViewを設定します。(開いている時のView(ピンクの方)は設定しなくて大丈夫です)
配置の制約をつける
閉じている時のView
Top SpaceとTrailing Space、Leading SpaceとHeight(高さ)に制約を付けます。
開いている時のView
こちらも同じように、Top SpaceとTrailing Space、Leading SpaceとHeight(高さ)に制約を付けます。ここで注意するのは、Top SpaceはSuperViewからのスペースにする必要があります。
Outlet接続
セルと各View、各ViewのTop Space制約をOutletで接続します。
接続するもの | 接続先変数名 | タイプ |
---|---|---|
閉じている時のView | foregroundView | RotatedView |
開いている時のView | containerView | UIView |
閉じている時のViewのTop Space制約 | foregroundViewTop | NSLayoutConstraint |
開いている時のViewのTop Space制約 | containerViewTop | NSLayoutConstraint |
開いている時のView(containerView)内にViewを配置する
パタパタと開くためのViewをcontainerView内に配置します。
今回は4つのViewを配置しました。(わかりやすいように、それぞれ背景色を変えています)
これらにレイアウトの制約を付けます。上下左右と高さを付けます、それぞのViewの間は0にしました。
containerView内のViewにカスタムクラスの設定する
上記で追加したViewにカスタムクラスとしてRotatedViewを設定しますが、ここで注意するのが、偶数番目に配置したViewのみ設定します。今回は2番めと4番めのViewが該当します。
Cellのプロパティ設定
FoldingCellのプロパティを設定します。
item Countにはパタパタ用に追加したViewの数(今回は4)、Back View Colorにはパタパタした時の裏側の色を設定します。(デフォルトだと茶色になります)
また、CellにIdentifierをつけておきます。
UITableViewControllerのコード
閉じた時と開いた時のセルの高さを定義する
それぞれの高さ+上下の余白分の高さをそれぞれ指定しましょう。
private let closeCellHeight: CGFloat = 96 private let openCellHeight: CGFloat = 328
セルの高さを保持しておく変数を定義する
それぞれのセルに対して高さの状態を保持するように配列を定義します。この配列の初期値は閉じている高さ(closeCellHeight)にしておきます。
private var cellHeights: [CGFloat] = [] override func viewDidLoad() { super.viewDidLoad() cellHeights = Array.init(repeating: closeCellHeight, count: cellCount) // cellCountはTableViewに表示するリストの件数 }
タップした時の処理
セルが選択されたタイミングで高さを見て開いたり閉じたりします。
cell.selectedAnimation(true, animated: true, completion: nil)
で開いた状態に、
cell.selectedAnimation(false, animated: true, completion: nil)
で閉じた状態になります。
サンプルコード
import UIKit class SampleTableViewController: UITableViewController { private let closeCellHeight: CGFloat = 96 private let openCellHeight: CGFloat = 328 private let cellCount = 3 private var cellHeights: [CGFloat] = [] override func viewDidLoad() { super.viewDidLoad() cellHeights = Array.init(repeating: closeCellHeight, count: cellCount) tableView.backgroundColor = UIColor.gray } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cellCount } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return cellHeights[indexPath.row] } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? SampleCell else { fatalError("Could not create SampleCell") } return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard case let cell as SampleCell = tableView.cellForRow(at: indexPath) else { return } var duration = 0.0 if cellHeights[indexPath.row] == closeCellHeight { // open cell cellHeights[indexPath.row] = openCellHeight cell.selectedAnimation(true, animated: true, completion: nil) duration = 0.5 } else {// close cell cellHeights[indexPath.row] = closeCellHeight cell.selectedAnimation(false, animated: true, completion: nil) duration = 1.1 } UIView.animate(withDuration: duration, delay: 0, options: .curveEaseOut, animations: { _ in tableView.beginUpdates() tableView.endUpdates() }, completion: nil) } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if case let cell as SampleCell = cell { if cellHeights[indexPath.row] == closeCellHeight { cell.selectedAnimation(false, animated: false, completion:nil) } else { cell.selectedAnimation(true, animated: false, completion: nil) } } } }
実行結果
何も配置していないので見た目は微妙ですが、折りたたむ表現を入れることができました。
さいごに
FoldingCellを使うと、今まで画面を遷移して詳細を表示していたものが、同じ画面内で詳細を表示する導線に変更することができるなと思いました。
高さを動的にするのは無理そうな感じですが、表示量がそこまで多くなくて固定でレイアウトを作れるものであれば、ぴったりと合うのではないでしょうか。
Android版も公開されています✨
https://github.com/Ramotion/folding-cell-android