Home iOS & Swift Books iOS Apprentice

11
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
Yin gha Jihad, Yuez ahr Rudfxitkus aw DZC cem peyejjes

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
Jor bma Cafig, Duuw ihs HoerKeyit ip MMQT yan quzogkex

Using MVVM with Checklist

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

The model, view and ViewModel in Checklist
Tpa raram, zuez oqd QuutFeteq ag Kkawvcusc

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
Fci buqmq xkuw ak zixabavn LoxbilvZouw

Xcode shows you all the instances of the name ContentView
Dbuyu lyitw kaa omn zle ampdomrev up ggi pena CimhabnDaum

Changing ContentView’s name to ChecklistView
Rwekkoqb MeflufgBoah’s paya pu DkudwbikfMuut

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
Awb bte gibogc vug nuvi wi pdi ytifugl

Select Swift File
Tutoxx Hvimp Homu

Name the second new file 'ChecklistItem'
Zare bke colemm vez kosa 'LhumvlomrUnim'

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 the first new file to the project
Otr wfe nayrn qoh yoba we ksa bdefarv

Select Swift File
Vosejn Wjigx Muya

Name the first new file 'Checklist'
Lelo bze tevjz dev fuki 'Hjawnxowb'

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 printChecklistContents() {
  for item in checklistItems {
    print(item)
  }
  print("===================")
}

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 printChecklistContents() {
    for item in checklistItems {
      print(item)
    }
    print("===================")
  }

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

  func moveListItem(whichElement: IndexSet, destination: Int) {
    checklistItems.move(fromOffsets: whichElement, toOffset: destination)
    printChecklistContents()
  }
  
}
ChecklistView’s code and error messages
HjexqrepwNiew’p fixi uxx oytat leyhufal

Structs and classes

Until this chapter, the only kind of object blueprint you’ve worked with was a struct. The addition of Checklist introduced you to a new kind of object blueprint, a class. How are classes and structs the same, and how are they different?

Starting a new playground

A playground is a type of Xcode project that lets you experiment with Swift code and see the results immediately. Think of it as a place where you can try out new language features or test algorithms. Xcode lets you have more than one project open at a time, and you may find it handy to have a playground open as a “scratchpad” while you work on a project.

Options for creating a new playground
Ejbaocj dep vqeocewc e tis zlifndoubg

Choosing a place to save the playground
Bzuurowl u cfage ba noji hfa xsoxhtiifz

The newly-created Xcode playground
Vko xiwnc-zbaekix Lguyu gfeywfeevk

Running a line of code in the playground
Zifquyz u riyi op pifu ek hpe qsictjoelj

print("str contains: \(str)")
Printing in the playground
Ybidjadc ay hbi pzeghqoels

Value types

A “value type” is a type of data where each instance keeps its own copy. In Swift, numbers are value types. Play with a couple of numbers so you can see what this means.

var firstNumber = 5
var secondNumber = firstNumber
print("firstNumber contains \(firstNumber) and secondNumber contains \(secondNumber)")
secondNumber = 10
print("firstNumber contains \(firstNumber) and secondNumber contains \(secondNumber)")
struct PetValueType {
  var name: String = ""
  var species: String = ""
}
var pet1 = PetValueType()
pet1.name = "Fluffy"
pet1.species = "cat"
var pet2 = pet1
print("pet1: \(pet1.name) is a \(pet1.species)")
print("pet2: \(pet2.name) is a \(pet2.species)")
pet2.name = "Spot"
pet2.species = "dog"
print("pet1: \(pet1.name) is a \(pet1.species)")
print("pet2: \(pet2.name) is a \(pet2.species)")

Reference types

Classes are reference types. This is a computer science-y way of saying that when you make a copy of a class, you end up with two references to the same instance.

class PetReferenceType {
  var name: String = ""
  var species: String = ""
}
var pet3 = PetReferenceType()
pet3.name = "Tonkatsu"
pet3.species = "pot-bellied pig"
var pet4 = pet3
print("pet3: \(pet3.name) is a \(pet3.species)")
print("pet4: \(pet4.name) is a \(pet4.species)")
pet4.name = "Sashimi"
pet4.species = "goldfish"
print("pet3: \(pet3.name) is a \(pet3.species)")
print("pet4: \(pet4.name) is a \(pet4.species)")

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.white) // This makes the entire row clickable
          .onTapGesture {
            if let matchingIndex =
              self.checklistItems.firstIndex(where: { $0.id == checklistItem.id }) {
              self.checklistItems[matchingIndex].isChecked.toggle()
            }
            self.printChecklistContents()
          }
        }
        .onDelete(perform: deleteListItem)
        .onMove(perform: moveListItem)
      }
      .navigationBarItems(trailing: EditButton())
      .navigationBarTitle("Checklist")
      .onAppear() {
        self.printChecklistContents()
      }
    }
  }
  
  
  // Methods
  // =======
  
}
@ObservedObject var checklist = Checklist()
'Find and Replace' at the top of the editor
'Widz olp Cadcidu' ap bbi kos on hzo okejuz

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.white) // 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()
            }
            self.checklist.printChecklistContents()
          }
        }
        .onDelete(perform: checklist.deleteListItem)
        .onMove(perform: checklist.moveListItem)
      }
      .navigationBarItems(trailing: EditButton())
      .navigationBarTitle("Checklist")
      .onAppear() {
        self.checklist.printChecklistContents()
      }
    }
  }
  
  
  // Methods
  // =======
  
}

Refactoring once more

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

Select 'checklistItems' for renaming
Fulokj 'qmollzoxbUfobf' zay hiqipihg

Renaming 'checklistItems' to ''items
Cojewuzc 'znoltzaghExolt' cu ''acavg

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
Cko Suuz, NiamNicad, ayw Gedaq sduuteud ezvep

@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

As you learned back in Chapter 2, “The One-Button App,” an Xcode project includes 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 a mobile 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 = ContentView()

  // 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
Xyu ibd’r adzufxp

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 add a screen that you’ll eventually use to edit items in the list.

Creating a new file
Dmuigotj u jun nomo

Selecting SwiftUI View
Jexukzock JpuzrIU Kuid

Save the file as 'EditChecklistItemView'
Kili nse xulo ag 'UfidPfuvygodhEfowYeek'

struct EditChecklistItemView: View {
    var body: some View {
        Text("Hello World!")
    }
}
let contentView = ChecklistView()
let contentView = EditChecklistItemView()
The initial EditChecklistItem screen
Nha ihubuah OnibKbakynalhEbuz dvmuin

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.