Creating a View Controller
A view controller can be instantiated in several ways:
// Programmatic instantiation
let controllerA = ExampleController()
// From a XIB file
let controllerB = ExampleController(nibName: "ExampleController", bundle: nil)
// From a storyboard
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
// Obtain the initial view controller
let initialController = storyboard.instantiateInitialViewController()
// Or fetch by a Storyboard ID
if let controllerC = storyboard.instantiateViewController(withIdentifier: "detailScene") as? DetailController {
// use controllerC
}
The view owned by a controller is loaded lazily. You can check whether the view is already in memory using the isViewLoaded property. Once the view is fully loaded, viewDidLoad() is called automatically.
Organizing Multiple Controllers
Most iOS apps contain more than one screen. A common pattern is to use a parent controller that manages several child controllers. UIKit provides two built‑in container controllers for this purpose:
UINavigationControllerUITabBarController
These containers handle layout and transitions between child controllers.
UINavigationController
UINavigationController maintains its child controllers as a stack. You add controllers by pushing them onto the stack and remove them by popping.
// Initialization and assignment as the window's root
let navigationController = UINavigationController()
window?.rootViewController = navigationController
// Pushing a new controller
navigationController.pushViewController(controllerA, animated: true)
// Pop the topmost controller
navigationController.popViewController(animated: true)
// Pop to a specific controller
navigationController.popToViewController(targetController, animated: true)
// Return to the root
navigationController.popToRootViewController(animated: true)
Customizing the Navigation Bar
The content of the navigation bar is driven by the navigationItem property of the controller on top of the stack. Key properties include:
title– a string displayed in the centertitleView– a custom view replacing the titleleftBarButtonItem/rightBarButtonItem– buttons on either sidebackBarButtonItem– back button shown when a new controller is pushed
navigationItem.title = "Profile"
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save,
target: self,
action: #selector(saveTapped))
Segues
A segue represents a transition defined in a storyboard. Each seegue is an instance of UIStoryboardSegue, which stores the source controller, the destination controller, and an optional identifier.
Automatic vs Manual Segues
- Automatic: triggered directly by a control (e.g., a tap on a button).
- Manual: performed programmatically with
performSegue(withIdentifier:sender:).
When a manual segue is triggered, the runtime:
- Finds the segue with the given identifier in the storyboard.
- Creates a
UIStoryboardSegueinstance and sets itssource. - Calls
prepare(for:sender:)on the source controller, passing the segue and the sender. - Invokes
perform()on the segue, which handles the actual transition (for example, by pushing the destination controller onto a navigation stack).
performSegue(withIdentifier: "showDetail", sender: selectedItem)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? DetailController,
let item = sender as? Item else { return }
destination.receivedItem = item
}
Passing Data Between Controllers
Forward Data Flow (A → C)
When moving from a source (A) to a destination (C),16 use prepare(for:sender:). Fetch the destination controller and set any required properties before it appears.
Reverse Data Flow (C → A)
When C needs to send data back to A (e.g., after editing), a common approach is the delegate pattern:
- Define a protocol in C that declares the callback methods.
- A conforms to that protocol and sets itself as C’s delegate before the transitoin.
- Inside C, call the delegate methods when data must be returned.
protocol FeedbackDelegate: AnyObject {
func didSubmitInformation(_ info: String)
}
class EditorController: UIViewController {
weak var delegate: FeedbackDelegate?
func finishEditing() {
delegate?.didSubmitInformation(editedText)
}
}
// In the source controller prepare(for:sender:):
if let editor = segue.destination as? EditorController {
editor.delegate = self
}