Skip to content

Instantly share code, notes, and snippets.

@jamesrochabrun
Created August 14, 2019 03:53
Show Gist options
  • Save jamesrochabrun/0fd9af4ad77b32f1ed3364989bd92291 to your computer and use it in GitHub Desktop.
Save jamesrochabrun/0fd9af4ad77b32f1ed3364989bd92291 to your computer and use it in GitHub Desktop.
class AbsoluteFrameAnimator: NSObject {
private var absoluteFrame: CGRect = CGRect.zero
init(duration: CGFloat) {
self.duration = duration
}
private let duration: CGFloat
private var blurEffectStyle: UIBlurEffect.Style = .light
enum DimTransitionMode: Int {
case present, dismiss
}
private lazy var blurEffectView: UIVisualEffectView? = {
let blurEffect = UIBlurEffect(style: blurEffectStyle)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
return blurEffectView
}()
private var transitionMode: DimTransitionMode = .present
func transitionMode(_ transMode: DimTransitionMode, blurStyle: UIBlurEffect.Style = .light, startingFrame: CGRect = CGRect.zero) {
transitionMode = transMode
blurEffectStyle = blurStyle
absoluteFrame = startingFrame
}
}
extension AbsoluteFrameAnimator: UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
transitionMode == .present ? presentInTransitionContext(transitionContext) : dismissInTransitionContext(transitionContext)
}
fileprivate func presentInTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
guard let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) else {
return
}
let containerView = transitionContext.containerView
blurEffectView?.effect = nil
if let blurEffectView = blurEffectView {
containerView.addSubview(blurEffectView)
blurEffectView.fillSuperview()
}
containerView.addSubview(presentedView)
presentedView.translatesAutoresizingMaskIntoConstraints = false
let topConstraint = presentedView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: absoluteFrame.origin.y)
let leadingConstraint = presentedView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: absoluteFrame.origin.x)
let widthConstraint = presentedView.widthAnchor.constraint(equalToConstant: absoluteFrame.width)
let heightConstraint = presentedView.heightAnchor.constraint(equalToConstant: absoluteFrame.height)
let constraints = [topConstraint, leadingConstraint, widthConstraint, heightConstraint]
constraints.forEach {$0.isActive = true }
// presentedView.frame = absoluteFrame // if we want to handle the frame isntead.
containerView.layoutIfNeeded()
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: .curveEaseOut, animations: {
self.blurEffectView?.effect = UIBlurEffect(style: self.blurEffectStyle)
constraints.forEach {$0.isActive = true }
// presentedView.frame = containerView.frame
presentedView.fillSuperview()
containerView.layoutIfNeeded()
}, completion: { _ in
let didComplete = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(didComplete)
})
}
fileprivate func dismissInTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {
guard let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) else {
return
}
let containerView = transitionContext.containerView
returningView.layer.cornerRadius = 16.0 // should have the same corner radius of the cell tapped.
returningView.clipsToBounds = true
containerView.layoutIfNeeded()
if let collectionView = returningView.subviews.first as? CardDashBoardExpandedView {
if collectionView.cardDashBoardCollectionView.contentOffset != CGPoint.zero {
collectionView.cardDashBoardCollectionView.setContentOffset(CGPoint.zero, animated: true)
}
collectionView.cardDashBoardCollectionView.backgroundColor = returningView.backgroundColor
}
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.7, options: .curveEaseOut, animations: {
self.blurEffectView?.effect = nil
var frame = self.absoluteFrame
frame.size.width = self.absoluteFrame.width
returningView.frame = frame
containerView.layoutIfNeeded()
}, completion: { _ in
let didComplete = !transitionContext.transitionWasCancelled
transitionContext.completeTransition(didComplete)
})
}
}
// This also needs this to get the frame position
extension UICollectionViewCell {
func absoluteCoordinateFrame() -> CGRect? {
guard let superView = superview else { return nil }
return superView.convert(frame, to: nil)
}
}
// example of use...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
guard let absoluteFrame = cell.absoluteCoordinateFrame() else { return }
// use absoluteFrame to pass it to the transition -> the absoluteFrame is the starting and ending frame, which is the tapped cell.
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment