The Model-View-Controller (MVC) pattern divides objects into three distinct types. Yes, you guessed it: the three types are: model, View, and controller!

It is fairly simple to illustrate the relationship between these types using the following diagram.

  • (Models) The model stores application data. They are usually structured or simple classes.
  • A View displays visual elements and controls on the screen. They’re usually subclasses of UIView.
  • Controllers coordinate between models and views. They’re usually subclasses of UIViewController.

MVC is very common in iOS programming because it’s the design pattern Apple chose to adopt in UIKit.

Allows controllers to provide powerful properties for their models and views so that they are directly accessible. Controllers can have more than one model and/or view.

In contrast, models and views should not hold strong references to their owning controllers. This leads to a retention cycle.

Instead, the model communicates with the controller through property observations (which you’ll learn more about in a later section), and views communicate with the controller through IBActions.

This lets you reuse models and views across multiple controllers. Win!

Note: A view can have a weak reference to its own controller through a delegate (see Chapter 4, “Delegate pattern”). For example, a UITableView can hold weak references to its own view controller for its delegate and/or dataSource references. However, the table view doesn’t know that these are set to its own controller — they just happen to be.

Controllers are harder to reuse because their logic is often very specific to whatever task they do. Therefore, MVC does not try to reuse them.

When should I use it?

Use this pattern as a starting point for creating iOS apps.

In almost every application, you may need more patterns than MVC, but it’s ok to introduce more patterns depending on your application’s needs.

The playground instance

Open the Starter directory FundamentalDesignPatterns xcworkspace. This is a collection of playground pages, one for each basic design pattern. By the end of this section, you’ll have a good design pattern reference!

Open the Overview page from the Files hierarchy.

This page lists three types of design patterns.

  • Structural patterns describe how objects are grouped into larger subsystems.
  • Behavior patterns describe how objects communicate with each other.
  • Create mode instantiates or “creates” objects for you.

MVC is a structural pattern because it consists of objects as a model, view, or controller.

Next, open the Model-View-Controller page from the File hierarchy. In the code example, you will create an “address screen” using MVC.

Can you guess what the three parts of the address screen will be? Models, views, and controllers, of course! Add this Code after Code Example to create the model.

import UIKit
// MARK: - Address
public struct Address {
  public var street: String
  public var city: String
  public var state: String
  public var zipCode: String
}
Copy the code

This creates a simple structure that represents an address.

And then we need to import UIKit to create AddressView as a subclass of UIView.

Just add this code.

// MARK: - AddressView
public final class AddressView: UIView {
  @IBOutlet public var streetTextField: UITextField!
  @IBOutlet public var cityTextField: UITextField!
  @IBOutlet public var stateTextField: UITextField!
  @IBOutlet public var zipCodeTextField: UITextField!
}
Copy the code

In a real iOS app, instead of a playground, you would also create a XIB or storyboard for this view and connect the IBOUTLET property to its subviews. You will practice doing this later in the tutorial project in this chapter.

And finally, you need to create the AddressViewController. Next, add this code.

// MARK: - AddressViewController
public final class AddressViewController: UIViewController {
// MARK: - Properties
  public var address: Address?
  public var addressView: AddressView! {
    guard isViewLoaded else { return nil }
    return (view as! AddressView)
  }
}
Copy the code

Here, you have the controller holding strong references to its own views and models.

AddressView is a calculated property because it only has a getter. It first checks isViewLoaded to prevent the view from being created before the view controller renders on the screen. If isViewLoaded is true, it projects the view onto an AddressView. To keep the warning silent, you surround the projection with parentheses.

In a real iOS app, you would also need to specify the class of the view on the storyboard or xiB to ensure that the app correctly creates an AddressView instead of the default UIView.

To recall, the controller’s responsibility is to coordinate the relationship between the model and the view. In this case, the controller should update its address View with the value from the address.

A good place to do that is whenever YOU call viewDidLoad. Add the following to the end of the AddressViewController class.

// MARK: - View Lifecycle public override func viewDidLoad() { super.viewDidLoad() updateViewFromAddress() } private func updateViewFromAddress() { guard let addressView = addressView, let address = address else { return } addressView.streetTextField.text = address.street addressView.cityTextField.text =  address.city addressView.stateTextField.text = address.state addressView.zipCodeTextField.text = address.zipCode }Copy the code

If the address is set after calling viewDidLoad, the controller should update the address View as well.

Replace the address property with the following.

public var address: Address? {
  didSet {
    updateViewFromAddress()
  }
}
Copy the code

This is an example of how the model tells the controller that something has changed and that the view needs to be updated.

What if you also want the user to update the address from the view? That’s right — you need to create an IBAction on the controller.

Add this after updateViewFromAddress().

// MARK: - Actions
@IBAction public func updateAddressFromView( _ sender: AnyObject) {
    guard let street = addressView.streetTextField.text, street.count > 0,
    let city = addressView.cityTextField.text, city.count > 0,
    let state = addressView.stateTextField.text, state.count > 0,
    let zipCode = addressView.zipCodeTextField.text, zipCode.count > 0 else {
    // TO-DO: show an error message, handle the error, etc
    return
  }
  address = Address(street: street, city: city,
}
Copy the code

Finally, this is an example of how the view can tell the controller that something has changed and that the model needs to be updated. In a real iOS app, you would also need to connect the IBAction from AddressView’s child view, such as valueChanged event on UITextField or touchUpInside event on UIButton.

All in all, this gives you a simple example of how the MVC pattern works. You’ve seen how the controller owns models and views, and how each model and view interacts with each other, but always through the controller.

What should you look out for?

MVC is a good starting point, but it has limitations. Not every object fits neatly into the category of model, view, or controller. Therefore, MVC-only applications tend to add a lot of logic to their controllers. This can cause the view controller to become very large! When this happens, there’s a rather odd term called “large-scale view controller.” (Ven: Problems with MVC use)

To solve this problem, you should introduce other design patterns based on your application’s needs.

The tutorial project

In this section, you will create a tutorial application called Rabble Wabble.

A language learning app similar to Duolingo (bit.ly/ ios-Duoling… And really (bit. Ly/ios – really).

You will be creating the project from scratch, so open Xcode and select File ▸ New ▸. The project. Then select iOS ▸ single View application and press Next.

Enter RabbleWabble as the product name; Select your team, leave it as “none” if you don’t have a team set (not necessary if you only use the emulator); Set your organization name and organization identifier to whatever you like; Confirm that the language is set to Swift; Uncheck use core data, include unit tests, and include UI tests; Then click “Next” to continue.

Select a convenient location to save the project, then press Create. You need to do some organizational work to demonstrate the MVC pattern.

Open ViewController.swift from File Hierarchy and remove all template code inside curly braces. Then right click on the ViewController and select Refactor ▸ to rename…. .

Enter QuestionViewController as the new name and press Enter to modify it. Then, in front of the class QuestionViewController, add the keyword public, like this.

  public class QuestionViewController: UIViewController
Copy the code

In this book, you use public for types, properties, and methods that should be publicly accessible by other classes; If something should only be accessed by the type itself, you would use private; If it should be accessed by subclasses or related classes, but is not intended for general use, you use internal. This is called access control.

This is a “best practice” in iOS development. If you’ve ever moved these files into a separate module, such as creating a shared library or framework, you’ll find it easier to do if you follow this best practice.

Next, select the yellow RabbleWabble group in the “Files” hierarchy and press Command + Option + N together to create a new group.

Select a new group and press Enter to edit its name. Type AppDelegate and press Enter to confirm.

Repeat this process to create new groups for controllers, models, resources, and views.

Will AppDelegate. Swift move AppDelegate group, will QuestionViewController. Swift move controller, will be Assets. Xcassets and Info., plist into resources, Move launchscreen. storyboard and main. storyboard into the view.

Finally, right-click the yellow RabbleWabble group and select Sort by name.

Your file hierarchy should end up like this.

Since you moved info.plist, you need to tell Xcode where its new location is. To do this, select the RabbleWabble project folder in blue; Select the RabbleWabble target, select the General TAB, and then click Choose Info.plist File…. .

In the new window that appears, click info.plist from the list of files, then press Choose to set. Build and run to verify that you don’t see any errors in Xcode.

This is a good start to using the MVC pattern! By simply grouping your files, you can see any errors in Xcode. By simply grouping files in this way, you can tell other developers that your project uses MVC. Clarity is good

Create the model

Next you will create the Rabble Wabble model.

First, you need to create a problem model. Select the “Model” group in the “File” hierarchy and press Command + N to create a new file. Select the Swift file from the list, and click Next. Name the file Question. Swift, and click Create.

Replace the entire content of Question. Swift with the following.

 public struct Question {
    public let answer: String
    public let hint: String?
    public let prompt: String
}
Copy the code

You also need another model to act as a container for the problem group.

Create another file named questiongroup.swift in the model group and replace its entire contents with the following.

public struct QuestionGroup {
  public let questions: [Question]
  public let title: String
}
Copy the code

Next, you need to add the data for the problem group. This may require retyping, so I’ve provided a file that you can simply drag and drop into the project.

Open Finder and navigate to where you downloaded the project in this chapter. Next to the Starter and Final directories, you’ll see a Resources directory containing QuestionGroupData.swift, Assets.xcassets, and launchScreen.storyboard.

Position the Finder window above Xcode, and then drag and drop questionGroupData.swift into the Models group like this.

When prompted, if necessary, select the option to copy the project and then press Finish to add the file.

Now that you have the Resources directory open, you should copy the other files as well. First, select the existing assets.xcassets under the resource in your application and press the Delete key to Delete them. Select “Move to recycle bin” when prompted. Then drag and drop the new Assets.xcassets from the Finder into the application’s resource group, checking the copy item when prompted if necessary.

Next, select the existing launchscreen. storyboard in your app and press Delete under Views. Also, make sure to select “Move to trash can” when prompted. Then drag and drop the new launchscreen.storyboard from the Finder into the application’s resource group, checking the copy project when prompted if necessary.

Open questionGroupData.swift and you’ll find several static methods defined for basic phrases, numbers, etc. The data set is in Japanese, but you can adjust it to another language if you like. You will soon be able to use these methods

Open launchscreen.storyboard and you’ll see a nice layout that shows up every time the application launches.

Build and run, view the cute app icon and launch screen!

Create a view

Now you need to set the “view” part of your MVC. Select the “View” group and create a new file called “questionView.swift”.

Replace its contents with the following.

import UIKit
public class QuestionView: UIView {
  @IBOutlet public var answerLabel: UILabel!
  @IBOutlet public var correctCountLabel: UILabel!
  @IBOutlet public var incorrectCountLabel: UILabel!
  @IBOutlet public var promptLabel: UILabel!
  @IBOutlet public var hintLabel: UILabel!
}
Copy the code

Next, open main.storyboard and scroll to the existing scene. Press the object library button and enter the label in the search bar. Hold down the Option key to prevent the window from closing, then drag and drop the three tabs onto the scene without overlapping.

Press the red X on the object library window and close the window.

Double-click the top TAB and set its text to “Tips”. Set the middle label to “Hint” and the bottom label to “answer”.

Select the Tips TAB, open the Utilities pane, and select the Properties Inspector TAB. Set the font of the label to System 50.0, alignment to center, and number of lines to 0.

Set the font of the “Tips” TAB to System 24.0, align to center, and line number to 0; Set the font for the “answer” TAB to System 48.0, alignment to center, and number of lines to 0.

If necessary, resize the labels to prevent clipping, and rearrange them so that they remain in the same order without overlapping.

Next, select the “Hint” TAB, select the “Add New Constraint” icon, and do the following.

  • Set the top constraint to 60
  • Set the leading constraint to 0
  • Set the tail constraint to 0
  • Check for margin limits
  • Press the “Add 3 constraints” key

Select the Prompt TAB, select the Add New Constraint icon, and then do the following.

  • Set the top constraint to 8
  • Set the leading constraint to 0
  • Set the tail constraint to 0
  • Check for margin limits
  • Press the “Add 3 constraints” key

Select the “Answer” TAB, select the “Add New Constraint” icon, and then do the following.

  • Set the top constraint to 50
  • Set the leading constraint to 0.
  • Set the tail constraint to 0.
  • Check constraint is margin.
  • Press add 3 constraints.

This is what the scene should look like.

Next, press the object library button, type UIButton in the search bar, and drag a new button to the lower left corner of the view.

Open the Properties Inspector, set the image of the button to IC_circle_X, and remove the default title for the button.

Drag another button into the lower right corner of the view. Set its image to IC_circle_check and remove the Button’s default title.

Drag a new label into the scene. Open the Property Inspector and set color to match the red circle. Set the font to System 32.0 and the Alignment to center. Resize this label as needed to prevent clipping.

Drag another label into the scene, place it below the green check button, and set its text to 0. Open the Properties Inspector and set the color to match the green circle. Set the font to System 32.0 and the alignment to center. Adjust the size of this label as needed to prevent clipping.

Next you need to set constraints on buttons and labels.

Select the red circle button, select the “Add New Constraint” icon, and do the following.

  • Set the leading constraint to 32
  • Set the bottom constraint to 8.
  • Check constraints to margins.
  • Press “Add 2 constraints”.

Select the red label, select the icon to add the new constraint, and then do the following.

  • Set the bottom constraint to 24
  • Check constraint is margin.
  • Press Add 1 constraint.

Select both the red circle image view and the red color label, select the aligned icon, and do the following.

  • Check the “horizontal center” box.
  • Press Add 1 constraint.

Select the green circle image view, select the icon to add the new constraint, and then do the following.

  • Set the tail constraint to 32.
  • Set the bottom constraint to 8.
  • Check constraint is margin.
  • Press the “Add 2 constraints” key

Select the green label, select the icon to add the new constraint, and then do the following.

  • Set the bottom constraint to 24
  • Check constraint is margin.
  • Press add 1 constraint.

Select both the green circle image view and the green color label, select the aligned icon, and do the following.

  • Check the “horizontal center” box
  • Press “Add 1 constraint”.

This is what the scene should look like.

To complete the QuestionView setup, you need to set the class of the view on the scene and connect the properties.

Click the view on the scene, taking care not to select any subviews instead, and open the identity checker. Set the class to QuestionView.

Open the Connection Inspector and drag from each entry to the appropriate subview, as shown.

Run and see the view. Fierce!

Creating a Controller

You are finally ready to create the “controller” part of an MVC.

Open the QuestionViewController. Swift and add the following attribute.

// MARK: - Instance Properties
public var questionGroup = QuestionGroup.basicPhrases() public var questionIndex = 0
public var correctCount = 0
public var incorrectCount = 0
public var questionView: QuestionView! {
  guard isViewLoaded else { return nil }
  return (view as! QuestionView)
}
Copy the code

You temporarily hard-code questionGroup into basic phrases. In a future chapter, you will extend the application to enable users to select question groups from a list.

QuestionIndex is an index of the questions currently displayed. As the user browses the question, you increment the index.

CorrectCount is the number of correct responses. The user presses the green check button to indicate a correct answer.

Similarly, incorrectCount is the number of incorrect responses that the user represents by pressing the red ‘X’ button.

The problem View is a calculated property. Here you check isViewLoaded, so you don’t accidentally load your view by accessing this property. If the view is already loaded, you force it to be converted to QuestionView.

Next you need to add code to actually display a problem. Add the following after the properties you just added.

// MARK: - View Lifecycle public override func viewDidLoad() { super.viewDidLoad() showQuestion() } private func showQuestion() {  let question = questionGroup.questions[questionIndex] questionView.answerLabel.text = question.answer questionView.promptLabel.text = question.prompt questionView.hintLabel.text = question.hint questionView.answerLabel.isHidden = true questionView.hintLabel.isHidden = true }Copy the code

Notice here how you write code in the controller to manipulate the view based on the data in the model. MVC FTW!

Build and run, and see how the problem looks on screen!

Now, there’s no way to find out. You should probably fix the problem. Add the following code to the end of the view controller.

// MARK: - Actions @IBAction func toggleAnswerLabels(_ sender: Any) { questionView.answerLabel.isHidden = ! questionView.answerLabel.isHidden questionView.hintLabel.isHidden = ! questionView.hintLabel.isHidden }Copy the code

This will toggle whether the prompt and answer tabs are hidden. Set the answer and prompt tabs hidden in showQuestion() to reset the status each time a new question is displayed.

This is an example of a view notifying its controller about an action that has taken place. In response, the controller executes code to handle the action.

You also need to hang the action on the view. Open main.storyboard and press the object library button.

Enter tap in the search field, and then drag and drop a TAP gesture recognizer onto the view.

Make sure you drag it onto the base view, not onto a label or button! Control – Drag and drop from the “tap Gesture recognizer” object to the “problem view”.

Controller object in the scenario, then select toggleAnswerLabels:.

Build and run, and try clicking on the view to show/hide the answer and prompt labels. Next, you need to deal with what happens every time the button is pressed.

Open the QuestionViewController. Swift, and at the end of the class to add the following content.

// 1 @IBAction func handleCorrect(_ sender: Any) { correctCount += 1 questionView.correctCountLabel.text = "\(correctCount)" showNextQuestion() } // 2 @IBAction func handleIncorrect(_ sender: Any) { incorrectCount += 1 questionView.incorrectCountLabel.text = "\(incorrectCount)" showNextQuestion() } // 3 private  func showNextQuestion() { questionIndex += 1 guard questionIndex < questionGroup.questions.count else { // TODO: - Handle this... ! return } showQuestion() }Copy the code

You just defined three more actions. Here’s what each action does.

1. HandleCorrect (_:) will be called when the user presses the green circle button to indicate that they have the correct answer. Here, you add correctCount and set the correctCountLabel text.

2. HandleIncorrect (_:) is called whenever a user presses the red circle button to indicate that they got an incorrect answer. Add incorrectCount and set the incorrectCountLabel text.

3. Call showNextQuestion() to progress to the next question. If you less than questionGroup according to questionIndex. Question. The count, to prevent if there are other problems, if any, displays the next question.

You will deal with the no-problem case in the next chapter. Finally, you need to connect the buttons on the view to these actions.

Open Main.storyboard and select the red circle button. Then Control-Drag to the QuestionViewController object and select handleIncorrect:.

Again, select the green circle button, then Control-Drag to the QuestionViewController object, and select handleCorrect:.

Again, these are all examples of what the view notification controller needs to do. Build and run and try to press every button.

The key point

In this chapter, you learned about the Model-View-Controller (MVC) pattern. Here are its key points.

  • MVC divides objects into three categories: model, view, and controller.
  • MVC advocates reuse of models and views between controllers. Because the logic of a controller is usually very specific, MVC usually doesn’t reuse controllers.
  • The controller is responsible for coordinating the model and view: it sets the values of the model to the view and handles IBAction calls from the view.
  • MVC is a good starting point, but it has limitations. Not every object fits neatly into the category of model, view, or controller. You should use MVC along with other patterns as needed.

You’ve got Rabble Wabble off to a great start! However, you need to add a lot of functionality: let the user select problem groups, what happens when remaining problems are handled, and so on. However, you still have a lot of functionality to add: let users select problem groups, handle situations when there are no remaining problems, and much more

Move on to the next chapter, where you learn about licensing design patterns and build Rabble Wabble.