Giter VIP home page Giter VIP logo

ezcustomnavigation's Introduction

Logo Easy Custom Navigation Controller that allows:

  • popping view controllers with pan gesture from center too. (Much like Instagram or Telegram do).
  • reopen a popped view controller with a right edge pan gesture. (Like the forward page action on Safari).

0 lines of code

Just replace your UINavigationController with the EZNavigationController and you are set with the default values.

Features

Left screen edge Pop

The user can always use the default UINavigationController like gesture from the left edge of the screen, no matter the content inside the ViewControllers embedded in the Navigation Controller.

Center screen Pan-to-Pop

In sections of the viewControllers where there is no horizontal pan gesture conflicting with our navigation controller pan gesture, the user will be able to Pan-to-pop from the center of the screen to pop view controllers like he is accustomed to in Instagram or Telegram apps.

Center screen Pan-to-Pop (on top of scrollViews)

In sections of the viewControllers where there is an horizontal scrollView, the user will be able to Pan-to-Pop from the center of the screen if the scrollView is at the start position (far left of the content) i.e. contentOffset.x = 0. Otherwise the scroll will work as usual.

GIF Pan-to-pop demo

Right screen edge Unpop (disabled by default)

After a NavigationController has popped a ViewController out of the stack, this ViewController can be unpoped with a pan gesture from the right screen edge. This is the same behavior you already have on safari when you go back to a page: you can go back forward to the page you just closed by swiping from the right screen edge.

To enable this behavior you have to create an EZNavigationConfiguration with an EZUnpopConfiguration and either:

  • Pass it to a EZNavigationControllerTransitionHelper to make it use this configuarion for a specific NavigationController
  • Replace the static defaulConfiguration of the EZNavigationConfiguration to make every EZNavigationController use it

GIF Unpop demo

To enable the unpop in one line:

EZNavigationConfiguration.defaultConfiguration = EZNavigationConfiguration(unpop: EZUnpopConfiguration(ttl: 30, stackDepth: 5))

You can put this line of code in AppDelegate.application(_:didFinishLaunchingWithOptions:) to enable the unpop on all EZNavigationController created after this line.

Installation

Cocoapods

Just add the EZCustomNavigation dependency with the version you want in the podfile:

pod 'EZCustomNavigation', '1.1.2'

Usage

Storyboard

Just insert the class name EZNavigationController in the Navigation Controller custom class tab.

Storyboard

NB: If you installed via Cocoapods be sure to add the module from which the EZNavigationController class is taken (EZCustomNavigation)

Programmatically

If you don't use storyboard you just need to use EZNavigationController instead of UINavigationController when presenting

let navigationController = EZNavigationController(rootViewController: SomeOtherUIViewController())
self.present(navigationController, animated: true, completion: nil)

Or when subclassing

import EZCustomNavigation

class CustomNavigationController: EZNavigationController {
    
}

Customization

UIScrollView Behavior

In order to prevent a scroll from interfering with the EZNavigationController pan gesture (with the left bounce), every orizzontally scrollable UIScrollView is, by default, considered eligible for avoiding left bounce functionality if it's embedded inside an EZNavigationController.

This behavior is defined inside the default implementation of the static UIScrollView.shouldAvoidLeftBounceBlock which is:

public static var shouldAvoidLeftBounceBlock: ((UIScrollView)->(Bool))? = { scrollView in
        // Returns true if scrollView has a EZNavigationController in it's responder chain, false otherwise
        return scrollView.isDescendantOfClass(EZNavigationController.self)
    }

You can change this behavior for every scrollView by replacing this static block with your own implementation.

If you instead want to override this behavior for a single scrollView, you can just set on that single scrollView the block property shouldAvoidLeftBounceBlock, returning the behavior you want for that specific scrollView at that specific time.

let someScrollView = UIScrollView()
someScrollView.shouldAvoidLeftBounceBlock = {
    return true // or false, if you want to remove our custom implementation for that scrollView at that time
}

Those blocks will be called right when a scroll is happening on a scrollView and, in case they return true, then left bounce behavior is blocked to allow pan to pop.

  • If the block for a single scrollView is provided, than the static one will be ignored.
  • If the block for a single scrollView is NOT provided, than the static one will be used.
  • If also the static one is nil (meaning that you removed the default one), then it is treated as a false, removing the functionality all togheter.

