Fall 2023 | Vin Bui
In the previous chapter, we learned what views are and how to create views in UIKit. We also learned how to use AutoLayout to position our views on the screen. However, our views contained hard coded data and did not have any functionality at all. When we create apps, we want our views to respond and update to user interactions.
MVC (Model-View-Controller) is a software design pattern which is a set of rules that govern the architecture that we follow when writing our code. There are other design patterns out there such as MVVM (Model-View-ViewModel) but we will be using MVC throughout this course. To better understand MVC, let’s take a deeper look at each component.
Models are objects that represent our application’s data. Let’s look at an app we are familiar with, Eatery. The models in Eatery are the dining halls, the dishes and food items, the user’s information, etc. In other words, models contain information about our application and are often updated based on user interaction or from a backend service.
Notice that we often use structs instead of classes when representing our models. The difference between these two is that structs are value types whereas classes are reference types. What this means is that if we were to change a property of an instance of a struct, the entire instance changes. On the other hand, if we change a property of an instance of a class, the instance does not change.
For example, consider a User model with the property name. For a class object (reference type), if we changed the value of name, all locations that have a reference to that object have the new updated value for name. If this were to be a struct object (value type), changing the value of name in one location DOES NOT update other locations because a new instance is being created. We can think of reference types as sharing a Google Doc with other collaborators. If one person were to change something on their side, the others would be able to see those changes. For structs, we can think of it as making a copy of the Google Doc.
One advantage of using a class is inheritance. In other words, we can use properties and methods already defined by a parent class, commonly seen in UIKit. However, there are times when we don't need to use all of these properties and methods, making structs more preferable. This is commonly seen in SwiftUI and why using structs is a lot faster than classes.
Views are the visible components that are used to build the user interface (UI). This includes buttons, labels, images, etc. In Eatery, everything we see is a view. For example, the name of the dining hall is a UILabel which contains information from the dining hall model. We don’t see models. We see views that contain information from the models.
Controllers belong in the middle between models and views. The main goal of a controller is to establish a connection between models and views. The controller is also in charge of processing the logic to update the models and views. For example, in Eatery, there is a User model that represents the logged in user. When we sign in, we are interacting with views. How does the model update based on what we passed into the text field? Well the controller handles that logic. If we provided the correct credentials, it will modify the models and update the views such as showing a logout button. If we provided invalid credentials, then the controller will still update the view and show a red error message.
One important thing to keep note of is that views and models are separate. They cannot directly interact with each other, but rather, they must go through a controller to make that connection.
We can think of MVC as watching television: the TV is the view, the remote is the controller, and the channels/content is the model. How does the TV (view) change the channel (model)? It can’t! We must use a remote (controller) in order to do that. When we change channels using the remote, we made changes to the model and tell the TV to update its view.
Fall 2023 | Vin Bui
So far, we've only worked with one screen which may contain many views. For this single screen, we controlled the models and views with a single UIViewController. However, many of the apps that we use today contain multiple screens that we can navigate between. In UIKit, we represent every distinct screen with a separate UIViewController. There are two ways to navigate between screens: 1) pushing/popping and 2) presenting/dismissing.
Before we dive in to the technical details, let’s observe what pushing and popping looks like.

As we can see, when we tap on one of the cells, a new screen shows up. Each new screen that we push is a separate UIViewController. So then, how do we keep track of the view controllers that have been pushed to figure out which one needs to be popped? We use a navigation stack. Every time a view controller is pushed, it goes on top of the navigation stack. Think of the navigation stack as a stack of books where each book is a view controller. The last item to be pushed into this stack will be the first one to be popped out (LIFO). How do we represent this navigation stack in UIKit? We use a UINavigationController.
Inside of SceneDelegate.swift add this code to the function scene:
The important step to remember here is step 3 where we first initialize the view controller (rootVC) that we want to be displayed when the app launches. Now, we have to place rootVC into the navigation stack by creating a UINavigationController with rootVC as the root view controller.
One thing to keep in mind is that the line let rootVC = ViewController() creates a view controller whose class name is ViewController. When we create a new project, by default, a class called ViewController is created for us. If we created a new class or renamed the class to HomeViewController, then we would use HomeViewController() instead.
Now, to push and pop a view controller is very simple:
UIViewController will be the view controller object that we want to push. Most of the time, we want to set animated to true. For example, if we had a class called ProfileViewController and wanted to push it, we would write the following code in the view controller class that is pushing it (not inside ProfileViewController):
To pop the ProfileViewController, we would write this code in ProfileViewController:
We could then link this code to some action such as a tapping on a button, cell, image, etc.
Let’s observe what presenting and dismissing looks like.
As we can see, a modal sheet is presented from the bottom of the screen and gradually transitions up. This is presenting. To dismiss, we can simply click on the cancel button or more commonly, swipe downwards from the top of the modal sheet. The view controller that is being presented is displayed on top of the previous view controller. There is not a navigation stack at play here ⇒ No UINavigationController.
To present/dismiss a view controller:
For example, if we had a class called ProfileViewController and wanted to present it, we would write the following code in the view controller class that is pushing it (not inside ProfileViewController):
To dismiss the ProfileViewController, we would write this code in ProfileViewController:
As we may have notice from the function header above, there is an optional parameter called completion. This is known as a completion handler which is a function that gets executed when the function call is complete. We will discuss this in detail once we get into networking.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// 1. Capture the scene
guard let windowScene = (scene as? UIWindowScene) else { return }
// 2. Create a new UIWindow and pass in a UIWindowScene
let window = UIWindow(windowScene: windowScene)
// 3. Create a view hierarchy programmatically
let rootVC = ViewController()
let navController = UINavigationController(rootViewController: rootVC)
// 4. Set the navigation controller as the window's root view controller
window.rootViewController = navController
// 5. Set the window and call makeKeyAndVisible()
self.window = window
window.makeKeyAndVisible()
}// Push
navigationController?.pushViewController(_ viewController: UIViewController, animated: Bool)
// Pop
navigationController?.popViewController(animated: Bool)let profileVC = ProfileViewController()
navigationController?.pushViewController(profileVC, animated: true)navigationController?.popViewController(animated: true)// Presenting
present(_ viewControllerToPresent: UIViewController, animated: Bool, completion: (() -> Void)?)
// Dismissing
dismiss(animated: Bool, completion: (() -> Void)?)let profileVC = ProfileViewController()
present(profileVC, animated: true)dismiss(animated: true)

git clone https://github.com/intro-to-ios/lec3-navigation.git
OR git clone [email protected]:intro-to-ios/lec3-navigation.gitgit checkout origin/1-navigation
OR git checkout 1-navigation
git checkout origin/2-delegation
OR git checkout 2-delegationFall 2023 | Vin Bui
The main purpose of the delegate pattern is to allow a child to communicate back with its parent without the child knowing its parent’s type. This makes it much easier to maintain and write reusable code. Delegation is a 1:1 relationship with a child and its parent.
To implement delegation in Swift, we use protocols. According to Swift’s official documentation,
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.
In other words, protocols are a set of properties and methods that classes must implement when conforming to it (similar to interfaces in Java).
You can learn more about protocols , but for our purpose, we will only need to define functions inside of the protocol. For example, If we wanted to create a protocol so that a child can tell its parent to update some text, we could do the following:
Make sure to conform the protocol to AnyObject!
It’s convention to name our protocol with its purpose followed by the word Delegate at the end. We also created a function called updateText that takes in a string called newText. Now, if we had a class called ParentViewController and we wanted to conform to this protocol, we could do the following:
Now, it’s common to use an extension to implement these functions to keep our code a lot more neat:
The only changes that we made was removing UpdateTextDelegate from the original class header and moved it to the extension header, followed by the function implementation required by the protocol.
In the code above, we conformed ParentViewController to the UpdateTextDelegate protocol. The class ParentViewController is known as the delegate. The delegate is the class that conforms to that protocol.
Now, if we had a view controller called ChildViewController that wants to communicate back with the parent, then this view controller is known as the delegator. The delegator is the class that wants the delegate to do something by calling the delegate.
We’ve looked at the code required by the delegate, but what about the delegator? Inside of ChildViewController, we would create a property whose type will be UpdateTextDelegate:
How does this child class know who the delegate is? The parent class would need to specify that it itself is the delegate:
Now, once the child has a reference to its parent (specifically, a weak reference, will explain this in another chapter), we can now call that function:
Note: In the example code above, we created the functions pushChildVC and communicateBack. These functions could be anything. What’s important is the code inside of the function.
There was a lot of code and it could be quite difficult to wrap our head around, so let's put everything together.
There are two classes: ParentViewController (delegate) and ChildViewController (delegator). ParentViewController is the delegate so it conforms to the protocol, meaning that it is required to implement the functions and properties defined by that protocol. When ParentViewController creates the ChildViewController, it will need to tell it that it itself is the delegate. We do this by creating a property in ChildViewController containing the reference of the delegate (which is ParentViewController). To communicate from the child to the parent, the child calls the function defined in the protocol using the delegate property that was created earlier. This child could then pass in whatever it wants to the function (since it’s the delegator) and the parent (the delegate) will use whatever the child passed in and do whatever it needs to do.
Let’s take a real life example to understand this better. Say we went to a bar to have a couple of drinks. The bar contains a menu, a bartender, and a customer. The menu is the protocol, the bartender is the delegate, and the customer is the delegator. The bartender must conform to the menu. In other words, the bartender can only make drinks that are on the menu. But how does the bartender know what drinks to make? Well, the customer (delegator), tells the bartender (delegate) what drinks to make. In other words, the customer cannot make the drinks themself but requires the bartender to do it for them.
protocol SomeProtocol: AnyObject {
// Protocol definition here
}protocol UpdateTextDelegate: AnyObject {
func updateText(newText: String)
}// Include `UpdateTextDelegate` in the class header
class ParentViewController: UIViewController, UpdateTextDelegate {
// Class definition here
// Must implement the function `updateText`
func updateText(newText: String) {
// Update some text given `newText`
}
}// Remove `UpdateTextDelegate` from the class header
class ParentViewController: UIViewController {
// Class definition here
}
// Add `UpdateTextDelegate`
extension ParentViewController: UpdateTextDelegate {
func updateText(newText: String) {
// Update some text given `newText`
}
}class ChildViewController: UIViewController {
// Class definition here
// Create the property
// If we make this private, an initializer is required
weak var updateTextDelegate: UpdateTextDelegate?
}// Assume this class conforms to `UpdateTextDelegate`
class ParentViewController: UIViewController {
// Class definition here
// When we create an instance to `ChildViewController`, pass in
// `self` as the delegate
private func pushChildVC() {
let childVC = ChildViewController()
childVC.updateTextDelegate = self
navigationController?.pushViewController(childVC, animated: true)
// If the property `updateTextDelegate` is private, we will need
// an initializer in `ChildViewController` that initializes it
// and use this line instead
let childVC = ChildViewController(updateTextDelegate: self)
}
}class ChildViewController: UIViewController {
// Class definition here
// Create the property
// If we make this private, an initializer is required
weak var updateTextDelegate: UpdateTextDelegate?
// Communicate with parent
private func communicateBack() {
let newString = "Here you go parent"
updateTextDelegate.updateText(newText: newString)
}
}