Models and Views | raywenderlich.com


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/15234721-your-second-ios-and-swiftui-app/lessons/3

This specific tutorial video seemed to confuse me more than help me. The narrator wasn’t quite clear on why we are making extensions of the Book struct in another file rather than with the existing book file. Also trying to explain why we are putting an optional in front of a SwiftUI Image extension with “we wont always have a valid image for a title to represent that, add a question mark after the keyword init.” I have no idea what the narrator is trying to say here. Then not explaining the .square part for the initialization of symbolName, along with not explaining line 57 right after that (self.init
).

Hi,

I tried to explain this from about 2:58 to 3:33. Please let me know what confused you about that, and I can elaborate.

It’s a failable initializer.
https://www.raywenderlich.com/1220-swift-tutorial-initialization-in-depth-part-1-2#toc-anchor-009

SFSymbols are covered in this video.

Thanks for taking the time to reply. The SFSymbols are my fault as I forgot that we learned that earlier. Will be reading more about failable initializers thanks for the resource! I’m still confused on why we call self.init(systemName: symbolName) on line 57 in our init? method. Is there a chance you can elaborate on that?

1 Like

Sure!

After we get to that part of the failable initializer, we know that the character is not nil. So the Image we’re initializing won’t be nil, either. Instead, it will be one of these SFSymbols:

In order to create an Image from one of those symbol names, you call
init(systemName:) | Apple Developer Documentation.

How come when we do extension Image {} swift knows that we are extending Swift’s Image and not the struct image that we just created? about 6:10 minutes into the video

How come when we do extension Image {} swift knows that we are extending Swift’s Image and not the struct image that we just created? about 6:10 minutes into the video

I don’t understand of the grammar in part of " case let 
 " why do we use ? and how to be possible ?

ìŠ€íŹëŠ°ìƒ· 2021-09-11 였후 5.44.23

An extension is written at file-scope. The only Image in that scope is SwiftUI.Image.

You would have to write

extension Book.Image {

if you specifically wanted to extend that type.

guard let and if let are just shorthand.

For example


guard let character = title.first

is shorthand for

guard case .some(let character) = title.first

But as I mention in the video, what you bind on each line does not have to be optional. If it’s not, you have to use the “case” form.

1 Like

Thank you.
Could you give me a reference for the part
"If it’s not, you have to use “case” form "

1 Like

I mention that it’s necessary at 10:39.
Sorry, I can’t find any documentation on binding non-optionals, only optionals.

All there is to it is that if you want to define new variables inside of an if or guard statement, you do that using case.

Example:

if
  case let ten = 2 * 5,
  ten < 11
{
  print("Ten is less than eleven.")
}

It’s an alternate form of this:

switch 2 * 5 {
case let ten where ten < 11:
  print("Ten is less than eleven.")
default:
  break
}
1 Like

Thank you very much for you rapid and kind answer.

I have just finished this tutorial and I found how the Image for the book is handled at this point very convoluted. I understand that this will be updated in the future when we grab the files, but to me I should not be passing the title of a book to Book.Image. A book has a stored property for title and there is no reason to pass this. A book knows its own title and therefore, should be able to create its own Image based on that title.

I’m going to continue with your example, but I’m also going to create an app that shadows every step for this tutorial, programming it the way that I think is more intuitive for a new user. (Just crossing my fingers that I don’t back myself into a corner :crazy_face:)

What seems like the most straightforward solution is that the book’s image should be a computed property of Book (ignoring the Emoji case here).

   var bookImage : Image {
      guard let character = title.first else {
         return Image(systemName: "book")
      }
      
      let symbolName = "\(character.lowercased()).square"
      return Image(systemName: symbolName) 
   }

I’ve created a view (BookView) to test. As one would expect, it has one property of type Book.

struct BookView: View {
   let book : Book
    var body: some View {
       HStack {
          book.bookImage
             .resizable()
             .scaledToFit()
             .frame(width: 80, height: 80)
             .font(Font.title.weight(.light))
             .foregroundColor(.secondary)
          VStack(alignment: .leading) {
             Text(book.title)
             Text(book.author)
                .foregroundColor(.secondary)
          }
       }
    }
}

Calling BookView like this:

BookView(book: Book(title: "Carrie", author: "Stephen King"))

seems to work very well and I am assuming will be helpful when it comes to the List view.

1 Like

A Book Image will only ever require a title, not an entire book, but the only place where this will end up mattering is in previews. Elsewhere in the app, there won’t be a Book Image without a Book. So I like your approach. Feel free to continue with it!

You’re right that, at the state of the project where this episode is, a computed property of Book will do wonderfully. Later, you’ll need to convert it to be a method, as arguments will be required. Utilizing a named View type allowed me to avoid having to introduce the ViewBuilder attribute. You’ll need to use that on your method; post a comment for a later episode if that ends up giving you problems.

Thank-you for the detailed reply Jessy. Its great discussion and interactions with course authors like you that make this site special. Much appreciated.

I’ve decided just to proceed with my code. It will be more work every lesson, but I think I will learn more.

One issue I think with my approach is that I have mixed UI elements into my data model. Not a big deal for this example, but probably as a programming practice this will make conversion to a different framework or converting a project to a different platform more difficult.

To fix this issue I have made a protocol called “ImageableItem” that requires a computed property (itemImage) to return a SwiftUI Image. Then added this extension to Book:

extension Book : ImageableItem {
   
   var itemImage : Image {
      guard let character = title.first else {
         return Image(systemName: "book")
      }
      
      let symbolName = "\(character.lowercased()).square"
      
      // Check for Emoji
      guard UIImage(systemName: symbolName) != nil else {
         return Image(systemName: "book")
      }

      return Image(systemName: symbolName)
   }
}

I know you mentioned that I will need to convert this to a method later, but I am going to stick with the computed property and try to make that work. I am going to build the ImageableItem protocol in such a way that it ensures any structure adhering to the protocol will be able to generate its image using a computed property. I feel like I should be able to accomplish this. (This may be backing me into a corner, but I’ll chalk it up to a learning experience :relaxed:)

Thanks again for your input.

1 Like