Last active
March 11, 2020 17:46
-
-
Save saito-sv/3f87398a04166fa71a33461753af0910 to your computer and use it in GitHub Desktop.
Swift slide up modal view
This file contains 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
// | |
// SlideUp.swift | |
// | |
// Created by Marlon Monroy on 1/29/19. | |
// Copyright © 2019 Monroy.io. All rights reserved. | |
// | |
import UIKit | |
class InstantPan: UIPanGestureRecognizer { | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { | |
if (self.state == .began) { return } | |
super.touchesBegan(touches, with: event) | |
self.state = .began | |
} | |
} | |
enum SlideUpState { | |
case closed | |
case open | |
var apposite : SlideUpState { | |
return self == .open ? .closed :.open | |
} | |
} | |
class SlideUp { | |
private let offset: CGFloat | |
private var containerView: UIView | |
private var state : SlideUpState = .closed | |
private var animator: UIViewPropertyAnimator! | |
private var progress: CGFloat = 0.0 | |
private let topSpacing:CGFloat = 40 | |
lazy var panGesture: InstantPan = { | |
let pan = InstantPan() | |
pan.addTarget(self, action: #selector(panned(_:))) | |
return pan | |
}() | |
@objc func panned(_ sender: UIPanGestureRecognizer) { | |
switch sender.state { | |
case .began: | |
animateTransition(to: state.apposite, and: 1) | |
animator.pauseAnimation() | |
progress = animator.fractionComplete | |
case .changed: | |
let translate = sender.translation(in: containerView) | |
var fraction = -translate.y / offset | |
if state == .open { fraction *= -1} | |
if animator.isReversed { fraction *= -1} | |
animator.fractionComplete = fraction + progress | |
case .ended: | |
let velocity = sender.velocity(in: containerView).y | |
if velocity == 0 { | |
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) | |
break | |
} | |
shouldClose(should: velocity > 0) | |
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) | |
default: break | |
} | |
} | |
func shouldClose(should:Bool) { | |
switch state { | |
case .open: | |
if !should && !animator.isReversed { animator.isReversed = !animator.isReversed} | |
if should && animator.isReversed { animator.isReversed = !animator.isReversed} | |
case .closed: | |
if should && !animator.isReversed { animator.isReversed = !animator.isReversed} | |
if !should && animator.isReversed { animator.isReversed = !animator.isReversed} | |
} | |
} | |
func animateTransition(to state: SlideUpState, and duration:TimeInterval) { | |
let transition = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) { | |
switch state { | |
case .open: | |
self.containerView.frame.origin.y = self.topSpacing | |
case .closed: | |
self.containerView.frame.origin.y = self.offset | |
} | |
} | |
transition.addCompletion { postion in | |
switch postion { | |
case .start: | |
self.state = state.apposite | |
case .end: | |
self.state = state | |
case .current: () | |
} | |
switch self.state { | |
case .open: | |
self.containerView.frame.origin.y = self.topSpacing | |
case .closed: | |
self.containerView.frame.origin.y = self.offset | |
} | |
} | |
animator = transition | |
animator.startAnimation() | |
} | |
init(offset:CGFloat = 300, and containerView:UIView) { | |
self.offset = offset | |
self.containerView = containerView | |
containerView.addGestureRecognizer(panGesture) | |
containerView.frame.origin.y = offset | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment