Your Second iOS & SwiftUI App

Nov 4 2021 Swift 5.5, iOS 15, Xcode 13

Part 1: List View Fundamentals

3. Models & Views

Lesson Complete

Play Next Lesson
Next
Save for later
About this episode
See versions

See forum comments
Cinema mode Mark as Complete Download course materials
Previous episode: 2. List Rows Next episode: 4. SF Symbols

This video was last updated on Nov 4 2021

We’ve got some simple but solid starter UI in place, and we’re ready to put some data in there. So, let’s take a minute to think about the kind of data we want to keep in this app.

ReadMe is meant to be a digital catalog for our personal physical libraries. And the basic building block of a library is The Book!

Here are all the properties we’re planning to keep track of in this app: collectively, those are going to be known as the “data model”, or, more simply, just, the “model” for a book.

A model is a way to structure data so that you can represent a real-world concept —like a book— in your app’s code.

To start, we’ll tackle just three book properties: A Title, Author, and Image. Let’s make a custom Book type to hold them!

We should put our new Book type in its own file, so: command N to make a new file. Select “Swift File”. Name it “Book” and create it!

You won’t need Foundation, so just overwrite that, making a struct named “Book”.

struct Book {

}

Now we can add the properties I talked about earlier. Title and Author? Both constants and strings.

let title: String
let author: String

It’s going to be helpful for our SwiftUI previews to be able to give books default values for those properties, but we don’t necessarily need them hardcoded into the type here.

Instead, we can provide default values via an initializer. Start a new one with init, and add parameters to match each property: title, and author.

  let author: String

  init(title: String, author: String) {
    <#statements#>
  }
}

The defaults can just be the words “Title” and “Author”…

init(title: String = "Title", author: String = "Author") {

…but feel free to use whatever you’d like. These won’t be used in your app. Just in Xcode.

To finish the initialization process off, forward the parameters along to their matching properties.

  init(title: String = "Title", author: String = "Author") {
    self.title = title
    self.author = author
  }

So now we get to the image. It’s not going to be a stored property of book, like title and author are.

Instead, what’s eventually going to happen in this app, is that you’ll be storing images to a separate dictionary in the Library type.

So to plan for that, we can create a new type of View that creates an Image for a book, on the fly!

By the end of the next episode, you’ll have this default book display rendering — where the first letter in a book’s title shows up in a rounded square. This is what will show up for every book before it’s assigned a proper image.

…Or, if your title doesn’t start with a number or letter for some reason, you’ll fall back to the book image from the last episode. In fact, we’ll start the process by extracting that book image!

It’s good organizational practice to keep your Views separate from other Swift files.

So open up the project navigator with Command - 0, right-click on ContentView.swift, and select “New Group from Selection”.

Name it Views. Then, hit command-N to create a new file …a SwiftUI View. It’ll be a collection of reusable views for Books, so name it Book views.

(It’s most common to name files the same as a type, but it’ not necessary. And it wouldn’t make sense, after we get multiple views in here.)

Let’s delete everything below import SwiftUI, because this naming isn’t helping us.

What we’re about to make is a “Book Image”. When you’ve got something like that, where the first part of a type name is an existing type, if often makes sense to extend that type.

So here, we’ll type “extension Book”, and close it off with braces.

import SwiftUI

extension Book {
  
}

Now head over to ContentView, and grab what we need.

And what we need is this Image and all of its modifiers. To make it easier on ourselves, we can extract the whole thing into its own View type.

Command-click on Image, there, and select “Extract Subview”.

Rename it to Image, then cut those lines from the file…

…And paste them in your extension.

extension Book {
  struct Image: View {
    var body: some View {
      Image(systemName: "book")
        .resizable()
        .scaledToFit()
        .frame(width: 80, height: 80)
        .font(Font.title.weight(.light))
        .foregroundColor(.secondary)
    }
  }
}

Now that’s not working, because the compiler thinks you’re trying to make a Book Image, in this line.

In order to disambiguate, you just need to write “SwiftUI dot” in front.

SwiftUI.Image(systemName: "book")

…and you’ll need to do the reverse in ContentView, specifying that you want a Book Image, not a SwiftUI Image.

Book.Image()

And that is a working “Book Image”. Which is helpful for clarifying, given that SwiftUI already has that more general purpose type with the same name. This technique is called “type nesting”.

And now, we can create a preview. “Book underscore Previews” is a good name for what we’ll need.

struct Book_Previews {

}

Adopt PreviewProvider

struct Book_Previews: PreviewProvider {

And start typing previews, to get some useful autocomplete happening, so you can conform to that protocol.

struct Book_Previews: PreviewProvider {
  static var previews: some View {

  }
}

Initialize a Book Image in there, and have a look!

  static var previews: some View {
    Book.Image()
  }

There it is. A reusable Book Image. But we can make a fancier placeholder image than that!