UINavigationController extension

To allow customization of some behaviors, you can use the methods of the UINavigationController extension instead of the premade EZNavigationController.

public func addCustomTransitioning(_ transitionHelper: EZNavigationControllerTransitionHelper = EZNavigationControllerTransitionHelper())

public func removeCustomTransitioning()

You can do this for several reasons:

  • If for some reason you can't override some custom implementation of UINavigationController (let's say you have another library that subclasses that and you can't change it's implementation)
  • If you want to customize some animations or some interactions. So you'll need to call those methods with your custom parameters or subclasses.
  • If you want to change the configuration to the specific helper of this NavigationController. Expecially useful if you want to enable the unpop behavior.

Use with some other UINavigationController library

For example, here is provided an example for some other library that gives you a NavigationController implementation (that derivers from UINavigationController and hopefully doesn't conflict with this library)

class MyNavigationImplementation: SomeOtherLibrariesImplementationOfUINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        addCustomTransitioning()
    }
    
    deinit {
        removeCustomTransitioning()
    }
}

Customize animations/interactions

And here is provided an example with some custom animator

class MyCustomAnimatorNavigationImplementation: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let helper = EZNavigationControllerTransitionHelper(transitionCoordinator:
            EZTransitionCoordinator(presentingAnimator: SomePresentingAnimator(),
                                    dismissingAnimator: SomeDismissingAnimator(),
                                    interactionController: SomeInteractionController()))
        addCustomTransitioning(helper)
    }
    
    deinit {
        removeCustomTransitioning()
    }
}

NB: be aware that the default impementation of Scroll Behavior won't work if you don't use the default EZNavigationController. In case you use some other UINavigationController subclass you must provide your own implementation of scroll behavior (but it shouldn't be hard following the default example).

Enable Unpop behavior

Unpop is disabled by default for several reasons:

  • Retrocompatibility: previous users may not want this behavior enabled by simply upgrading from 1.0.0 to 1.1.0
  • It's situational: you don't always want to have this behavior enabled. You sometimes want to close a view controller and not allow for someone to reopen it by unpopping
  • Delays controllers deallocation: since view controllers are kept alive in an internal unpopStack, they may not be deallocated when you expect them to be. This means that memory is freed later and that other logic, connected to deinit, may happen later too. Extra care must be taken when you decide it's the case to enable unpop for one or all EZNavigationControllers

As an example on why you SHOULD NOT use this, you can think of a Controller with a VideoPlayer. You expect that when you pop this ViewController out of the NavigationController it automatically releases the Player and therefore stops the playback. But if this ViewController is kept alive by the unpopStack (for some time or forever) playback may continue while the user is navigating the rest of the app. In this case, for example, you may need to stop the player on the ViewController.willDisappear() method, or something like that.

Why should I enable it

Simply because sometimes the user is working on something and ends up popping the view controller by mistake. Maybe they were writing a very important text, or maybe they were editing a cool picture. Wouldn't be cool if they cold bring the view controller back after they dismissed it?

Every editing app for example has both an "undo" and "redo" operations. Even on Safari the user can pan from the left screen edge to undo a navigation operation (or go back) and pan from the right edge to redo a navigation operation (go forward).

Of course there are some safer ways to avoid a closure by mistake (think about a pop up message asking if you really want to lose all your work, for example) but this is not what you always want (safari for example doesn't do this).

Also, sometimes the user just wants to come back, look at something from a previous screen, and then come back to what they were doing in the other screen. And this is really well done with the unpop behavior.

In a few words, you just simply have a "re-do" option, once you get back to a previous view controller, to once again go to the latest one you just closed, with all the unsaved work already there.

How can I enable it

You can just enable it by default on all EZNavigationController by changing the defaultConfiguration in the AppDelegate:

EZNavigationConfiguration.defaultConfiguration = EZNavigationConfiguration(unpop: EZUnpopConfiguration(ttl: 30, stackDepth: 5))

If you do this, all EZNavigationController created after this configuration change will have the unpop behavior enabled.


Or you can create your own NavigationController subclass that you want to use in the cases you need it and pass the configuration to the transition helper that you use to add the custom transitioning.

class MyUnpopEnabledNavigationImplementation: UINavigationController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let config = EZNavigationConfiguration(unpop: EZUnpopConfiguration(ttl: 30, stackDepth: 5))
        let coordinator = EZTransitionCoordinator()
        let helper = EZNavigationControllerTransitionHelper(transitionCoordinator: coordinator,
                                                            configuration: config)
        addCustomTransitioning(helper)
    }
    
    deinit {
        removeCustomTransitioning()
    }
}

EZNavigationConfiguration

For the time being, you can only configure the unpop behavior. If you pass the UnpopConfiguration you enable it. You can set 2 properties:

  • ttl: the amount of seconds the popped view controller are kept inside the unpopStack
  • stackDepth: the maximum amount of view controllers that can be stored in the stack and can potentially be unpopped

TTL can also be nil, even if it's unadvised. If present it must be greater than 0. StackDepth also needs to be greater than 0. The greater this parameters are, the more time and for more time the view controllers are kept in the stack.

Therefore it's suggested to not use arbitrarily large numbers. Choose the stackDepth and the TTL according to your usecase.

Contribution

Feel free to open issues or make pull requests if you see something is wrong or could be better

License

This project is licensed under the MIT License - see the LICENSE.txt file for details

ezcustomnavigation's People

Contributors

enricoza avatar redetection avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

ezcustomnavigation's Issues

When used inside of a tab bar controller, and hiding the bottom bar on push, the destination controller has a gap at the bottom

When using EZNavigationController on a navigation controller within a UITabBarController that has a controller with hidesBottomBarWhenPushed enabled, there is an effect where the destination view controller is not rendered at the bottom. This is only a problem during the animation, the controller looks correct when the animation is finished. The interactive pop also works perfectly.

IMG_D2A9982CA9B1-1

Non-standard push/pop look and feel

Another issue started to bother me: animation feels made up (and in fact it is custom) and the difference is clearly visible on modern iOS 14 devices. Previous view controller on the stack is usually dimmed during transition. Also the animation curve is linear and not ease in/out. I understand it feels better to have it linear when interactive and it is easily achievable, but it seems to be tweaked even when I tap Back or use standard pushViewController (I see they are swizzled). Do you know a way to in to retain original look and feel and reuse what is provided by the OS?

Pan-To-Pop conflicts with `leadingSwipeActions` on UITableView cells

On a tableView with leadingSwipeActions the pan-to-pop gesture takes precedence over the _UISwipeActionPanGestureRecognizer of the tableView, making it impossible to show and activate those leading actions.

It should be allowed to, at least, disable the pan-to-pop entirely on some view controllers or, even better, it should be allowed to make it require the other gesture to fail and, therefore, take precedence.

Pan-to-pop gesture not responsive

Firstly love simplicity of the plugin!

Upon some testing I realized the pan-to-pop gesture isn't quite responsive compared to similar gestures implemented in major apps like Facebook.

For example, if you make a quick swipe that only moves the screen a little to the right, the view will bounce back instead of getting dismissed. The bounce-back requires the user to swipe again with a larger finger movement, thus slows down user action and makes the interaction feel not responsive.

It would be ideal if this can be fixed, otherwise the custom pan-to-pop gesture isn't quite usable.

Thanks.

Proposal: Unpop ViewController

Could be useful in some cases to come back to a popped view controller, keeping it in memory for a while.

It could maybe be configurable with:

  • amount of VC to keep in memory
  • amount of time to keep that VC in memory

Of course the stack would be cleaned every time a new VC is pushed in the stack.

It should have a way to clean that stack of popped view controller punctually and a way to opt-out of the unpop behavior for a single ViewController or a single NavigationController or from the whole app.

The default behavior should be to not have this option enabled for retrocompatibility reasons.

Glitch when fast scrolling back

When you start the pan gesture during another pop animation, you can sometimes see a glitch where the first VC suddenly becomes full screen again.

This can easily. be experienced if you scroll back indefinitely very fast.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.