Created
April 19, 2020 12:39
-
-
Save hachinobu/42ae89e616b0b7b2790d18ff9c132c5d to your computer and use it in GitHub Desktop.
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 ViewController: UIViewController { | |
enum ImageViewState: Int { | |
case open | |
case close | |
var opposite: ImageViewState { | |
switch self { | |
case .open: | |
return .close | |
case .close: | |
return .open | |
} | |
} | |
} | |
private lazy var imageView: UIImageView = { | |
let imageView = UIImageView() | |
imageView.image = #imageLiteral(resourceName: "background") | |
imageView.isUserInteractionEnabled = true | |
return imageView | |
}() | |
private let blurView: UIVisualEffectView = { | |
let blurView = UIVisualEffectView() | |
blurView.isUserInteractionEnabled = false | |
return blurView | |
}() | |
private var imageViewTopConstraint = NSLayoutConstraint() | |
private var offset: CGFloat { | |
return view.frame.maxY - 80 | |
} | |
// UIViewPropertyAnimatorの生成 | |
private var animator = UIViewPropertyAnimator(duration: 3.0, curve: .easeInOut) | |
private var animationProgress: CGFloat = 0.0 | |
private var imageViewState: ImageViewState = .close | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
imageView.translatesAutoresizingMaskIntoConstraints = false | |
imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor) | |
view.addSubview(imageView) | |
NSLayoutConstraint.activate( | |
[ | |
imageViewTopConstraint, | |
imageView.leftAnchor.constraint(equalTo: view.leftAnchor), | |
imageView.rightAnchor.constraint(equalTo: view.rightAnchor), | |
imageView.heightAnchor.constraint(equalTo: view.heightAnchor) | |
] | |
) | |
blurView.translatesAutoresizingMaskIntoConstraints = false | |
imageView.addSubview(blurView) | |
NSLayoutConstraint.activate( | |
[ | |
blurView.topAnchor.constraint(equalTo: imageView.topAnchor), | |
blurView.leftAnchor.constraint(equalTo: imageView.leftAnchor), | |
blurView.rightAnchor.constraint(equalTo: imageView.rightAnchor), | |
blurView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) | |
] | |
) | |
let recognizer = UIPanGestureRecognizer() | |
recognizer.addTarget(self, action: #selector(self.imageViewPanned(recognizer:))) | |
imageView.addGestureRecognizer(recognizer) | |
} | |
private func setupAnimator(state: ImageViewState) { | |
// 逆再生をoff | |
animator.isReversed = false | |
// アニメーション実行中の場合はアニメーションを設定しない | |
if animator.isRunning { | |
return | |
} | |
// アニメーションの追加 | |
animator.addAnimations { | |
switch state { | |
case .close: | |
self.imageViewTopConstraint.constant = -self.offset | |
case .open: | |
self.imageViewTopConstraint.constant = 0 | |
} | |
self.view.layoutIfNeeded() | |
} | |
// アニメーションの追加 | |
animator.addAnimations { | |
switch state { | |
case .close: | |
self.blurView.effect = UIBlurEffect(style: .light) | |
case .open: | |
self.blurView.effect = nil | |
} | |
} | |
// アニメーション完了時の処理 | |
animator.addCompletion { position in | |
switch position { | |
case .start: | |
self.imageViewState = state | |
case .end: | |
self.imageViewState = state.opposite | |
case .current: () | |
} | |
switch self.imageViewState { | |
case .close: | |
self.imageViewTopConstraint.constant = 0.0 | |
case .open: | |
self.imageViewTopConstraint.constant = -self.offset | |
} | |
} | |
} | |
@objc private func imageViewPanned(recognizer: UIPanGestureRecognizer) { | |
switch recognizer.state { | |
case .began: | |
// Animatorの設定 | |
setupAnimator(state: imageViewState) | |
// アニメーションの一時停止 | |
animator.pauseAnimation() | |
// アニメーションの進捗率を保持 | |
animationProgress = animator.fractionComplete | |
case .changed: | |
let translation = recognizer.translation(in: imageView) | |
var fraction = -translation.y / offset | |
if imageViewState == .open { fraction *= -1 } | |
// ユーザー操作とアニメーションの進捗率を連動させインタラクティブアニメーションを実現するキーポイント | |
animator.fractionComplete = fraction + animationProgress | |
case .ended: | |
let yVelocity = recognizer.velocity(in: imageView).y | |
if yVelocity == 0 { | |
// アニメーション再開 | |
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) | |
} | |
switch imageViewState { | |
case .close: | |
if yVelocity > 0 { | |
// 逆再生モードON | |
animator.isReversed = true | |
} | |
case .open: | |
if yVelocity < 0 { | |
// 逆再生モードON | |
animator.isReversed = true | |
} | |
} | |
// アニメーション再開 | |
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) | |
default: () | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment