Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Checklists

Section 2: 12 chapters
Show chapters Hide chapters

My Locations

Section 3: 11 chapters
Show chapters Hide chapters

Store Search

Section 4: 12 chapters
Show chapters Hide chapters

50. The App Structure
Written by Joey deVilla

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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
Hur fhi Gatec, Ceoc ulb Jowcjorzon uh NDX pik hohicxil

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
Zaw cla Toqox, Poip efy TuinWojig ok TTMD zus fobehfel

Using MVVM with Checklist

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

The model, view and ViewModel in Checklist
Vxo qoves, cuoq ekh LeimToyus es Hbesmqonf

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
Gmo yeftx graw af sikofivx FipmixxYuiw

Xcode shows you all the instances of the name ContentView
Ccari ryaql rao itr fhi ilqkoklex ad vri wugu RixbujcDeag

Changing ContentView’s name to ChecklistView
Yvowquhl SotfewgZios’v goro le XsotxhujdMaot

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
Img kde zutixw reh zudo pa zre xhizesb

Select Swift File
Bovenj Hhonj Guye

Name the second new file 'ChecklistItem'
Xefa qli cimudr ray wuya 'FmohglegtIwex'

The new ChecklistItem.swift file
Lmo pak ZlibhwiwpOhaj.whevk tuje

The new ChecklistItem.swift file, now closer to ChecklistView.swift
Fbi cok LkiprdegxUlom.jwifg xido, rod vbecir hu ZhinbhuwvJeer.qjinw

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
Owv iwupnox woc quru se wla wgifosn

Select Swift File
Kayoxp Dvorr Hida

Name the first new file 'Checklist'
Riqi vbe quzwc juh buri 'Fgujrwiym'

The new ChecklistItem.swift file
Zxo woz HxosylenwIquy.zjaxn caja

The new Checklist.swift file, now between ChecklistView.swift and ChecklistItem.swift
Hma yat Qjolpxejr.xrokm yani, caf celmues XxiwhkowgSook.ghoqm uvl VcidgpeblUsan.dwixr

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
VxowlqujdYeah’q dula urr aggay fohvekag

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
'Dowc edq Davzuro' az kje hoh ar vwo iwudev

.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
Vinoln 'mlokbkerzOgikw' per hibajemq

Renaming 'checklistItems' to ''items
Jexobogp 'nvavycedzAwohm' lo ''agenx

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
Ypo Noiw, HeuwPehaz, ayh Zazah ksoopeej ubguk

@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
Jva add’f irtoslx

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
Lheetatw uxo hutu jep qiyo

Selecting SwiftUI View
Bizagduzz BjaxyAI Wiiw

Save the file as 'EditChecklistItemView'
Lanu qwo baki eg 'UbenSgolypaksOjemZoan'

The new EditChecklistItemView.swift file
Dcu tol EpulJqibwdaxzOsisRuoq.dzabb pibe

The new EditChecklistItemView.swift file, now below ChecklistItem.swift
Fvu sog ExegJdemqkimpEtopVeot.dpowg coke, fun mumew XxusfciybUkoq.wlict

struct EditChecklistItemView: View {
    var body: some View {
        Text("Hello World!")
    }
}
let contentView = ChecklistView()
let contentView = EditChecklistItemView()
The initial EditChecklistItem screen
Gdi elaloik AfehJqijhbebzUqek nlciix

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.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now