Skip to content

creastel/ios-card-transition

Repository files navigation

CSCardTransition_dark CSCardTransition_light

CSCardTransition

Version Xcode 10.0+ iOS 10.0+ Swift 5.0+ License

CSCardTransition is a small library allowing you to create wonderful push and pop transition animations like in the App Store. It works side by side with your navigation controller, and ensure that you only code what's necessary for your custom transition.

Screenshots

Library Example On Time
Available for iOS
My Toolbox
Available for iOS and MacOS

Installation

CocoaPod

Add this line to your Podfile.

pod 'CSCardTransition'

Manually

Drag the files inside the CSCardTransition folder into your project.

Usage

You can go through the example provided in this repo, and run it, to see how to implement beautiful transitions. You can also follow the following steps.

After creating a "Card Presenter" View Controller and your "Presented Card" View Controller, you will need to follow this steps:

Swizzle your UINavigationController (Required step)

Add the 4 following lines to your custom UINavigationController.

import UIKit
// 1
import CSCardTransition

class YourNavigationController: UINavigationController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
         // 2
        delegate = self
    }
    ...
}

extension YourNavigationController: UINavigationControllerDelegate {
 
    func navigationController(
        _ navigationController: UINavigationController,
        animationControllerFor operation: UINavigationController.Operation,
        from fromVC: UIViewController,
        to toVC: UIViewController
    ) -> UIViewControllerAnimatedTransitioning? {
        // 3
        return CSCardTransition.navigationController(
            navigationController,
            animationControllerFor: operation,
            from: fromVC,
            to: toVC
        )
    }
    
    func navigationController(
        _ navigationController: UINavigationController,
        interactionControllerFor animationController: UIViewControllerAnimatedTransitioning
    ) -> UIViewControllerInteractiveTransitioning? {
        // 4
        return CSCardTransition.navigationController(
            navigationController,
            interactionControllerFor: animationController
        )
    }
    
}

Card View Presenter (Required step)

In the View Controller that serves as the view "Presenter", in other word, in the View Controller that contains the card to be expanded, you will need to add the CSCardViewPresenter protocol and define what UIView should be used as the card to be presented:

extension YourViewController: CSCardViewPresenter {
    var cardViewPresenterCard: UIView? {
        return cardView // Return the view that represents the start of your transition
    }
}

Presented Card View (Required step)

In the View Controller that serves as the "Presented" card view, in other word, in the View Controller that is the expanded Card View, you will need to define a CSCardTransitionInteractor and add the CSCardPresentedView protocol:

class YourViewController: UIViewController {
    ...
    // To enable the drag gesture to pop out the card view and go back to the parent view controller.
    lazy var cardTransitionInteractor: CSCardTransitionInteractor? = CSCardTransitionInteractor(viewController: self)
    ...
}
extension YourViewController: CSCardPresentedView {
    ... // You will probably add certain methods here later.
}

Status Bar Style Transition (Optional step)

If your status bar style is different from one view to the other, you will need to implement the cardViewPresenterShouldUpdateBar(to style: UIStatusBarStyle) protocol in the Card Presenter View Controller and in the Presented Card View Controller so that the transition stays smooth. To do so, you may want to add a new var that determines the current status bar style like in the following example:

class YourViewController: UIViewController {
    ...
    // /!\ Set the following var to the style of the status bar in you view controller (here: .default)
    private var currentStatusBarStyle: UIStatusBarStyle = .default
    override var preferredStatusBarStyle: UIStatusBarStyle { currentStatusBarStyle } // Overrides the status bar style
    ...
}
extension YourViewController: CSCardViewPresenter // Do the same for the CSCardPresentedView {
    ...
    /// Called when the status bar style should be updated to match the transition progress
    func cardViewPresenterShouldUpdateBar(to style: UIStatusBarStyle) {
        currentStatusBarStyle = style
        setNeedsStatusBarAppearanceUpdate() // Tell the OS to change the status bar style
    }
}

Custom transition in the Presented view (Optional but often followed step)

You may want to customize you transition by disabling/enabling/updating layout constraints, changing view's alpha, corner radius, ... You may also want to disable the card transition at some point for some reason (for example when you don't know if the card view is still on screen in the Parent View Controller). You can do so by implementing the following methods. You can read the code example to get some ideas.

extension YourViewController: CSCardPresentedView {
    /// A Boolean indicating whether or not the card transition should occur.
    var cardTransitionEnabled: Bool { get }
        
    /// Called when the transition to this view controller is about to start.
    /// - Parameter cardView: The UIView used in the parent view controller to start the transition.
    func cardPresentedViewWillStartPresenting(from cardView: UIView)
    /// Called when the transition to this view controller just started.
    /// Autolayout changes will automatically be animated here.
    func cardPresentedViewDidStartPresenting()
    /// Called when the transition to this view controller is currently in progress
    /// - Parameter progress: The current progress of the transition (between 0 and 1)
    func cardPresentedViewDidUpdatePresentingTransition(progress: CGFloat) {}
    /// Called when the transition to this view controller is about to end.
    func cardPresentedViewWillEndPresenting() {}
        
    /// Called when the transition back to the parent view controller is about to start.
    func cardPresentedViewWillStartDismissing() {}
    /// Called when the transition back to the parent view controller has started.
    func cardPresentedViewDidStartDismissing() {}
    /// Called when the transition back to the parent view controller is about to be canceled.
    func cardPresentedViewWillCancelDismissing() {}
    /// Called when the transition back to the parent view controller is currently in progress.
    /// - Parameter progress: The current progress of the transition (between 0 and 1)
    func cardPresentedViewDidUpdateDismissingTransition(progress: CGFloat) {}
    /// Called when the transition back to the parent view controller is about to be completed.
    func cardPresentedViewWillEndDismissing() {}
}

Custom transition in the View Presenter (Optional step)

You may want to customize you transition in the presenter by disabling/enabling layout constraints, changing view's alpha, corner radius, ... You can do so by implementing the following methods. You can read the code example to get some ideas.

extension YourViewController: CSCardViewPresenter {
    /// Called when the transition to the card view controller just started.
    /// Autolayout changes will automatically be animated here.
    func cardViewPresenterDidStartDismissing() {}
    /// Called when the transition to the card view controller is currently in progress
    /// - Parameter progress: The current progress of the transition (between 0 and 1)
    func cardViewPresenterDidUpdateDismissingTransition(progress: CGFloat) {}
    /// Called when the transition to the card view controller is about to end.
    func cardViewPresenterWillEndDismissing() {}
    
    /// Called when the transition back to this view controller is about to start.
    func cardViewPresenterWillStartPresenting() {}
    /// Called when the transition back to this view controller has started.
    func cardViewPresenterDidStartPresenting() {}
    /// Called when the transition back to this view controller is about to be canceled.
    func cardViewPresenterWillCancelPresenting() {}
    /// Called when the transition back to this view controller is currently in progress.
    /// - Parameter progress: The current progress of the transition (between 0 and 1)
    func cardViewPresenterDidUpdatePresentingTransition(progress: CGFloat) {}
    /// Called when the transition back to this view controller is about to be completed.
    func cardViewPresenterWillEndPresenting() {}
}

Customize your transition

You can customize all of the properties below simply by providing a custom instance of CSCardTransitionProperties to the Presented View Controller. For instance:

extension YourViewController: CSCardPresentedView {
	var cardTransitionProperties: CSCardTransitionProperties { 
		return CSCardTransitionProperties(
			/// Presenting animation properties
			presentPositioningDuration: TimeInterval, 
			presentResizingDuration: TimeInterval, 
			presentStatusStyleUpdateDuration: TimeInterval, 
			/// Dismissing animation properties
			dismissPositioningDuration: TimeInterval, 
			dismissResizingDuration: TimeInterval, 
			dismissBlurDuration: TimeInterval, 
			dismissStatusStyleUpdateDuration: TimeInterval, 
			/// Fade transition duration between presented card view and presenter card view
			dismissFadeCardAnimationTime: TimeInterval, 
			/// How far should the user swipe to dismiss the view
			preDismissingTransitionProgressPortion: CGFloat, 
			/// Cancel animation duration
			cancelTransitionResizingDuration: TimeInterval, 
			/// Blurred background color during transition
			transitionBackgroundColor: UIColor
		)
	}
}

Note: all of these properties have default values, so you can skip the ones you don't want to change in the instance creation.

Debug your transition

The transition must be quick in production, but slow it down (to 1/10) during its development so you can easily see what is working and what still needs some improvements. You can enable debug mode by simply providing a debug instance of the CSCardTransitionProperties to the Presented View Controller.

extension YourViewController: CSCardPresentedView {
	var cardTransitionProperties: CSCardTransitionProperties { 
		return .debug
	}
}

Good to know

A CSCardPresentedView can also be a CSCardViewPresenter, that is what makes this library so powerful 😉

Informations

This library is brought to you by Creastel, French Digital Agency. You can reach us at hello@creastel.com.

Github Banner - CSCardTransition