Awesome Open Source
Awesome Open Source


A highly customizable UINavigationController suited for macOS.

MIT License Carthage compatible Platform OSX

Table of Contents


If you're familiar with UINavigationController API, you won't be lost, it's quite the same.

Creating the navigation controller

Creating a navigation controller is easy, here is the constructor signature:

init(rootViewController: NSViewController, contentView: NSView, navigationBarView: NSView)

It takes 3 arguments:

  1. rootViewController: the controller you want to be at the bottom of the navigation stack.
  2. contentView: the view you want to be used as the container of pushed views.
  3. navigationBarView: the view you want to be used as the container of pushed views in the navigation bar.

Note: JSNavigationController does not hold reference to the views you pass as contentView.

Pushing view controllers

func push(viewController: NSViewController, animated: Bool)
func push(viewController: NSViewController, animation: AnimationBlock?)
func push(viewController: NSViewController, contentAnimation: AnimationBlock?, navigationBarAnimation: AnimationBlock?)

Note: pushing a view controller that is already in the navigation stack will have no effect.

In order to push a view in the navigation bar as well, the pushed view controller must conform to the JSNavigationBarViewControllerProvider protocol, which is defined as follow:

public protocol JSNavigationBarViewControllerProvider: class {
	weak var navigationController: JSNavigationController? { get set }
	func navigationBarViewController() -> NSViewController

Popping view controllers

• Popping to the previous view controller

func popViewController(animated: Bool)
func popViewController(animation: AnimationBlock?)
func popViewController(contentAnimation: AnimationBlock?, navigationBarAnimation: AnimationBlock?)

• Popping to the root view controller

func popToRootViewController(animated: Bool)
func popToRootViewController(animation: AnimationBlock?)
func popToRootViewController(contentAnimation: AnimationBlock?, navigationBarAnimation: AnimationBlock?)

• Popping to a specific view controller

func pop(toViewController viewController: NSViewController, animated: Bool)
func pop(toViewController viewController: NSViewController, animation: AnimationBlock?)
func pop(toViewController viewController: NSViewController, contentAnimation: AnimationBlock?, navigationBarAnimation: AnimationBlock?)

Note: do nothing if the specified view controller is not in the navigation stack or is the top view controller.

Custom animations

How does AnimationBlock works? Let's take a look at its declaration:

typealias AnimationBlock = (_ fromView: NSView?, _ toView: NSView?) -> (fromViewAnimations: [CAAnimation], toViewAnimations: [CAAnimation])
  • fromView: it's the view currently on screen (the view to hide).
  • toView: the view that will be on screen after the animation completed (the view to show).

The block must return a tuple which contains animations for corresponding views.

Note: at the end of animations, fromView is removed from its superview and all animations attached to its layer are also removed. It means that you can't have an animation where both fromView and toView are visible at the end.


A simple crossfade animation:

let animation: AnimationBlock = { (_, _) in
	let fadeInAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
	fadeInAnimation.fromValue = 0.0
	fadeInAnimation.toValue = 1.0
	fadeInAnimation.duration = 0.25
	fadeInAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
	fadeInAnimation.fillMode = kCAFillModeForwards
	fadeInAnimation.removedOnCompletion = false

	let fadeOutAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
	fadeOutAnimation.fromValue = 1.0
	fadeOutAnimation.toValue = 0.0
	fadeOutAnimation.duration = 0.25
	fadeOutAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
	fadeOutAnimation.fillMode = kCAFillModeForwards
	fadeOutAnimation.removedOnCompletion = false

	return ([fadeOutAnimation], [fadeInAnimation])


public protocol JSNavigationControllerDelegate: class {
	func navigationController(_ navigationController: JSNavigationController, willShowViewController viewController: NSViewController, animated: Bool)
	func navigationController(_ navigationController: JSNavigationController, didShowViewController viewController: NSViewController, animated: Bool)


You should only push JSViewController subclasses, that way you'll have access to destinationViewController and destinationViewControllers properties.

Use JSNavigationControllerSegue for segues class.

Segues identifiers

  • rootViewController to set the root view controller of the navigation controller.
  • navigationBarViewController to set the navigation bar view controller of a view controller.
  • navigationControllerPush to set the destination view controller of a view controller.

If your view controller can push multiple view controllers, use navigationControllerPush#NameOfYourViewController pattern.

That way, you can retrieve a specific view controller and push it like this:

let myViewController = destinationViewControllers["NameOfYourViewController"]
navigationController?.push(viewController: myViewController, animated: true)

See the ExampleStoryboard project for an example of implementation.


See the Example and ExampleStoryboard projects in the .zip file.


  • Xcode 7
  • OS X 10.11



Add github "juliensagot/JSNavigationController" to your Cartfile.


Add pod 'JSNavigationController', :git => '' to your Podfile.


Download the .zip file and add the content of JSNavigationController/Sources folder to your project.

Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
swift (7,706
macos (1,748