Skip to content

Instantly share code, notes, and snippets.

@nekonora
Last active June 19, 2020 17:04
Show Gist options
  • Save nekonora/54512bf5479db51b29df3b90d15ddf8b to your computer and use it in GitHub Desktop.
Save nekonora/54512bf5479db51b29df3b90d15ddf8b to your computer and use it in GitHub Desktop.
import Foundation
import UIKit
class ModalScreenVC: UIViewController {
// MARK: - Outlets
/// A subview filling this controller's view with desired top margin
@IBOutlet private var contentView: UIView!
// MARK: - Properties
private var contentViewInitialYOrigin: CGFloat = 0
// MARK: - Lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
presentContent()
}
override func viewDidLoad() {
super.viewDidLoad()
modalPresentationStyle = .overFullScreen
modalTransitionStyle = .crossDissolve
addGesture()
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
dismissContent()
}
// MARK: - Actions
@objc private func onGestureCalled(_ gesture: UIPanGestureRecognizer) {
handleGesture(gesture)
}
@IBAction private func didTapClose(_ sender: UIButton) {
dismiss(animated: false)
}
}
private extension ModalScreenVC {
func presentContent() {
guard let presentingVC = presentingViewController else { return }
view.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0.6)
contentView.transform = CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.height)
presentingVC.view.layer.cornerRadius = 10
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.3, options: [.curveEaseOut], animations: {
presentingVC.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
presentingVC.view.alpha = 0.4
self.contentView.transform = .identity
}) { _ in
self.contentViewInitialYOrigin = self.contentView.frame.origin.y
}
}
func addGesture() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onGestureCalled(_:)))
contentView.addGestureRecognizer(panGesture)
}
func handleGesture(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .changed:
contentView.frame.origin.y = contentViewInitialYOrigin + (gesture.translation(in: view).y / 2)
case .cancelled, .ended, .failed:
if gesture.translation(in: view).y > UIScreen.main.bounds.height / 4 {
dismiss(animated: false)
} else {
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 0.3, options: [.curveEaseOut], animations: {
self.contentView.frame.origin.y = self.contentViewInitialYOrigin
}, completion: nil)
}
default: break
}
}
func dismissContent() {
guard let presentingVC = presentingViewController else { super.dismiss(animated: false, completion: nil); return }
UIView.animate(withDuration: 0.3, animations: {
self.contentView.transform = CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.height * 1.5)
presentingVC.view.transform = .identity
presentingVC.view.alpha = 1
self.view.backgroundColor = .clear
}) { _ in
super.dismiss(animated: false, completion: { presentingVC.setNeedsStatusBarAppearanceUpdate() })
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment