Skip to content

Commit

Permalink
Project Setup
Browse files Browse the repository at this point in the history
  • Loading branch information
vermaanand committed Aug 11, 2020
1 parent 82be861 commit c2e86d4
Show file tree
Hide file tree
Showing 16 changed files with 1,036 additions and 0 deletions.
495 changes: 495 additions & 0 deletions AVStackController.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>AVStackController.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
19 changes: 19 additions & 0 deletions AVStackController/AVStackController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AVStackController.h
// AVStackController
//
// Created by Anand Verma on 11/08/20.
// Copyright © 2020 AVSwiftHub. All rights reserved.
//

#import <Foundation/Foundation.h>

//! Project version number for AVStackController.
FOUNDATION_EXPORT double AVStackControllerVersionNumber;

//! Project version string for AVStackController.
FOUNDATION_EXPORT const unsigned char AVStackControllerVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <AVStackController/PublicHeader.h>


22 changes: 22 additions & 0 deletions AVStackController/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
33 changes: 33 additions & 0 deletions AVStackController/Sources/BottomPopupDismissAnimator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// DraggableDismissAnimator.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//


import UIKit

final class BottomPopupDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private unowned var attributesOwner: BottomPresentableViewController

init(attributesOwner: BottomPresentableViewController) {
self.attributesOwner = attributesOwner
}

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return attributesOwner.popupDismissDuration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromVC = transitionContext.viewController(forKey: .from)!
let dismissFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: fromVC.view.frame.size)

UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
fromVC.view.frame = dismissFrame
}) { (_) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// DraggableDismissInteractionController.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//

import UIKit

protocol BottomPopupDismissInteractionControllerDelegate: class {
func dismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat)
}

final class BottomPopupDismissInteractionController: UIPercentDrivenInteractiveTransition {
private let kMinPercentOfVisiblePartToCompleteAnimation = CGFloat(0.5)
private let kSwipeDownThreshold = CGFloat(1000)
private weak var presentedViewController: BottomPresentableViewController?
private weak var transitioningDelegate: BottomPopupTransitionHandler?
private unowned var attributesDelegate: BottomPopupAttributesDelegate
weak var delegate: BottomPopupDismissInteractionControllerDelegate?

private var currentPercent: CGFloat = 0 {
didSet {
delegate?.dismissInteractionPercentChanged(from: oldValue, to: currentPercent)
}
}

init(presentedViewController: BottomPresentableViewController?, attributesDelegate: BottomPopupAttributesDelegate) {
self.presentedViewController = presentedViewController
self.transitioningDelegate = presentedViewController?.transitioningDelegate as? BottomPopupTransitionHandler
self.attributesDelegate = attributesDelegate
super.init()
preparePanGesture(in: presentedViewController?.view)
}

private func finishAnimation(withVelocity velocity: CGPoint) {
if currentPercent > kMinPercentOfVisiblePartToCompleteAnimation || velocity.y > kSwipeDownThreshold {
finish()
} else {
cancel()
}
}

private func preparePanGesture(in view: UIView?) {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
presentedViewController?.view?.addGestureRecognizer(panGesture)
}

@objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) {
guard attributesDelegate.popupShouldBeganDismiss else { return }

let translationY = pan.translation(in: presentedViewController?.view).y
currentPercent = min(max(translationY/(presentedViewController?.view.frame.size.height ?? 0), 0), 1)

switch pan.state {
case .began:
transitioningDelegate?.isInteractiveDismissStarted = true
presentedViewController?.dismiss(animated: true, completion: nil)
case .changed:
update(currentPercent)
default:
let velocity = pan.velocity(in: presentedViewController?.view)
transitioningDelegate?.isInteractiveDismissStarted = false
finishAnimation(withVelocity: velocity)
}
}
}
34 changes: 34 additions & 0 deletions AVStackController/Sources/BottomPopupPresentAnimator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// DraggablePresentAnimator.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//

import UIKit

final class BottomPopupPresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private unowned var attributesOwner: BottomPresentableViewController

init(attributesOwner: BottomPresentableViewController) {
self.attributesOwner = attributesOwner
}

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return attributesOwner.popupPresentDuration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toVC = transitionContext.viewController(forKey: .to)!
transitionContext.containerView.addSubview(toVC.view)
let presentFrame = transitionContext.finalFrame(for: toVC)
let initialFrame = CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height), size: presentFrame.size)
toVC.view.frame = initialFrame
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
toVC.view.frame = presentFrame
}) { (_) in
transitionContext.completeTransition(true)
}
}
}
74 changes: 74 additions & 0 deletions AVStackController/Sources/BottomPopupPresentationController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// BottomPopupPresentationController.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//


import UIKit

final class BottomPopupPresentationController: UIPresentationController {
private var dimmingView: UIView!
private unowned var attributesDelegate: BottomPopupAttributesDelegate

override var frameOfPresentedViewInContainerView: CGRect {
get {
return CGRect(origin: CGPoint(x: 0, y: UIScreen.main.bounds.size.height - attributesDelegate.popupHeight), size: CGSize(width: presentedViewController.view.frame.size.width, height: attributesDelegate.popupHeight))
}
}

private func changeDimmingViewAlphaAlongWithAnimation(to alpha: CGFloat) {
guard let coordinator = presentedViewController.transitionCoordinator else {
dimmingView.backgroundColor = UIColor.black.withAlphaComponent(alpha)
return
}

coordinator.animate(alongsideTransition: { _ in
self.dimmingView.backgroundColor = UIColor.black.withAlphaComponent(alpha)
})
}

init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, attributesDelegate: BottomPopupAttributesDelegate) {
self.attributesDelegate = attributesDelegate
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
setupDimmingView()
}

