Last active
January 12, 2019 09:44
-
-
Save fumiyasac/b12739a043d52cbee0731c3318d4b04b to your computer and use it in GitHub Desktop.
Tinder風なUIを実装する際のアイデアと実装例紹介 ref: https://qiita.com/fumiyasac@github/items/c68b7ce812bf3ef48a67
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
// 楽天レシピ別カテゴリランキングのAPIキー ※各自取得をお願いします。 | |
static let API_KEY_RAKUTEN_RECIPE_RANKING = "" |
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
class CollectionViewTinderViewController: UIViewController, SFSafariViewControllerDelegate { | |
・・・(省略)・・・ | |
// ドラッグ可能なイメージビュー | |
fileprivate var draggableImageView: UIImageView! | |
// カード表示用のUICollectionViewCell格納用のレシピデータ配列 | |
fileprivate var recipeDataList: [RecipeModel] = [] { | |
didSet { | |
self.tinderCardSetCollectionView.reloadData() | |
} | |
} | |
・・・(省略)・・・ | |
// 選択状態の判定用のフラグ | |
fileprivate var isSelectedFlag: Bool = false | |
・・・(省略)・・・ | |
} | |
・・・(省略)・・・ | |
// MARK: - UICollectionViewDelegate, UICollectionViewDataSource | |
extension CollectionViewTinderViewController: UICollectionViewDelegate, UICollectionViewDataSource { | |
・・・(省略)・・・ | |
// MARK: - Private Function | |
// セルを長押しした際(UILongPressGestureRecognizerで実行された際)に発動する処理 | |
@objc private func longPressCell(sender: UILongPressGestureRecognizer) { | |
guard let targetView = sender.view else { return } | |
// 長押ししたセルのタグ名と現在位置を設定する | |
let targetTag: Int = targetView.tag | |
let pressPoint: CGPoint = sender.location(ofTouch: 0, in: self.view) | |
// 現在の中心位置を算出する | |
let centerX = pressPoint.x | |
let centerY = pressPoint.y | |
// ドラッグ可能なImageViewとぶつかる範囲の設定 | |
let minX: CGFloat = 75.0 | |
let maxX: CGFloat = UIScreen.main.bounds.width - minX | |
let minY: CGFloat = 100.0 | |
let maxY: CGFloat = UIScreen.main.bounds.height - minY | |
// 長押し対象のセルに配置されていたものを格納するための変数 | |
var targetCell: TinderCardCollectionViewCell? = nil | |
// CollectionView内の要素で該当のセルのものを抽出する | |
for targetView in tinderCardSetCollectionView.subviews { | |
if targetView is TinderCardCollectionViewCell { | |
let cc = targetView as! TinderCardCollectionViewCell | |
if cc.tag == targetTag { | |
targetCell = cc | |
break | |
} | |
} | |
} | |
// UILongPressGestureRecognizerが開始された際の処理 | |
if sender.state == UIGestureRecognizerState.began { | |
guard let targetView = targetCell?.subviews.first else { return } | |
// セル内のViewを非表示にする | |
targetCell?.isHidden = true | |
// ドラッグ可能なUIImageViewを作成&配置する | |
setDraggableImageView(targetView: targetView, x: centerX, y: centerY) | |
view.addSubview(draggableImageView) | |
// UILongPressGestureRecognizerが動作中の際の処理 | |
} else if sender.state == UIGestureRecognizerState.changed { | |
// 中心位置の更新と回転量の反映を行う | |
let diffOfCenterX = pressPoint.x - (UIScreen.main.bounds.size.width / 2) | |
let targetRotationAngel = CGFloat.pi / 180 + diffOfCenterX / 1000 | |
let transforms = CGAffineTransform(rotationAngle: targetRotationAngel) | |
draggableImageView.center = CGPoint(x: centerX, y: centerY) | |
draggableImageView.transform = transforms | |
// Debug. | |
//print("x:\(minX) ~ \(maxX), y:\(minY) ~ \(maxY)"); | |
//print("x:\(pressPoint.x), y:\(pressPoint.y)"); | |
// 設定した領域の範囲内にあるか否かを判定する | |
let containsOfTargetRect: Bool = ((minX <= pressPoint.x && pressPoint.x <= maxX) && (minY <= pressPoint.y && pressPoint.y <= maxY)) | |
isSelectedFlag = (containsOfTargetRect) ? false : true | |
// UILongPressGestureRecognizerが終了した際の処理 | |
} else if sender.state == UIGestureRecognizerState.ended { | |
// 設定した領域の範囲内に中心位置がない場合は該当のレシピデータを削除してUICollectionViewを更新 | |
if isSelectedFlag { | |
// 左右のどちらにスワイプするかを決定する | |
let isSwipeLeft = (minX > pressPoint.x) | |
let isSwipeRight = (pressPoint.x > maxX) | |
var swipeOutPosX: CGFloat = 0 | |
let swipeOutPosY: CGFloat = self.draggableImageView.center.y | |
UIView.animate(withDuration: TinderCardDefaultSettings.durationOfSwipeOut / 2.5, animations: { | |
if isSwipeLeft { | |
swipeOutPosX = -UIScreen.main.bounds.width * 2.0 | |
} else if isSwipeRight { | |
swipeOutPosX = UIScreen.main.bounds.width * 2.0 | |
} | |
self.draggableImageView.center = CGPoint(x: swipeOutPosX, y: swipeOutPosY) | |
}, completion: { _ in | |
if isSwipeLeft { | |
self.swipeOutLeftDraggableImageView() | |
} else if isSwipeRight { | |
self.swipeOutRightDraggableImageView() | |
} | |
self.recipeDataList.remove(at: targetTag) | |
self.removeDraggableImageView() | |
// セル内のViewを表示する | |
targetCell?.isHidden = false | |
}) | |
isSelectedFlag = false | |
// 設定した領域の範囲内に中心位置がある場合はUICollectionViewの表示を元に戻す | |
} else { | |
removeDraggableImageView() | |
// セル内のViewを表示する | |
targetCell?.isHidden = false | |
} | |
} | |
} | |
// ドラッグ可能なUIImageViewを左側の画面外へ動かす | |
private func swipeOutLeftDraggableImageView() { | |
// Debug. | |
//print("左方向へのスワイプ完了しました。") | |
} | |
// ドラッグ可能なUIImageViewを右側の画面外へ動かす | |
private func swipeOutRightDraggableImageView() { | |
// Debug. | |
//print("右方向へのスワイプ完了しました。") | |
} | |
// ドラッグ可能なUIImageViewを画面から消去する | |
private func removeDraggableImageView() { | |
draggableImageView.image = nil | |
draggableImageView.removeFromSuperview() | |
} | |
// ドラッグ可能なUIImageViewに関する初期設定をする | |
private func setDraggableImageView(targetView: UIView, x: CGFloat, y: CGFloat) { | |
draggableImageView = UIImageView() | |
draggableImageView.frame.size = CGSize( | |
width: TinderCardDefaultSettings.cardSetViewWidth, | |
height: TinderCardDefaultSettings.cardSetViewHeight | |
) | |
draggableImageView.center = CGPoint( | |
x: x, | |
y: y | |
) | |
draggableImageView.backgroundColor = UIColor.white | |
draggableImageView.image = getSnapshotOfCell(inputView: targetView) | |
// MEMO: この部分では背景のViewに関する設定のみ実装 | |
draggableImageView.layer.borderColor = TinderCardDefaultSettings.backgroundBorderColor | |
draggableImageView.layer.borderWidth = TinderCardDefaultSettings.backgroundBorderWidth | |
draggableImageView.layer.cornerRadius = TinderCardDefaultSettings.backgroundCornerRadius | |
draggableImageView.layer.shadowRadius = TinderCardDefaultSettings.backgroundShadowRadius | |
draggableImageView.layer.shadowOpacity = TinderCardDefaultSettings.backgroundShadowOpacity | |
draggableImageView.layer.shadowOffset = TinderCardDefaultSettings.backgroundShadowOffset | |
draggableImageView.layer.shadowColor = TinderCardDefaultSettings.backgroundBorderColor | |
} | |
// 選択したCollectionViewCellのスナップショットを取得する | |
private func getSnapshotOfCell(inputView: UIView) -> UIImage? { | |
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0) | |
guard let context = UIGraphicsGetCurrentContext() else { return nil } | |
inputView.layer.render(in: context) | |
let image = UIGraphicsGetImageFromCurrentImageContext() | |
UIGraphicsEndImageContext() | |
return image | |
} | |
} |
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
class PureViewTinderViewController: UIViewController, SFSafariViewControllerDelegate { | |
// カード表示用のViewを格納するための配列 | |
fileprivate var tinderCardSetViewList: [TinderCardSetView] = [] | |
・・・(省略)・・・ | |
// 画面上にあるカードの山のうち、一番上にあるViewのみを操作できるようにする | |
fileprivate func enableUserInteractionToFirstCardSetView() { | |
if !tinderCardSetViewList.isEmpty { | |
if let firsttTinderCardSetView = tinderCardSetViewList.first { | |
firsttTinderCardSetView.isUserInteractionEnabled = true | |
} | |
} | |
} | |
// 現在配列に格納されている(画面上にカードの山として表示されている)Viewの拡大縮小を調節する | |
fileprivate func changeScaleToCardSetViews(skipSelectedView: Bool = false) { | |
// アニメーション関連の定数値 | |
let duration: TimeInterval = 0.26 | |
let reduceRatio: CGFloat = 0.018 | |
var targetCount: CGFloat = 0 | |
for (targetIndex, tinderCardSetView) in tinderCardSetViewList.enumerated() { | |
// 現在操作中のViewの縮小比を変更しない場合は、以降の処理をスキップする | |
if skipSelectedView && targetIndex == 0 { continue } | |
// 後ろに配置されているViewほど小さく見えるように縮小比を調節する | |
let targetScale: CGFloat = 1 - reduceRatio * targetCount | |
UIView.animate(withDuration: duration, animations: { | |
tinderCardSetView.transform = CGAffineTransform(scaleX: targetScale, y: targetScale) | |
}) | |
targetCount += 1 | |
} | |
} | |
} | |
・・・(省略)・・・ | |
// MARK: - TinderCardSetDelegate | |
extension PureViewTinderViewController: TinderCardSetDelegate { | |
// ドラッグ処理が開始された際にViewController側で実行する処理 | |
func beganDragging(_ cardView: TinderCardSetView) { | |
// Debug. | |
//print("ドラッグ処理が開始されました。") | |
changeScaleToCardSetViews(skipSelectedView: true) | |
} | |
// ドラッグ処理中に位置情報が更新された際にViewController側で実行する処理 | |
func updatePosition(_ cardView: TinderCardSetView, centerX: CGFloat, centerY: CGFloat) { | |
// Debug. | |
//print("移動した座標点 X軸:\(centerX) Y軸:\(centerY)") | |
} | |
// 左方向へのスワイプが完了した際にViewController側で実行する処理 | |
func swipedLeftPosition(_ cardView: TinderCardSetView) { | |
// Debug. | |
//print("左方向へのスワイプ完了しました。") | |
tinderCardSetViewList.removeFirst() | |
enableUserInteractionToFirstCardSetView() | |
changeScaleToCardSetViews(skipSelectedView: false) | |
} | |
// 右方向へのスワイプが完了した際にViewController側で実行する処理 | |
func swipedRightPosition(_ cardView: TinderCardSetView) { | |
// Debug. | |
//print("右方向へのスワイプ完了しました。") | |
tinderCardSetViewList.removeFirst() | |
enableUserInteractionToFirstCardSetView() | |
changeScaleToCardSetViews(skipSelectedView: false) | |
} | |
// 元の位置へ戻った際にViewController側で実行する処理 | |
func returnToOriginalPosition(_ cardView: TinderCardSetView) { | |
// Debug. | |
//print("元の位置へ戻りました。") | |
changeScaleToCardSetViews(skipSelectedView: false) | |
} | |
} |
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 | |
import SwiftyJSON | |
struct RecipeModel { | |
//メンバ変数(取得したJSONレスポンスのKeyに対応する値が入る) | |
let recipeId: Int | |
let recipeTitle: String | |
let recipeUrl: String | |
let foodImageUrl: String | |
let recipeIndication: String | |
let recipeCost: String | |
let recipeDescription: String | |
let recipePublishday: String | |
//イニシャライザ(取得したJSONレスポンスに対して必要なものを抽出する) | |
init(result: JSON) { | |
self.recipeId = result["recipeId"].int ?? 0 | |
self.recipeTitle = result["recipeTitle"].string ?? "" | |
self.recipeUrl = result["recipeUrl"].string ?? "" | |
self.foodImageUrl = result["foodImageUrl"].string ?? "" | |
self.recipeIndication = result["recipeIndication"].string ?? "" | |
self.recipeCost = result["recipeCost"].string ?? "" | |
self.recipeDescription = result["recipeDescription"].string ?? "" | |
self.recipePublishday = result["recipePublishday"].string ?? "" | |
} | |
} |
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 | |
import SwiftyJSON | |
protocol RecipePresenterProtocol: class { | |
func bindRecipes(_ recipes: [RecipeModel]) | |
func showErrorMessage() | |
} | |
class RecipePresenter { | |
var presenter: RecipePresenterProtocol! | |
//MARK: - Initializer | |
init(presenter: RecipePresenterProtocol) { | |
self.presenter = presenter | |
} | |
//MARK: - Functions | |
// レシピデータをAPI経由で取得する | |
func getRecipes() { | |
let parameters = [ | |
"format" : "json", | |
"applicationId" : APIConstant.API_KEY_RAKUTEN_RECIPE_RANKING, | |
"categoryId" : APIConstant.getCategoryByRandom() | |
] | |
// 楽天レシピカテゴリ別ランキングAPIへアクセスをするための準備をする | |
let apiRequestManager = APIRequestManager( | |
endPoint: "/Recipe/CategoryRanking/20121121", | |
method: .get, | |
parameters: parameters | |
) | |
// 楽天レシピカテゴリ別ランキングAPIへの通信処理を実行する | |
apiRequestManager.request( | |
// 通信成功時: | |
success: { (data: Dictionary) in | |
// JSONデータを解析してRecipeModel型データを作成 | |
let json = JSON(data) | |
let recipes: [RecipeModel] = json["result"].map{ (_, result) in | |
return RecipeModel(result: JSON(result)) | |
} | |
// 通信成功時の処理をプロトコルを適用したViewController側で行う | |
self.presenter.bindRecipes(recipes) | |
}, | |
// 通信失敗時: | |
fail: { (error: Error?) in | |
// 通信失敗時の処理をプロトコルを適用したViewController側で行う | |
self.presenter.showErrorMessage() | |
} | |
) | |
} | |
} |
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 TinderCardCollectionViewLayout: UICollectionViewLayout { | |
// MEMO: 下記の記事を参考に作成しています。 | |
// 「UICollectionViewのLayoutで悩んだら」 | |
// http://techlife.cookpad.com/entry/2017/06/29/190000 | |
// 設定したレイアウト属性を格納するための変数 | |
private var layout = [UICollectionViewLayoutAttributes]() | |
// セル表示時の拡大縮小の変化割合とアルファ値の割合 | |
private let reduceRatio: CGFloat = 0.018 | |
private let alphaRatio: CGFloat = 0.008 | |
// レイアウトの事前計算を行う前に実行する | |
override func prepare() { | |
super.prepare() | |
// 設定したレイアウト属性を事前計算処理前にリセットする | |
layout.removeAll() | |
// UICollectionViewの要素数を取得する | |
var numberOfItemCount: Int = 0 | |
if let targetCollectionView = collectionView { | |
numberOfItemCount = targetCollectionView.numberOfItems(inSection: 0) | |
} | |
// 現在画面に表示されている要素分のレイアウト属性の算出を行う | |
for targetCount in (0..<numberOfItemCount).reversed() { | |
// indexPathの値を取得する | |
let indexPath = IndexPath(item: targetCount, section: 0) | |
// X軸&Y軸の値を新たに算出する | |
let newPositionX: CGFloat = (UIScreen.main.bounds.width - TinderCardDefaultSettings.cardSetViewWidth) / 2 | |
let newPositionY: CGFloat = (UIScreen.main.bounds.height - TinderCardDefaultSettings.cardSetViewHeight) / 2.7 | |
- CGFloat(6 * targetCount) | |
// レイアウトの配列に位置とサイズに関する情報を登録する | |
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) | |
attributes.frame = CGRect( | |
x: newPositionX, | |
y: newPositionY, | |
width: TinderCardDefaultSettings.cardSetViewWidth, | |
height: TinderCardDefaultSettings.cardSetViewHeight | |
) | |
// 後ろに配置されているUICollectionViewCellほど小さく見えるように拡大縮小比を調節する | |
let targetScale: CGFloat = 1 - reduceRatio * CGFloat(targetCount) | |
let targetAlpha: CGFloat = 1 - alphaRatio * CGFloat(targetCount) | |
attributes.alpha = targetAlpha | |
attributes.transform = CGAffineTransform(scaleX: targetScale, y: targetScale) | |
attributes.zIndex = numberOfItemCount - targetCount | |
layout.append(attributes) | |
} | |
} | |
// 範囲内に含まれるすべてのセルのレイアウト属性を返す | |
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { | |
super.layoutAttributesForElements(in: rect) | |
return layout | |
} | |
} |
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
class TinderCardDefaultSettings: TinderCardSetting { | |
// ・・・(省略)・・・ | |
// MARK: - TinderCardSetViewSettingプロトコルで定義した変数 | |
static var cardSetViewWidth: CGFloat = 300 | |
static var cardSetViewHeight: CGFloat = 320 | |
// ・・・(以下カード用Viewのデザイン設定に関する定数値)・・・ | |
} |
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
protocol TinderCardSetting { | |
// MARK: - Static Properties | |
// カード用View高さ | |
static var cardSetViewWidth: CGFloat { get } | |
// カード用View幅 | |
static var cardSetViewHeight: CGFloat { get } | |
// ・・・(以下カード用Viewのデザイン設定に関する変数を定義する)・・・ | |
} |
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
class TinderCardSetView: CustomViewBase { | |
・・・(省略)・・・ | |
// ドラッグが開始された際に実行される処理 | |
@objc private func startDragging(_ sender: UIPanGestureRecognizer) { | |
// 中心位置からのX軸&Y軸方向の位置の値を更新する | |
xPositionFromCenter = sender.translation(in: self).x | |
yPositionFromCenter = sender.translation(in: self).y | |
// UIPangestureRecognizerの状態に応じた処理を行う | |
switch sender.state { | |
// ドラッグ開始時の処理 | |
case .began: | |
// ドラッグ処理開始時のViewがある位置を取得する | |
originalPoint = CGPoint( | |
x: self.center.x - xPositionFromCenter, | |
y: self.center.y - yPositionFromCenter | |
) | |
// DelegeteメソッドのbeganDraggingを実行する | |
self.delegate?.beganDragging(self) | |
// Debug. | |
//print("beganCenterX:", originalPoint.x) | |
//print("beganCenterY:", originalPoint.y) | |
// ドラッグ処理開始時のViewのアルファ値を変更する | |
UIView.animate(withDuration: durationOfStartDragging, delay: 0.0, options: [.curveEaseInOut], animations: { | |
self.alpha = self.startDraggingAlpha | |
}, completion: nil) | |
break | |
// ドラッグ最中の処理 | |
case .changed: | |
// 動かした位置の中心位置を取得する | |
let newCenterX = originalPoint.x + xPositionFromCenter | |
let newCenterY = originalPoint.y + yPositionFromCenter | |
// Viewの中心位置を更新して動きをつける | |
self.center = CGPoint(x: newCenterX, y: newCenterY) | |
// DelegeteメソッドのupdatePositionを実行する | |
self.delegate?.updatePosition(self, centerX: newCenterX, centerY: newCenterY) | |
// 中心位置からのX軸方向へ何パーセント移動したか(移動割合)を計算する | |
currentMoveXPercentFromCenter = min(xPositionFromCenter / UIScreen.main.bounds.size.width, 1) | |
// 中心位置からのY軸方向へ何パーセント移動したか(移動割合)を計算する | |
currentMoveYPercentFromCenter = min(yPositionFromCenter / UIScreen.main.bounds.size.height, 1) | |
// Debug. | |
//print("currentMoveXPercentFromCenter:", currentMoveXPercentFromCenter) | |
//print("currentMoveYPercentFromCenter:", currentMoveYPercentFromCenter) | |
// 上記で算出したX軸方向の移動割合から回転量を取得し、初期配置時の回転量へ加算した値でアファイン変換を適用する | |
let initialRotationAngle = atan2(initialTransform.b, initialTransform.a) | |
let whenDraggingRotationAngel = initialRotationAngle + CGFloat.pi / 10 * currentMoveXPercentFromCenter | |
let transforms = CGAffineTransform(rotationAngle: whenDraggingRotationAngel) | |
// 拡大縮小比を適用する | |
let scaleTransform: CGAffineTransform = transforms.scaledBy(x: maxScaleOfDragging, y: maxScaleOfDragging) | |
self.transform = scaleTransform | |
break | |
// ドラッグ終了時の処理 | |
case .ended, .cancelled: | |
// ドラッグ終了時点での速度を算出する | |
let whenEndedVelocity = sender.velocity(in: self) | |
// Debug. | |
//print("whenEndedVelocity:", whenEndedVelocity) | |
// 移動割合のしきい値を超えていた場合には、画面外へ流れていくようにする(しきい値の範囲内の場合は元に戻る) | |
let shouldMoveToLeft = (currentMoveXPercentFromCenter < -swipeXPosLimitRatio && abs(currentMoveYPercentFromCenter) > swipeYPosLimitRatio) | |
let shouldMoveToRight = (currentMoveXPercentFromCenter > swipeXPosLimitRatio && abs(currentMoveYPercentFromCenter) > swipeYPosLimitRatio) | |
if shouldMoveToLeft { | |
moveInvisiblePosition(verocity: whenEndedVelocity, isLeft: true) | |
} else if shouldMoveToRight { | |
moveInvisiblePosition(verocity: whenEndedVelocity, isLeft: false) | |
} else { | |
moveOriginalPosition() | |
} | |
// ドラッグ開始時の座標位置の変数をリセットする | |
originalPoint = CGPoint.zero | |
xPositionFromCenter = 0.0 | |
yPositionFromCenter = 0.0 | |
currentMoveXPercentFromCenter = 0.0 | |
currentMoveYPercentFromCenter = 0.0 | |
break | |
default: | |
break | |
} | |
} | |
・・・(省略)・・・ | |
// カードを初期配置する位置へ戻す | |
private func moveInitialPosition() { | |
// 表示前のカードの位置を設定する | |
let beforeInitializePosX: CGFloat = CGFloat(Int.createRandom(range: Range(-300...300))) | |
let beforeInitializePosY: CGFloat = CGFloat(-Int.createRandom(range: Range(300...600))) | |
let beforeInitializeCenter = CGPoint(x: beforeInitializePosX, y: beforeInitializePosY) | |
// 表示前のカードの傾きを設定する | |
let beforeInitializeRotateAngle: CGFloat = CGFloat(Int.createRandom(range: Range(-90...90))) | |
let angle = beforeInitializeRotateAngle * .pi / 180.0 | |
let beforeInitializeTransform = CGAffineTransform(rotationAngle: angle) | |
beforeInitializeTransform.scaledBy(x: beforeInitializeScale, y: beforeInitializeScale) | |
// 画面外からアニメーションを伴って現れる動きを設定する | |
self.alpha = 0 | |
self.center = beforeInitializeCenter | |
self.transform = beforeInitializeTransform | |
UIView.animate(withDuration: durationOfInitialize, animations: { | |
self.alpha = 1 | |
self.center = self.initialCenter | |
self.transform = self.initialTransform | |
}) | |
} | |
// カードを元の位置へ戻す | |
private func moveOriginalPosition() { | |
UIView.animate(withDuration: durationOfReturnOriginal, delay: 0.0, usingSpringWithDamping: 0.68, initialSpringVelocity: 0.0, options: [.curveEaseInOut], animations: { | |
// ドラッグ処理終了時はViewのアルファ値を元に戻す | |
self.alpha = self.stopDraggingAlpha | |
// Viewの配置を元の位置まで戻す | |
self.center = self.initialCenter | |
self.transform = self.initialTransform | |
}, completion: nil) | |
// DelegeteメソッドのreturnToOriginalPositionを実行する | |
self.delegate?.returnToOriginalPosition(self) | |
} | |
// カードを左側の領域外へ動かす | |
private func moveInvisiblePosition(verocity: CGPoint, isLeft: Bool = true) { | |
// 変化後の予定位置を算出する(Y軸方向の位置はverocityに基づいた値を採用する) | |
let absPosX = UIScreen.main.bounds.size.width * 1.6 | |
let endCenterPosX = isLeft ? -absPosX : absPosX | |
let endCenterPosY = verocity.y | |
let endCenterPosition = CGPoint(x: endCenterPosX, y: endCenterPosY) | |
UIView.animate(withDuration: durationOfSwipeOut, delay: 0.0, usingSpringWithDamping: 0.68, initialSpringVelocity: 0.0, options: [.curveEaseInOut], animations: { | |
// ドラッグ処理終了時はViewのアルファ値を元に戻す | |
self.alpha = self.stopDraggingAlpha | |
// 変化後の予定位置までViewを移動する | |
self.center = endCenterPosition | |
}, completion: { _ in | |
// DelegeteメソッドのswipedLeftPositionを実行する | |
let _ = isLeft ? self.delegate?.swipedLeftPosition(self) : self.delegate?.swipedRightPosition(self) | |
// 画面から該当のViewを削除する | |
self.removeFromSuperview() | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment