Note: This article is part of the course Introduction to iOS using UIKit I've given many times in the past. The course was originally in Spanish, and I decided to release it in English so more people can read it and hopefully it will help them.
Introduction
So far, we've seen example applications where we only had a single view in the screen. In real world apps we have several screens per app. The user starts their journey in one of them but depending on the actions they take, the app will show them other screens.
The ability of moving between screens in an app is called navigation.
Stacks
What is a stack?
- A stack is a data structure in which we can insert elements.
- Whenever we add an element to the stack (push), it will be put at the top.
- If we remove an element from the stack (pop), we'll always remove the top element in the stack, so the element that was below the top, will become the top after that.
Navigation Stack
In iOS, navigation works using one or many stacks. Each element in the navigation stack is a screen, and each screen is represented by a UIViewController
.
The element (screen) that is in the top of the navigation stack is the screen that's visible to the user in the app.
Present/Dismiss
There is a "global" navigation stack in iOS. We don't have to do anything to create it. Just by creating an app in iOS, we'll have that global navigation stack ready to be used.
Every UIViewController
subclass have a present
and a dismiss
methods.
Let's see an example:
If we want to navigate from FirstScreenViewController
to SecondScreenViewController
, we need to:
- Instantiate
SecondScreenViewController
from a method onFirstScreenViewController
. - Call the
present
method right after that, passing to it theSecondScreenViewController
instance, aBool
indicating whether we want to present the screen using an animation transition, and optionally, also a closure that will be executed as soon as the transition has finished.
class FirstScreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func navigateButtonPressed() {
let secondViewController = SecondViewController()
present(secondViewController, animated: true, completion: nil)
}
}
Optionally, we can configure the property modalPresentationStyle
in SecondScreenViewController
with the value UIModelPresentationStyle.fullScreen
before navigating to it, so the destination view controller will fit the full size of the screen.
@IBAction func navigateButtonPressed() {
let secondViewController = SecondViewController()
secondViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
present(secondViewController, animated: true, completion: nil)
}
The problem we have now is that, as it's now fullscreen, we can't go back to the first screen anymore. To avoid this, we can configure an action in the second screen, using the dismiss
method.
The dismiss
method takes two parameters: a Bool
indicating if we want an animated transition, and an optional closure
, in case we want to execute custom code after the transition has finished.
@IBAction func goBackButtonScreen() {
dismiss(animated: true, completion: nil)
}
UINavigationController
However, the practice of doing present/dismiss is commonly used to show a modal to the user in specific situatioons.
Most commonly, we'll use UINavigationController
, which is a class that contains an internal navigation stack.
To add a screen/controller to a UINavigationController
, we'll use the push
method.
To remove a screen/controller from a UINavigationController
, we'll use the pop
method.
A UINavigationController
is instantiated with a UIViewController
that's called the root view controller, and that will be the bottom of the stack.
In addition, using a UINavigationController
gives us a view at the top of our screens that's called UINavigationBar
, that contains the current screen title, helper buttons at the sides, and back button, among others.
Let's try to modify the second screen of our app, so that it will be contained inside a UINavigationController
.
Remember this action:
@IBAction func navigateButtonPressed() {
let secondViewController = SecondViewController()
secondViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
present(secondViewController, animated: true, completion: nil)
}
We'll modify it so that secondViewController
will be inside a UINavigationController
.
@IBAction func navigateButtonPressed() {
let secondViewController = UINavigationController(rootViewController: SecondViewController())
secondViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
present(secondViewController, animated: true, completion: nil)
}
The view that we see at the top is the UINavigationBar
.
Something we can do to personalize it, is to assign a title to it. To do so, we'll modify the property title
in the view controller:
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Second"
}
@IBAction func goBackButtonPressed() {
dismiss(animated: true, completion: nil)
}
}
In order to perform a transition in a UINavigationController
we need to:
- Create another screen. I mean, a new
UIViewController
- Inside
SecondScreenViewController
, perform apush
to the third screen. - To do this
push
, let's keep in mind that the classUIViewController
has an optional property callednavigationController
with typeUINavigationController?
.
@IBAction func thirdScreenButtonPressed() {
let thirdViewController = ThirdScreenViewController()
navigationController?.pushViewController(thirdViewController, animated: true)
}
You might have noticed that the navigation bar automatically adds a button to return to the previous screen. If we want to do a back navigation programmatically, we'll use the pop
method from UINavigationController
.
Small comment: to add a padding to the button, we can set the content insets of it in the attributes inspector
@IBAction func popButtonPressed() {
navigationController?.popViewController(animated: true)
}
Passing data
How could we send data from one screen to the next one. The answer is, that before performing the navigation, we can set a variable in the destination UIViewController
.
We must NEVER configure a view on the destination controller, because they aren't initialized until the navigation is committed.
Top comments (0)