override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}

override func presentationTransitionWillBegin() {
containerView?.insertSubview(dimmingView, at: 0)
changeDimmingViewAlphaAlongWithAnimation(to: attributesDelegate.popupDimmingViewAlpha)
}

override func dismissalTransitionWillBegin() {
changeDimmingViewAlphaAlongWithAnimation(to: 0)
}

@objc private func handleTap(_ tap: UITapGestureRecognizer) {
guard attributesDelegate.popupShouldBeganDismiss else { return }
presentedViewController.dismiss(animated: true, completion: nil)
}

@objc private func handleSwipe(_ swipe: UISwipeGestureRecognizer) {
guard attributesDelegate.popupShouldBeganDismiss else { return }
presentedViewController.dismiss(animated: true, completion: nil)
}
}

private extension BottomPopupPresentationController {
func setupDimmingView() {
dimmingView = UIView()
dimmingView.frame = CGRect(origin: .zero, size: UIScreen.main.bounds.size)
dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:)))
swipeGesture.direction = [.down, .up]
dimmingView.isUserInteractionEnabled = true
[tapGesture, swipeGesture].forEach { dimmingView.addGestureRecognizer($0) }
}
}
58 changes: 58 additions & 0 deletions AVStackController/Sources/BottomPopupTransitionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// DraggableTransitioningDelegate.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//

import UIKit

final class BottomPopupTransitionHandler: NSObject, UIViewControllerTransitioningDelegate {
private let presentAnimator: BottomPopupPresentAnimator
private let dismissAnimator: BottomPopupDismissAnimator
private var interactionController: BottomPopupDismissInteractionController?
private unowned var popupViewController: BottomPresentableViewController
fileprivate weak var popupDelegate: BottomPopupDelegate?

var isInteractiveDismissStarted = false

init(popupViewController: BottomPresentableViewController) {
self.popupViewController = popupViewController

presentAnimator = BottomPopupPresentAnimator(attributesOwner: popupViewController)
dismissAnimator = BottomPopupDismissAnimator(attributesOwner: popupViewController)
}

//MARK: Public
func notifyViewLoaded(withPopupDelegate delegate: BottomPopupDelegate?) {
self.popupDelegate = delegate
if popupViewController.popupShouldDismissInteractivelty {
interactionController = BottomPopupDismissInteractionController(presentedViewController: popupViewController, attributesDelegate: popupViewController)
interactionController?.delegate = self
}
}

//MARK: Specific animators
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return BottomPopupPresentationController(presentedViewController: presented, presenting: presenting, attributesDelegate: popupViewController)
}

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return presentAnimator
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissAnimator
}

func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return isInteractiveDismissStarted ? interactionController : nil
}
}

extension BottomPopupTransitionHandler: BottomPopupDismissInteractionControllerDelegate {
func dismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) {
popupDelegate?.bottomPopupDismissInteractionPercentChanged(from: oldValue, to: newValue)
}
}
52 changes: 52 additions & 0 deletions AVStackController/Sources/BottomPopupUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// BottomPopupUtils.swift
// CREDAssignment
//
// Created by Anand Verma on 10/08/20.
// Copyright © 2020 CRED. All rights reserved.
//


import UIKit

typealias BottomPresentableViewController = BottomPopupAttributesDelegate & UIViewController

public protocol BottomPopupDelegate: class {
func bottomPopupViewLoaded()
func bottomPopupWillAppear()
func bottomPopupDidAppear()
func bottomPopupWillDismiss()
func bottomPopupDidDismiss()
func bottomPopupDismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat)
}

public extension BottomPopupDelegate {
func bottomPopupViewLoaded() { }
func bottomPopupWillAppear() { }
func bottomPopupDidAppear() { }
func bottomPopupWillDismiss() { }
func bottomPopupDidDismiss() { }
func bottomPopupDismissInteractionPercentChanged(from oldValue: CGFloat, to newValue: CGFloat) { }
}

public protocol BottomPopupAttributesDelegate: class {
var popupHeight: CGFloat { get }
var popupTopCornerRadius: CGFloat { get }
var popupPresentDuration: Double { get }
var popupDismissDuration: Double { get }
var popupShouldDismissInteractivelty: Bool { get }
var popupDimmingViewAlpha: CGFloat { get }
var popupShouldBeganDismiss: Bool { get }
var popupViewAccessibilityIdentifier: String { get }
}

public struct BottomPopupConstants {
static let kDefaultHeight: CGFloat = 377.0
static let kDefaultTopCornerRadius: CGFloat = 10.0
static let kDefaultPresentDuration = 0.5
static let kDefaultDismissDuration = 0.5
static let dismissInteractively = true
static let shouldBeganDismiss = true
static let kDimmingViewDefaultAlphaValue: CGFloat = 0.5
static let defaultPopupViewAccessibilityIdentifier: String = "bottomPopupView"
}
Loading

0 comments on commit c2e86d4

Please sign in to comment.