JSON Decoding | raywenderlich.com


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5429634-saving-data-in-ios/lessons/11

When we change the PrioritizedTask to coform to Codable protocol we still saw an error. In the tutorial video once we change the Priority enum to also be Codable the issue resolved.

My question is, how do we know that it needs to conform to Codable protocol as well. Because the error that we gave was also not informative enough to identify this change.

Excellent question! Thank you asking it :slight_smile:

A good general rule would be to ensure that all of your properties, and their respective types, can be encoded and, thus, adhere to Codable. A lot of built in types already do this, like Int, String, etc.

However, let’s think of the following example:

struct Reminder {
  var creator: User
  var date: Date
  var text: String
}

struct User {
  var name: String
  var age: Int
}

Now let’s say you just want to save your reminders, so you make it adhere to Codable:

struct Reminder: Codable {
  var creator: User
  var date: Date
  var text: String
}

You are going to get that error you mention, and the reason is that if you go down the list of properties in your custom Reminder type, one by one, you need to ask yourself: “Can this be encoded automatically?”.

In the case of date and text the answer is yes, but in the case of creator, Swift is going to say "I have no idea what the User type is. That becomes your indicator to look in User and have it conform to Codable:

struct User: Codable {
  var name: String
  var age: Int
}

Same as with Reminder, go through your properties one by one and ask whether they can be encoded out of the box. Both String and Int are Codable types, and now User conforms to Codable, so all of your errors should be gone.

Think of it like a tree with many branches, where each branch is a property with a type. All branches in the tree must be “codable” or the entire tree will fail to encode :slight_smile:

Let me know if that helps clarify things for you and if it helps you for future types you may encode/decode :smiley:

Excellent explanation of the above question !

@nz_hamza @shrutisharma Please also check out our encoding and decoding tutorial when you get a chance:

https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift

I hope it helps!

In the ContentView, the code
ForEach(taskStore.prioritizedTasks) { index in
…. }
specifies the type of the element of the prioritizedTasks is PrioritizedTasks. However, the the type of the parameter of the closure is Int. Why can this setting still work and is no error shown?

In this case we are asking SwiftUI to help us create some Views for a given number of items in our collection. The closure parameter, however, is the index of each item in prioritizedTasks.

Let’s say we have 5 prioritized tasks that we loop through using ForEach. It will go through them and tell us: “Hey, I’m currently working with a prioritizedTask that’s at index 2 of the collection”. This allows us to do things specifically per index, or anything we need necessary.

There are a couple of flavors of ForEach available for us, we decided to go with this one for this particular example :slight_smile:

Hope that helps clear up a bit why we have one type of parameter for the closure versus the actual collection we use in the ForEach.

First of all, thank you very much for your detailed and practical course, which helped me a lot. At the same time, thank you for your immediate response.

I still have some questions I want to discuss with you again.
After checking Developer Documentation a few times, I found that ForEach has three initializers:
init(Data, content: (Data.Element) → Content)
init(Range, content: (Int) → Content)
init(Data, id: KeyPath<Data.Element, ID>, content: (Data.Element) → Content)

It seems that the parameter type of the closure must be the same as Data.Element.

For testing, I created a new file and reprogrammed it according to your sample program.
When compiling, the following error appears:
Cannot convert value of type’[PrioritizedTasks]’ to expected argument type’Range Int>’

Do you have any suggestions for dealing with this problem?

My most sincere apology. I forgot to mention that the initializer we are using there is an extension we created in ForEach.swift:

  init<Base: RandomAccessCollection>(
    _ base: Base,
    @ViewBuilder content: @escaping (Base.Index) -> Content
  )
  where
    Data == IndexedCollection<Base>,
    Base.Element: Identifiable,
    ID == Base.Element.ID
  {
    self.init(IndexedCollection(base), id: \.element.id) {
      index, _ in content(index)
    }
  }

This allows us to iterate over a collection and have the index of the item being iterated on as a closure parameter :slight_smile:

Hopefully that helps clarify why it works so different than the three (at least last I checked :stuck_out_tongue: ) initializer we get out-of-the-box for ForEach in SwiftUI :slight_smile:

Thank you for your explanation.
You provided a great custom initializer, and I will use it often in the future.

You’re most welcome. Apologies for replying after several weeks.

I hope you are enjoying the course, and feel free to ask any questions you may have :slight_smile: