Skip to content

Instantly share code, notes, and snippets.

@saito-sv
Last active March 11, 2020 17:46
Show Gist options
  • Save saito-sv/3f87398a04166fa71a33461753af0910 to your computer and use it in GitHub Desktop.
Save saito-sv/3f87398a04166fa71a33461753af0910 to your computer and use it in GitHub Desktop.
Swift slide up modal view
//
// 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