Last active
November 21, 2018 08:14
-
-
Save fumiyasac/5c9c6aa515fc21002b8be12ddef779be to your computer and use it in GitHub Desktop.
できるだけUI系のライブラリを用いないアニメーションを盛り込んだサンプル実装まとめ(後編) ref: https://qiita.com/fumiyasac@github/items/b694f9859cbb61c95c1a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class StoryPictureViewController: UIViewController { | |
//UI部品の配置 | |
@IBOutlet weak fileprivate var storyPictureScrollView: UIScrollView! | |
@IBOutlet weak fileprivate var storyPictureImageView: UIImageView! | |
@IBOutlet weak private var storyPictureCloseButton: UIButton! | |
//UIScrollViewの中にあるUIImageViewの上下左右の制約 | |
@IBOutlet weak fileprivate var storyPictureImageTopConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageBottomConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageLeftConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageRightConstraint: NSLayoutConstraint! | |
private var targetStoryPicture: Photo? = nil { | |
didSet { | |
if let photo = targetStoryPicture { | |
self.storyPictureImageView.image = photo.imageData | |
self.setStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
} | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setupStoryPictureScrollView() | |
setupStoryDetailBackButton() | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
//画像スライダー用のUIScrollView等の初期設定を行う | |
setStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
} | |
//MARK: - Function | |
func setPicture(_ photo: Photo) { | |
targetStoryPicture = photo | |
} | |
//MARK: - Private Function | |
//一覧に戻るボタンをタップした際に実行されるアクションとの紐付け | |
@objc private func onTouchUpInsideStoryPictureCloseButton() { | |
self.dismiss(animated: true, completion: nil) | |
} | |
//Story用の写真を配置したUIScrollViewの初期設定 | |
private func setupStoryPictureScrollView() { | |
storyPictureScrollView.delegate = self | |
} | |
//Story用の写真の拡大縮小比を設定する | |
private func setStoryPictureImageViewScale(_ size: CGSize) { | |
//self.viewのサイズを元にUIImageViewに表示する画像の縦横サイズの比を取り、小さい方を適用する | |
let widthScale = size.width / storyPictureImageView.bounds.width | |
let heightScale = size.height / storyPictureImageView.bounds.height | |
let minScale = min(widthScale, heightScale) | |
//最小の拡大縮小比 | |
storyPictureScrollView.minimumZoomScale = minScale | |
//現在時点での拡大縮小比 | |
storyPictureScrollView.zoomScale = minScale | |
} | |
//戻るボタンの設定を行う | |
private func setupStoryDetailBackButton() { | |
storyPictureCloseButton.addTarget(self, action: #selector(self.onTouchUpInsideStoryPictureCloseButton), for: .touchUpInside) | |
} | |
} | |
//MARK: - UIScrollViewDelegate | |
extension StoryPictureViewController: UIScrollViewDelegate { | |
//(重要)UIScrollViewのデリゲートメソッドの一覧: | |
//参考にした記事:よく使うデリゲートのテンプレート: | |
//https://qiita.com/hoshi005/items/92771d82857e08460e5c | |
//ズーム中に実行されてズームの値に対応する要素を返すメソッド | |
func viewForZooming(in scrollView: UIScrollView) -> UIView? { | |
return storyPictureImageView | |
} | |
//ズームしたら呼び出されるメソッド ※UIScrollView内のUIImageViewの制約を更新する為に使用する | |
func scrollViewDidZoom(_ scrollView: UIScrollView) { | |
updateStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
//UIScrollViewの中で拡大・縮小の動きに合わせて、中のUIImageViewの大きさを変更する | |
private func updateStoryPictureImageViewScale(_ size: CGSize) { | |
//Y軸方向のAutoLayoutの制約を加算する | |
let yOffset = max(0, (size.height - storyPictureImageView.frame.height) / 2) | |
storyPictureImageTopConstraint.constant = yOffset | |
storyPictureImageBottomConstraint.constant = yOffset | |
//X軸方向のAutoLayoutの制約を加算する | |
let xOffset = max(0, (size.width - storyPictureImageView.frame.width) / 2) | |
storyPictureImageLeftConstraint.constant = xOffset | |
storyPictureImageRightConstraint.constant = xOffset | |
view.layoutIfNeeded() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
class FlipDismissCustomTransition: NSObject { | |
//トランジション(実行)の秒数 | |
fileprivate let duration: TimeInterval = 0.72 | |
//ディレイ(遅延)の秒数 | |
fileprivate let delay: TimeInterval = 0.00 | |
} | |
extension FlipDismissCustomTransition: UIViewControllerAnimatedTransitioning { | |
//アニメーションの時間を定義する | |
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
return duration | |
} | |
//アニメーションの実装を定義する | |
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { | |
//コンテキストを元にViewControllerのインスタンスを取得する(存在しない場合は処理を終了) | |
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { | |
return | |
} | |
guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { | |
return | |
} | |
//アニメーションの実態となるコンテナビューを作成する | |
let containerView = transitionContext.containerView | |
//遷移後のframe(大きさと位置)を定義する | |
let finalFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) | |
//UIViewのスナップショットを取得する | |
guard let snapshotView = fromViewController.view.snapshotView(afterScreenUpdates: true) else { | |
return | |
} | |
//スナップショットの設定 | |
snapshotView.layer.masksToBounds = true | |
//コンテナビューの中に遷移先のViewControllerを配置し、更にその上にスナップショットのViewを配置する | |
containerView.addSubview(toViewController.view) | |
containerView.addSubview(snapshotView) | |
//遷移元のViewControllerは非表示の状態にしておく | |
fromViewController.view.isHidden = true | |
//CoreAnimationを用いて回転して切り替える処理を登録しておく ※FlipPresentCustomTransition.swiftとほぼ同じ | |
//コンテナビューに適用するパースペクティブを設定する | |
var perspectiveTransform = CATransform3DIdentity | |
perspectiveTransform.m34 = -0.002 | |
containerView.layer.sublayerTransform = perspectiveTransform | |
//X軸に対して-90°(-π/2ラジアン)回転させる | |
toViewController.view.layer.transform = CATransform3DMakeRotation(CGFloat(-Double.pi / 2), 0.0, 1.0, 0.0) | |
//アニメーションを実行する秒数設定する | |
let targetDuration = transitionDuration(using: transitionContext) | |
//キーフレームアニメーションを設定する | |
UIView.animateKeyframes(withDuration: targetDuration, delay: delay, options: .calculationModeCubic, animations: { | |
//キーフレーム(1) | |
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/3, animations: { | |
snapshotView.frame = finalFrame | |
}) | |
//キーフレーム(2) | |
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3, animations: { | |
snapshotView.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi / 2), 0.0, 1.0, 0.0) | |
}) | |
//キーフレーム(3) | |
UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3, animations: { | |
toViewController.view.layer.transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0) | |
}) | |
}, completion: { _ in | |
//アニメーションが完了した際の処理を実行する | |
fromViewController.view.isHidden = false | |
snapshotView.removeFromSuperview() | |
transitionContext.completeTransition(!transitionContext.transitionWasCancelled) | |
}) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
class FlipPresentCustomTransition: NSObject { | |
//トランジション(実行)の秒数 | |
fileprivate let duration: TimeInterval = 0.72 | |
//ディレイ(遅延)の秒数 | |
fileprivate let delay: TimeInterval = 0.00 | |
} | |
extension FlipPresentCustomTransition: UIViewControllerAnimatedTransitioning { | |
//アニメーションの時間を定義する | |
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
return duration | |
} | |
/** | |
* アニメーションの実装を定義する | |
* この場合には画面遷移コンテキスト(UIViewControllerContextTransitioningを採用したオブジェクト) | |
* → 遷移元や遷移先のViewControllerやそのほか関連する情報が格納されているもの | |
*/ | |
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { | |
//コンテキストを元にViewControllerのインスタンスを取得する(存在しない場合は処理を終了) | |
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) else { | |
return | |
} | |
guard let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { | |
return | |
} | |
//アニメーションの実態となるコンテナビューを作成する | |
let containerView = transitionContext.containerView | |
//遷移前と遷移後のframe(大きさと位置)を定義する | |
let initialFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) | |
let finalFrame = transitionContext.finalFrame(for: toViewController) | |
//UIViewのスナップショットを取得する | |
// (参考) snapshotViewに関する公式ドキュメント | |
// https://developer.apple.com/documentation/uikit/uiview/1622531-snapshotview | |
guard let snapshotView = toViewController.view.snapshotView(afterScreenUpdates: true) else { | |
return | |
} | |
//スナップショットの設定 | |
snapshotView.frame = initialFrame | |
snapshotView.layer.masksToBounds = true | |
//コンテナビューの中に遷移先のViewControllerを配置し、更にその上にスナップショットのViewを配置する | |
containerView.addSubview(toViewController.view) | |
containerView.addSubview(snapshotView) | |
//遷移先のViewControllerは非表示の状態にしておく | |
toViewController.view.isHidden = true | |
//CoreAnimationを用いて回転して切り替える処理を登録しておく | |
/** | |
* 今回のサンプルの動きに関しては下記の記事で紹介されているサンプルを元に実装している | |
* | |
* 参考: Custom UIViewController Transitions: Getting Started | |
* https://www.raywenderlich.com/170144/custom-uiviewcontroller-transitions-getting-started | |
*/ | |
//コンテナビューに適用するパースペクティブを設定する | |
/** | |
* 参考: [Objective-C] フリップアニメーションでビューを切り替える | |
* https://qiita.com/edo_m18/items/45fcbc67154eb68ef469 | |
*/ | |
var perspectiveTransform = CATransform3DIdentity | |
perspectiveTransform.m34 = -0.002 | |
containerView.layer.sublayerTransform = perspectiveTransform | |
//X軸に対して90°(π/2ラジアン)回転させる | |
/** | |
* 角度の計算を利用して裏返しをして画面遷移を行うようにする | |
* | |
* 参考1: 【iOS Swift入門 #233】表裏(両面)をもつViewを作る | |
* http://swift.swift-studying.com/entry/2015/07/18/115735 | |
* 参考2: Effect – UIViewで裏返せるパネルをつくる | |
* http://lepetit-prince.net/ios/?p=631 | |
*/ | |
snapshotView.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi / 2), 0.0, 1.0, 0.0) | |
//アニメーションを実行する秒数設定する | |
let targetDuration = transitionDuration(using: transitionContext) | |
//キーフレームアニメーションを設定する | |
UIView.animateKeyframes(withDuration: targetDuration, delay: delay, options: .calculationModeCubic, animations: { | |
//キーフレーム(1) | |
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1/3, animations: { | |
fromViewController.view.layer.transform = CATransform3DMakeRotation(CGFloat(-Double.pi / 2), 0.0, 1.0, 0.0) | |
}) | |
//キーフレーム(2) | |
UIView.addKeyframe(withRelativeStartTime: 1/3, relativeDuration: 1/3, animations: { | |
snapshotView.layer.transform = CATransform3DMakeRotation(0.0, 0.0, 1.0, 0.0) | |
}) | |
//キーフレーム(3) | |
UIView.addKeyframe(withRelativeStartTime: 2/3, relativeDuration: 1/3, animations: { | |
snapshotView.frame = finalFrame | |
}) | |
}, completion: { _ in | |
//アニメーションが完了した際の処理を実行する | |
toViewController.view.isHidden = false | |
snapshotView.removeFromSuperview() | |
transitionContext.completeTransition(!transitionContext.transitionWasCancelled) | |
}) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
//NSObjectProtocolの拡張 | |
extension NSObjectProtocol { | |
//クラス名を返す変数"className"を返す | |
static var className: String { | |
return String(describing: self) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class StoryPageViewController: UIViewController { | |
・・・(省略)・・・ | |
//ContainerViewにEmbedしたUIPageViewControllerのインスタンスを保持する | |
fileprivate var pageViewController: UIPageViewController? | |
//ページングして表示させるViewControllerを保持する配列 | |
fileprivate var storyViewControllerLists = [StoryViewController]() | |
//Storyデータを格納するための変数 | |
fileprivate var storyContents: [Story] = [] { | |
didSet { | |
self.setupStoryViewControllerLists() | |
self.setupPageViewController() | |
} | |
} | |
//StoryPresenterに設定したプロトコルを適用するための変数 | |
fileprivate var presenter: StoryPresenter! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
・・・(省略)・・・ | |
setupStoryPresenter() | |
} | |
・・・(省略)・・・ | |
//Presenterとの接続に関する設定を行う | |
private func setupStoryPresenter() { | |
presenter = StoryPresenter(presenter: self) | |
presenter.getStory() | |
} | |
private func setupPageViewController() { | |
//ContainerViewにEmbedしたUIPageViewControllerを取得する | |
pageViewController = childViewControllers[0] as? UIPageViewController | |
//UIPageViewControllerのデータソースの宣言 | |
pageViewController!.delegate = self | |
pageViewController!.dataSource = self | |
//MEMO: UIPageViewControllerでUIScrollViewDelegateが欲しい場合はこのように適用する | |
//for view in pageViewController!.view.subviews { | |
// if let scrollView = view as? UIScrollView { | |
// scrollView.delegate = self | |
// } | |
//} | |
//最初に表示する画面として配列の先頭のViewControllerを設定する | |
pageViewController!.setViewControllers([storyViewControllerLists[0]], direction: .forward, animated: false, completion: nil) | |
} | |
//Storyboard上に配置したViewController(StoryboardID = StoryViewController)をインスタンス化して配列に追加する | |
private func setupStoryViewControllerLists() { | |
for index in 0..<storyContents.count { | |
let storyboard: UIStoryboard = UIStoryboard(name: "Story", bundle: Bundle.main) | |
let storyViewController = storyboard.instantiateViewController(withIdentifier: "StoryViewController") as! StoryViewController | |
//「タグ番号 = インデックスの値」でスワイプ完了時にどのViewControllerかを判別できるようにする & ストーリーデータをセットする | |
storyViewController.view.tag = index | |
storyViewController.setStoryCardView(storyContents[index]) | |
//storyViewControllerListsに追加する | |
storyViewControllerLists.append(storyViewController) | |
} | |
//StoryViewControllerの総数をセットする | |
totalIndexLabel.text = "\(storyContents.count)" | |
} | |
} | |
//MARK: - StoryPresenterProtocol | |
extension StoryPageViewController: StoryPresenterProtocol { | |
//表示するデータを取得した場合の処理 | |
func showStory(_ story: [Story]) { | |
storyContents = story | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class StoryPictureViewController: UIViewController { | |
//UI部品の配置 | |
@IBOutlet weak fileprivate var storyPictureScrollView: UIScrollView! | |
@IBOutlet weak fileprivate var storyPictureImageView: UIImageView! | |
@IBOutlet weak private var storyPictureCloseButton: UIButton! | |
//UIScrollViewの中にあるUIImageViewの上下左右の制約 | |
@IBOutlet weak fileprivate var storyPictureImageTopConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageBottomConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageLeftConstraint: NSLayoutConstraint! | |
@IBOutlet weak fileprivate var storyPictureImageRightConstraint: NSLayoutConstraint! | |
private var targetStoryPicture: Photo? = nil { | |
didSet { | |
if let photo = targetStoryPicture { | |
self.storyPictureImageView.image = photo.imageData | |
self.setStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
} | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setupStoryPictureScrollView() | |
setupStoryDetailBackButton() | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
//画像スライダー用のUIScrollView等の初期設定を行う | |
setStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
override func didReceiveMemoryWarning() { | |
super.didReceiveMemoryWarning() | |
} | |
//MARK: - Function | |
func setPicture(_ photo: Photo) { | |
targetStoryPicture = photo | |
} | |
//MARK: - Private Function | |
//一覧に戻るボタンをタップした際に実行されるアクションとの紐付け | |
@objc private func onTouchUpInsideStoryPictureCloseButton() { | |
self.dismiss(animated: true, completion: nil) | |
} | |
//Story用の写真を配置したUIScrollViewの初期設定 | |
private func setupStoryPictureScrollView() { | |
storyPictureScrollView.delegate = self | |
} | |
//Story用の写真の拡大縮小比を設定する | |
private func setStoryPictureImageViewScale(_ size: CGSize) { | |
//self.viewのサイズを元にUIImageViewに表示する画像の縦横サイズの比を取り、小さい方を適用する | |
let widthScale = size.width / storyPictureImageView.bounds.width | |
let heightScale = size.height / storyPictureImageView.bounds.height | |
let minScale = min(widthScale, heightScale) | |
//最小の拡大縮小比 | |
storyPictureScrollView.minimumZoomScale = minScale | |
//現在時点での拡大縮小比 | |
storyPictureScrollView.zoomScale = minScale | |
} | |
//戻るボタンの設定を行う | |
private func setupStoryDetailBackButton() { | |
storyPictureCloseButton.addTarget(self, action: #selector(self.onTouchUpInsideStoryPictureCloseButton), for: .touchUpInside) | |
} | |
} | |
//MARK: - UIScrollViewDelegate | |
extension StoryPictureViewController: UIScrollViewDelegate { | |
//(重要)UIScrollViewのデリゲートメソッドの一覧: | |
//参考にした記事:よく使うデリゲートのテンプレート: | |
//https://qiita.com/hoshi005/items/92771d82857e08460e5c | |
//ズーム中に実行されてズームの値に対応する要素を返すメソッド | |
func viewForZooming(in scrollView: UIScrollView) -> UIView? { | |
return storyPictureImageView | |
} | |
//ズームしたら呼び出されるメソッド ※UIScrollView内のUIImageViewの制約を更新する為に使用する | |
func scrollViewDidZoom(_ scrollView: UIScrollView) { | |
updateStoryPictureImageViewScale(self.view.bounds.size) | |
} | |
//UIScrollViewの中で拡大・縮小の動きに合わせて、中のUIImageViewの大きさを変更する | |
private func updateStoryPictureImageViewScale(_ size: CGSize) { | |
//Y軸方向のAutoLayoutの制約を加算する | |
let yOffset = max(0, (size.height - storyPictureImageView.frame.height) / 2) | |
storyPictureImageTopConstraint.constant = yOffset | |
storyPictureImageBottomConstraint.constant = yOffset | |
//X軸方向のAutoLayoutの制約を加算する | |
let xOffset = max(0, (size.width - storyPictureImageView.frame.width) / 2) | |
storyPictureImageLeftConstraint.constant = xOffset | |
storyPictureImageRightConstraint.constant = xOffset | |
view.layoutIfNeeded() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class StoryViewController: UIViewController { | |
・・・(省略)・・・ | |
//適用するカスタムトランジションのクラス | |
fileprivate let flipPresentCustomTransition = FlipPresentCustomTransition() | |
fileprivate let flipDismissCustomTransition = FlipDismissCustomTransition() | |
//スワイプアクションに関するControllerのインスタンス | |
fileprivate let swipeInteractionController = SwipeInteractionController() | |
・・・(省略)・・・ | |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
if segue.identifier == "ToStoryDetailViewController", let destinationViewController = segue.destination as? StoryDetailViewController { | |
destinationViewController.setStoryDetail(targetStory) | |
destinationViewController.transitioningDelegate = self | |
swipeInteractionController.wireToViewController(destinationViewController) | |
} | |
} | |
・・・(省略)・・・ | |
} | |
//MARK: - UIViewControllerTransitioningDelegate | |
extension StoryViewController: UIViewControllerTransitioningDelegate { | |
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
return flipPresentCustomTransition | |
} | |
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
return flipDismissCustomTransition | |
} | |
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { | |
return swipeInteractionController.interactionInProgress ? swipeInteractionController : nil | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
class SwipeInteractionController: UIPercentDrivenInteractiveTransition { | |
//動かしている状態か否かを判定する変数 | |
var interactionInProgress = false | |
//トランジションが終了したか否かを判定する変数 | |
private var shouldCompleteTransition = false | |
//該当するViewControllerを格納する変数 ※弱参照にしておく | |
private weak var viewController: UIViewController! | |
//MARK: - Function | |
//受け取った遷移対象のViewControllerの画面左にGestureRecognizer(UIScreenEdgePanGestureRecognizer)を追加する | |
func wireToViewController(_ viewController: UIViewController!) { | |
self.viewController = viewController | |
prepareGestureRecognizerInView(viewController.view) | |
} | |
//MARK: - Private Function | |
//UIScreenEdgePanGestureRecognizerが発火した際のアクションを定義する | |
@objc private func handleGesture(_ gestureRecognizer: UIScreenEdgePanGestureRecognizer) { | |
guard let gestureRecognizerView = gestureRecognizer.view else { | |
return | |
} | |
guard let gestureRecognizerSuperview = gestureRecognizerView.superview else { | |
return | |
} | |
//X軸方向の変化量を算出する | |
let translation = gestureRecognizer.translation(in: gestureRecognizerSuperview) | |
var progress = (translation.x / 200) | |
progress = CGFloat(fminf(fmaxf(Float(progress), 0.0), 1.0)) | |
//UIScreenEdgePanGestureRecognizerの状態によって動き方の場合分けにする | |
switch gestureRecognizer.state { | |
//(1)開始時 | |
case .began: | |
interactionInProgress = true | |
viewController.dismiss(animated: true, completion: nil) | |
//(2)変更時 | |
case .changed: | |
shouldCompleteTransition = (progress > 0.5) | |
update(progress) | |
//(3)キャンセル時 | |
case .cancelled: | |
interactionInProgress = false | |
cancel() | |
//(4)終了時 | |
case .ended: | |
interactionInProgress = false | |
if shouldCompleteTransition { | |
finish() | |
} else { | |
cancel() | |
} | |
default: | |
print("This state is unsupported to UIScreenEdgePanGestureRecognizer.") | |
} | |
} | |
//UIScreenEdgePanGestureRecognizerを追加する | |
private func prepareGestureRecognizerInView(_ view: UIView) { | |
let gesture = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(self.handleGesture(_:))) | |
gesture.edges = UIRectEdge.left | |
view.addGestureRecognizer(gesture) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
//UICollectionReusableViewの拡張 | |
extension UICollectionReusableView { | |
//独自に定義したセルのクラス名を返す | |
static var identifier: String { | |
return className | |
} | |
} | |
//UICollectionViewの拡張 | |
extension UICollectionView { | |
//作成した独自のカスタムセルを初期化するメソッド | |
func registerCustomCell<T: UICollectionViewCell>(_ cellType: T.Type) { | |
register(UINib(nibName: T.identifier, bundle: nil), forCellWithReuseIdentifier: T.identifier) | |
} | |
//作成した独自のカスタムセルをインスタンス化するメソッド | |
func dequeueReusableCustomCell<T: UICollectionViewCell>(with cellType: T.Type, indexPath: IndexPath) -> T { | |
return dequeueReusableCell(withReuseIdentifier: T.identifier, for: indexPath) as! T | |
} | |
//作成した独自のカスタムヘッダービューをインスタンス化するメソッド | |
func dequeueReusableCustomHeaderView<T: UICollectionReusableView>(with cellType: T.Type, indexPath: IndexPath) -> T { | |
return dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: T.identifier, for: indexPath) as! T | |
} | |
//作成した独自のカスタムフッタービューをインスタンス化するメソッド | |
func dequeueReusableCustomFooterView<T: UICollectionReusableView>(with cellType: T.Type, indexPath: IndexPath) -> T { | |
return dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: T.identifier, for: indexPath) as! T | |
} | |
} | |
/** | |
* ※1 初期化の際は、 | |
* sampleCollectionView.registerCustomCell(SampleCollectionViewCell.self) | |
* と記載すればセルの登録ができる。 | |
* | |
* ※2 セルをインスタンス化する際は、 | |
* func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
* let cell = collectionView.dequeueReusableCustomCell(with: SampleCollectionViewCell.self, indexPath: indexPath) | |
* ・・・(以下処理が続く)・・・ | |
* } | |
* と記載すればセルをインスタンス化することができる。 | |
* | |
* ※3 ヘッダー及びフッターをインスタンス化する際は、 | |
* func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { | |
* var collectionReusableView: UICollectionReusableView | |
* if kind == UICollectionElementKindSectionHeader { | |
* collectionReusableView = collectionView.dequeueReusableCustomHeaderView(with: SampleCollectionHeaderView.self, indexPath: indexPath) | |
* } else if kind == UICollectionElementKindSectionFooter { | |
* collectionReusableView = collectionView.dequeueReusableCustomFooterView(with: SampleCollectionFooterView.self, indexPath: indexPath) | |
* } | |
* ・・・(以下処理が続く)・・・ | |
* } | |
* と記載すればヘッダー及びフッターをインスタンス化することができる。 | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import UIKit | |
//UITableViewCellの拡張 | |
extension UITableViewCell { | |
//独自に定義したセルのクラス名を返す | |
static var identifier: String { | |
return className | |
} | |
} | |
//UITableViewの拡張 | |
extension UITableView { | |
//作成した独自のカスタムセルを初期化するメソッド | |
func registerCustomCell<T: UITableViewCell>(_ cellType: T.Type) { | |
register(UINib(nibName: T.identifier, bundle: nil), forCellReuseIdentifier: T.identifier) | |
} | |
//作成した独自のカスタムセルをインスタンス化するメソッド | |
func dequeueReusableCustomCell<T: UITableViewCell>(with cellType: T.Type) -> T { | |
return dequeueReusableCell(withIdentifier: T.identifier) as! T | |
} | |
} | |
/** | |
* ※1 初期化の際は、 | |
* sampleTableView.registerCustomCell(SampleTableViewCell.self) | |
* と記載すればセルの登録ができる。 | |
* | |
* ※2 セルをインスタンス化する際は、 | |
* func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
* let cell = tableView.dequeueReusableCustomCell(with: SampleTableViewCell.self) | |
* ・・・(以下処理が続く)・・・ | |
* } | |
* と記載すればセルをインスタンス化することができる。 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment