Home iOS & Swift Books iOS Apprentice

50
The App Structure Written by Joey deVilla

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

In the previous chapter, you helped Checklist earn its name by giving it the capacity to store the “checked” status of checklist items and by giving the user the ability to check and uncheck items. This added to the capabilities the app already had: Displaying a list of items and letting the user rearrange the list and delete items. Thanks to SwiftUI, you built all that functionality with surprisingly little code: Fewer than 100 lines!

However, the app’s still missing some very important functionality. It has no “long-term memory” and always launches with the same five hard-coded items in the same order, even if you’ve moved or deleted them. There’s no way for the user to add new items or edit existing ones.

But before you add new functionality, there are some steps that you should take. More functionality means more complexity, and managing complexity is a key part of programming.

Programs are made up of ideas and don’t have the limits of physical objects, which means that they’re always changing, growing and becoming more complex. You need to structure your programs in a way that makes it easier to deal with these changes.

In this chapter, you’ll update Checklist’s structure to ensure that you can add new features to it without drowning in complexity. You’ll learn about the concept of design patterns, and you’ll cover two specific design patterns that you’ll encounter when you write iOS apps.

You’ll also learn about an app’s inner workings, what happens when an app launches, and how the objects that make up an app work together.

Design patterns: MVC and MVVM

All the code that you’ve written for Checklist so far lives in a single file: ContentView.swift. In this chapter, you’ll split the code into three groups, each of which has a different function. This will make your code easier to maintain in the future. Before you start, learn a little bit about why organizing things this way makes a lot of sense.

Different parts of the code do different things. These things generally fall into one of three “departments,” each with a different responsibility:

  • Storing and manipulating the underlying data: The checklist and its individual checklist items handle this. In the code, checklistItems and instances of ChecklistItem, deleteListItem(whichElement:) and moveListItem(whichElement:destination:) work together to handle these jobs.

  • Displaying information to the user: This work takes place within ContentView’s body, which contains NavigationView, List and the views that define the list rows. Each of these includes each item’s name and checkbox.

  • Responding to user input: The method calls attached to the views in ContentView’s body do this work. They ensure that when the user taps on a list item, moves an item or deletes an item, the checklist data changes appropriately.

Many programmers follow the practice of dividing their code into these three departments, then having them communicate with each other as needed.

The “three departments” approach is one of many recurring themes in programming. There’s a geeky term for these themes: Software design patterns, which programmers often shorten to design patterns or just patterns. They’re a way of naming and describing best practices for arranging code objects to solve problems that come up frequently in programming. Design patterns give developers a vocabulary that they can use to talk about their programs with other developers.

Note: There’s a whole branch of computer literature devoted to design patterns, with the original book being Design Patterns: Elements of Reusable Object-Oriented Software, first published in 1994. Its four authors are often referred to as the “Gang of Four,” or “GoF” for short.

While it’s good to get knowledge straight from the source, the Gang of Four’s book is an incredibly dry read; I’ve used it as a sleep aid. There are many books on the topic that are much easier to read, including our own Design Patterns by Tutorials, which was written specifically with iOS development in Swift in mind.

The Model-View-Controller (MVC) pattern

The formal name for the “three departments” pattern is Model-View-Controller, or MVC for short. Each name represents a category of object:

How the Model, View and Controller in MVC fit together
Pix dye Netev, Yaov afm Hankxobdub oy HBD xib letollov

Model-View-ViewModel (MVVM)

Over the years since its introduction, programmers have come up with modified versions of the Model-View-Controller pattern that better fit their needs. One of these is Model-View-ViewModel, which is often shortened to MVVM.

How the Model, View and ViewModel in MVVM fit together
Zev wze Cuqan, Yauv anx XoojNoyaq od RMNR siq solebyut

Using MVVM with Checklist

Here’s how you’ll split up Checklist’s code:

The model, view and ViewModel in Checklist
Kze robek, boor agl SoonJiyom az Cmojgfogf

Renaming the view

Both Bullseye and Checklist are based on Xcode’s Single View App project template. As the template’s name implies, it generates a bare-bones app with a single pre-defined screen with an all-purpose name: ContentView.

The first step in renaming ContentView
Zxe kawbx cgur ic goyerazm QustunsSeaj

Xcode shows you all the instances of the name ContentView
Lreco nserx goi uqm gwo ehrjiwcak ev tzu yafe VamjanjLaoy

Changing ContentView’s name to ChecklistView
Wneyzabf LolwibsWuid’t zoqa fu XsiktduxsYaer

Adding a file for the model

Creating a file for the model

Now that you’ve given the app’s main view a better name, you’ll need to create files for the other objects in the MVVM pattern. You’ll start by creating a file for the model’s code.

Add the second new file to the project
Uxy xpa jasasc sey memo na sxo wgezorf

Select Swift File
Venisg Dmupc Wuro

Name the second new file 'ChecklistItem'
Lafi fbe nahugc guc jufe 'FwivlroqkOxeb'

The new ChecklistItem.swift file
Kme lal DsukcfuwhUxig.zveqf piji

The new ChecklistItem.swift file, now closer to ChecklistView.swift
Dbe tur FxoxswiwqEyep.bcajg neco, xer ynejax ja PdiqlraprKuoq.hluft

Moving the model code to the file

Now that you have a new file for the model, it’s time to move its code there. Luckily, this is a simple process.

struct ChecklistItem: Identifiable {
  let id = UUID()
  var name: String
  var isChecked: Bool = false
}
import Foundation

struct ChecklistItem: Identifiable {
  let id = UUID()
  var name: String
  var isChecked: Bool = false
}

Adding a file for the ViewModel

Your next step is to create a file for the ViewModel.

Add another new file to the project
Ogc obuhgof cok fuce ke rqa ktavakn

Select Swift File
Metogt Nbizk Keso

Name the first new file 'Checklist'
Yale wfu fikyk nit giho 'Xpasfbovn'

The new ChecklistItem.swift file
Ptu xey TrovyyugzUyaz.jxutp xale

The new Checklist.swift file, now between ChecklistView.swift and ChecklistItem.swift
Bno xoq Fnamgrarl.xwokn vitu, vih zicmeeb HcubmkohkXoaz.yjojt ahj KjeywkavvUlam.zmocc

Moving the ViewModel code to the file

Add the following to Checklist.swift, just after the import Foundation line:

class Checklist: ObservableObject {

}
@State var checklistItems = [
  ChecklistItem(name: "Walk the dog", isChecked: false),
  ChecklistItem(name: "Brush my teeth", isChecked: false),
  ChecklistItem(name: "Learn iOS development", isChecked: true),
  ChecklistItem(name: "Soccer practice", isChecked: false),
  ChecklistItem(name: "Eat ice cream", isChecked: true),
]
class Checklist: ObservableObject {

  @Published var checklistItems = [
    ChecklistItem(name: "Walk the dog", isChecked: false),
    ChecklistItem(name: "Brush my teeth", isChecked: false),
    ChecklistItem(name: "Learn iOS development", isChecked: true),
    ChecklistItem(name: "Soccer practice", isChecked: false),
    ChecklistItem(name: "Eat ice cream", isChecked: true),
  ]

}
func deleteListItem(whichElement: IndexSet) {
  checklistItems.remove(atOffsets: whichElement)
  printChecklistContents()
}

func moveListItem(whichElement: IndexSet, destination: Int) {
  checklistItems.move(fromOffsets: whichElement, toOffset: destination)
  printChecklistContents()
}
class Checklist: ObservableObject {

  @Published var checklistItems = [
    ChecklistItem(name: "Walk the dog", isChecked: false),
    ChecklistItem(name: "Brush my teeth", isChecked: false),
    ChecklistItem(name: "Learn iOS development", isChecked: true),
    ChecklistItem(name: "Soccer practice", isChecked: false),
    ChecklistItem(name: "Eat ice cream", isChecked: true),
  ]

  func deleteListItem(whichElement: IndexSet) {
    checklistItems.remove(atOffsets: whichElement)
  }

  func moveListItem(whichElement: IndexSet, destination: Int) {
    checklistItems.move(fromOffsets: whichElement, toOffset: destination)
  }

}
ChecklistView’s code and error messages
HcujgveqkXeaz’p muli oxv ulvac sussoqoy

Connecting the view to the ViewModel

In the Model-View-ViewModel pattern, the model is connected to the ViewModel, and the ViewModel is connected to the view.

class Checklist: ObservableObject {
@Published var checklistItems = [
struct ChecklistView: View {

  // Properties
  // ==========

  // User interface content and layout
  var body: some View {
    NavigationView {
      List {
        ForEach(checklistItems) { checklistItem in
          HStack {
            Text(checklistItem.name)
            Spacer()
            Text(checklistItem.isChecked ? "✅" : "🔲")
          }
          .background(Color(UIColor.systemBackground)) // This makes the entire row clickable
          .onTapGesture {
            if let matchingIndex = self.checklistItems.firstIndex(where: { $0.id == checklistItem.id }) {
              self.checklistItems[matchingIndex].isChecked.toggle()
            }
          }
        }
        .onDelete(perform: deleteListItem)
        .onMove(perform: moveListItem)
      }
      .navigationBarItems(trailing: EditButton())
      .navigationBarTitle("Checklist")
    }
  }


  // Methods
  // =======

}
@ObservedObject var checklist = Checklist()
'Find and Replace' at the top of the editor
'Cihr olj Qiclubu' ap xbo kan of xwo oxeqic

.onDelete(perform: deleteListItem)
.onDelete(perform: checklist.deleteListItem)
.onMove(perform: moveListItem)
.onMove(perform: checklist.moveListItem)
struct ChecklistView: View {

  // Properties
  // ==========

  @ObservedObject var checklist = Checklist()

  // User interface content and layout
  var body: some View {
    NavigationView {
      List {
        ForEach(checklist.checklistItems) { checklistItem in
          HStack {
            Text(checklistItem.name)
            Spacer()
            Text(checklistItem.isChecked ? "✅" : "🔲")
          }
          .background(Color(UIColor.systemBackground)) // This makes the entire row clickable
          .onTapGesture {
            if let matchingIndex = self.checklist.checklistItems.firstIndex(where: { $0.id == checklistItem.id }) {
              self.checklist.checklistItems[matchingIndex].isChecked.toggle()
            }
          }
        }
        .onDelete(perform: checklist.deleteListItem)
        .onMove(perform: checklist.moveListItem)
      }
      .navigationBarItems(trailing: EditButton())
      .navigationBarTitle("Checklist")
    }
  }


  // Methods
  // =======

}

Refactoring once more

You still have one more change to the code to make…

Select 'checklistItems' for renaming
Zatatw 'bkogjhuwqAdohr' guh taxixurw

Renaming 'checklistItems' to ''items
Zibufohp 'wrogttuzvOcozv' xi ''iwenj

What happens when you launch an app?

In its new Model-View-ViewModel configuration, here’s how each of the objects that make up the app is created:

The View, ViewModel, and Model creation order
Ffa Siol, KaehPujeb, ezh Canis vxiejaal akrem

@ObservedObject var checklist = Checklist()
@Published var items = [
  ChecklistItem(name: "Walk the dog", isChecked: false),
  ChecklistItem(name: "Brush my teeth", isChecked: false),
  ChecklistItem(name: "Learn iOS development", isChecked: true),
  ChecklistItem(name: "Soccer practice", isChecked: false),
  ChecklistItem(name: "Eat ice cream", isChecked: true),
]

The app delegate and scene delegate

Xcode projects include a number of source files that contain code to support the app you’re writing. This code handles all the behind-the-scenes details necessary to make the app work, freeing you to focus on the code that’s specific to your app.

class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  // Override point for customization after application launch.
  return true
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
  // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
  // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

  // Create the SwiftUI view that provides the window contents.
  let contentView = ChecklistView()

  // Use a UIHostingController as window root view controller.
  if let windowScene = scene as? UIWindowScene {
      let window = UIWindow(windowScene: windowScene)
      window.rootViewController = UIHostingController(rootView: contentView)
      self.window = window
      window.makeKeyAndVisible()
  }
}
// Create the SwiftUI view that provides the window contents.
let contentView = ChecklistView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(rootView: contentView)
    self.window = window
    window.makeKeyAndVisible()
}
window.rootViewController = UIHostingController(rootView: contentView)
The app’s objects
Rlu ilz’h ayqibvj

Changing the app’s first screen

Your next step is to change the app so that it starts with a screen other than ContentView. The app will need a couple of additional screens anyway, so you’ll add a screen that you’ll eventually use to edit items in the list.

Creating one more new file
Mfeenozx ijo facu xik cone

Selecting SwiftUI View
Xocipgemp LtuypAA Noek

Save the file as 'EditChecklistItemView'
Xegu zra jazu oz 'IvanYloshpipjEhevJuip'

The new EditChecklistItemView.swift file
Vji dog IcuxGxexfvaktAjolSiow.zpinz yobi

The new EditChecklistItemView.swift file, now below ChecklistItem.swift
Pfu mor EnoyFzuglgogtIgujJaos.dbakc zasi, xix sewig HvilhjutnOpay.pgehn

struct EditChecklistItemView: View {
    var body: some View {
        Text("Hello World!")
    }
}
let contentView = ChecklistView()
let contentView = EditChecklistItemView()
The initial EditChecklistItem screen
Dwa ikiceip IromXyinpnugnIgis jlbeoj

let contentView = EditChecklistItemView()
let contentView = ChecklistView()

Key points

In this chapter, you did the following:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.