www.startappstudio.com

Parsing JSON response and save it in CoreData, step by step.

Hint: This app was made with Xcode 8.1, iOS 10 and Swift 3

James Rochabrun
21 min readMar 6, 2017

--

Do you remember when you are under a tunnel or camping with friends, and the content from a feed of an app that you are using becomes just a blank screen?

It would not be better if we as developers find a way to show some content to the user in those situations and make her/him have a better experience?

Developers MUST think about the details and go beyond their implementations, always adopting a mindset that advocates the user.

So, how do we fix this issue and make content available, somehow, even in situations with a poor internet connection?

One way to solve it is to make that our app persist data in the user’s phone, there are different tools for that task and one of the most popular is the CoreData framework which is the persistence framework provided by Apple.

I tried to find tutorials online that shows how to handle persisting data coming from a web server, and although I found awesome material and tips (and a lot of tutorials on how to use CoreData to build a “to do List” App) I didn’t found something related to this specific task.

Ok, let’s begin, we are going to make a very simple App that shows a feed of photos coming from the Flickr public API and how to implement CoreData to make the feed visible even with no internet connection; we are are going to use many tools on this project and before we continue, I want to throw a quick disclaimer…it can be a long post and I apologize in advance for that, but it’s because I will try my best to explain a little bit of each tool used in this app.

On this project, we will …

  • Parsing JSON from a server
  • Use closures to handle Asynchronous calls.
  • Mix Generics and Enums to handle Errors
  • Use the map function.
  • Use CoreData to save data.
  • Use sqliteBrowser to see the saved data in your computer.
  • Delete data from CoreData
  • Update data in CoreData
  • Implement NSFetchedResultsController to fetch data.
  • Implement the NSFetchedResultsControllerDelegate to update the UI.
  • Use a Singleton.
  • Use an alert controller to show error messages to the user.

Ready? start by cloning or downloading this repo, I started creating this project by checking the box “Use CoreData”, this will autogenerate an implementation in your AppDelegate for CoreData, and also I setup a basic UI programmatically, if you prefer to use Interface builder just create a project and add a TableViewController in your storyboard, don’t forget to check the “Use CoreData” box.

Run the app and you will see this, the gray section is an ImageView, it has two labels below and a gray line that separates both labels.

Start by creating a new file and call it APIService, make it a subclass of NSObject, add this two properties.

let query = “dogs” lazy var endPoint: String = { return “https://api.flickr.com/services/feeds/photos_public.gne?format=json&tags=\(self.query)&nojsoncallback=1#" }()

The endPoint variable is the endpoint from the Flickr public API, it let you add a query in the URL that will change the content of the response. In this file but outside the class paste this enum.

enum Result <T>{ 
case Success(T)
case Error(String)
}

Here we create an enum with associated values and constrained to a generic type, you probably heard about generics in Swift, but what are they?

Generic programming is a way to write functions and data types while making minimal assumptions about the type of data being used, Swift generics create code that does not get specific about underlying data types, allowing for elegant abstractions that produce cleaner code with fewer bugs! check more about generics here

The benefit of use generics in your programming is that you won’t have to repeat implementations separately for each specific type; this enum, for example, will accept any type and in the success case, will associate that type to its associated value type.

Next, let’s create a function to help us get some data from the web; for this task, we are going to create a function with a closure, what is a closure? well, a closure is just another function that is passed as an argument, did you heard that functions in swift are first class citizens? well, that means that function can be assigned to variables, constants and also can be passed as arguments, very cool right? btw, they are called blocks in objective-C and I must accept that the syntax to create one in that language, although I declared myself many times as a big Objective — C fan, is more than horrifying. Closures are great to handle Asynchronous calls because they are executed after the completion of a certain task, that’s why sometimes are named as completion handlers. Copy and paste this inside your APIService class.

func getDataWith(completion: @escaping (Result<[String: AnyObject]>) -> Void) { 
}

Ok, let’s go step by step, the getDataWith function accepts a parameter that is another function, and that function accepts as a parameter our enum constraint to a type Dictionary, remember the weird <T> in the enum? well, in this function the T becomes of type [String: AnyObject], can you see how awesome generics are, not yet? ..it’s ok, just get the idea that you don’t need to create a specific enum for every type thank to generics.

Finally, what is the @escaping keyword? the definition is actually very simple and easy to understand. If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping. It is also said that the closure argument escapes the function body.

Inside this new function, we are going to use the class URLSession to get data from a web server, before we continue I suggest to check Networking libraries like Alamofire or Tron, to handle networking calls, they definitely will make your life easier, but at this time I won’t use them because I don’t want to go to deep in how to include them in your project using cocoapods.(maybe I’ll do another tutorial in the future doing that :) )
Copy this inside the function.

guard let url = URL(string: endPoint) else { return }URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { return }
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers]) as? [String: AnyObject] {
DispatchQueue.main.async {
completion(.Success(json))
}
}
} catch let error {
print(error)
}
}.resume()

Here we are using the URLSession datatask with completion method to make a GET request to the server, inside it, we are using guard statements to handle potential errors, then we are using the JSONSerialization class to parse the data and downcast it into a dictionary.
Finally, you can see that we call the completion on the main thread, that’s because closures are executed Asynchronously, remember that, if not, your app can crash. Time to print some data. Go to PhotoVC and add this inside viewDidload and run your app.

let service = APIService()  
service.getDataWith { (result) in
print(result)
}

In your console, you should see a JSON data, that looks like a dictionary with keys like “description”, “title”, “items”, we are going to parse the data inside the “items” key that is an array of dictionaries. To access that we need to make two changes to our function, start by adding this above the dispatch block.

guard let itemsJsonArray = json["items"] as? [[String: AnyObject]] else {  return  }

We are using the key “items” of the JSON dictionary and downcast the value as an array of dictionaries, and we are going to use those dictionaries to create our model. In order to be able to pass this array as an argument value for the the closure, we need to change the type of the closure argument, replace this…

Result<[String: AnyObject]>

for this…

Result<[[String: AnyObject]]>

Now you can pass the itemsJsonArray instead of the JSON dictionary, like this.

DispatchQueue.main.async {
completion(.Success(itemsJsonArray))
}

Your function declaration should look like this now

func getDataWith(completion: @escaping (Result<[[String: AnyObject]]>) -> Void) {
//Here goes the implementation
}

Run the app, and in the console, you will see just the content of the items key; btw, did you realize that when we changed the type of the argument of the closure, we didn’t have to change the type constraint in the enum? does generics make more sense now?

If you are having problems to see the data structure in the console I recommend to use the same endpoint in a Chrome extension like Postman or JsonView, they will make your life easier.

At this point you are probably asking, so…what is the enum for? and I a glad you asked (Hope you did :P ), well, I am going to show you one way to handle errors. Making a request to a server can give us different kind of errors and it will be nice to know more about them, not only to make our debugging easier but also to tell the user the type of issue that he/she is experimenting. At this point, we are handling errors just by making the program returns and that’s fine but we can do it better, if you see the enum, it has two cases, a Success, that we already used, and one case Errors. For a quick demo, inside the catch clause inside our function, instead of printing the error add this line.

return completion(.Error(error.localizedDescription))

Go to the endpoint variable and delete anything from the string, in short words make it a not valid endpoint, and run the app, you should see a message like this in your console “Error(“The data couldn’t be read because it isn’t in the correct format.”)”

As I mentioned not every error from an HTTP request is the same so we are going to handle them differently, instead of just make a return in every guard statement we are going to call our completion now using the Error case, your final implementation will look like this.

func getDataWith(completion: @escaping (Result<[[String: AnyObject]]>) -> Void) {
guard let url = URL(string:endPoint ) else { return completion(.Error("Invalid URL, we can't update your feed")) }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { return completion(.Error(error!.localizedDescription)) }
guard let data = data else { return completion(.Error(error?.localizedDescription ?? "There are no new Items to show"))
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers]) as? [String: AnyObject] {
guard let itemsJsonArray = json["items"] as? [[String: AnyObject]] else {
return completion(.Error(error?.localizedDescription ?? "There are no new Items to show"))
}
DispatchQueue.main.async {
completion(.Success(itemsJsonArray))
}
}
} catch let error {
return completion(.Error(error.localizedDescription))
}
}.resume()
}

Now that we have a specific message that we can use to send a message to the user based on the type of error let’s create an alert that shows the message, go to PhotoVC and add this method

func showAlertWith(title: String, message: String, style: UIAlertControllerStyle = .alert) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: style)
let action = UIAlertAction(title: title, style: .default) { (action) in
self.dismiss(animated: true, completion: nil)
}
alertController.addAction(action)
self.present(alertController, animated: true, completion: nil)
}

Also in viewDidLoad replace the getDataWith() method for this implementation…

let service = APIService()
service.getDataWith { (result) in
switch result {
case .Success(let data):
print(data)
case .Error(let message):
DispatchQueue.main.async {
self.showAlertWith(title: "Error", message: message)
}
}
}

Here “result” represents the enum case that we passed on the completion call and we can switch throw it to perform a specific action.

Now run the app and you should see an alert showing an error message. We are ready now for the second part of this post, CoreData.

Before we jump into CoreData, now is a good moment for a break, so go and check what’s new on Instagram, snap something or get a coffee, and come back when you are ready.

Ready? ok…start by fixing the endpoint for the original, it should look like this:

lazy var endPoint: String = {
return "https://api.flickr.com/services/feeds/photos_public.gne?format=json&tags=\(self.query)&nojsoncallback=1#"
}()

Now that we have data we are going to create our model, it will be a simple class called Photo, If you see the structure of each dictionary in the items array you can see different keys and values, we are going to use just three key values, “author”, “media”, and “tags” to set up our Photo model.

If you have no experience with CoreData, now is where things become interesting. When I am saying that we are going to create a Photo object you are probably thinking in create it by subclassing NSObject but what we need instead is an object subclass of NSManagedObject, this is a generic class that implements all the basic behavior required of a CoreData model object, its not that hard to implement so just follow along with me, start by opening the file with extension .xcdatamodeld in your bundle (it is where all your files are), click on it, it will open something like this.

Click on Add Entity.

The red arrow points to the section where you need to add a name for your entity (entity is just a name for an object subclass of NSmanagedobject), type Photo.
The green arrow points to the place where you add attributes for the entity, (attributes are just properties of an object subclass of NSmanagedObject), add three attributes, author, mediaURLand tags, make them of type String.

If you are using Swift 3, you are probably going to find errors in your project if you miss the last step pointed by the blue arrow, just change the module to “Current Product Module” and the Codegen to “Manual/None” (I found this solution in StackOverflow)

Finally, in editor click on Create NSManagedObject Subclass, if you want I add a Model group in the project where you can save this new class (just to keep this organized but you don’t have to)

Now, you have your class and if you go inside the file you will see that it don’t look like the ones that you are used to.
Now go to AppDelegate file, you will see a pragma mark at the bottom that says CoreData Stack, if you worked with CoreData in the past you are probably surprised how compressed is now, and we have to thank Apple for that!
In order to keep our code clean we are going to separate the CoreData implementation from the AppDelegate, you can just keep it like it is and make a reference every time to your AppDelegate when you need to, but a better way to do it is to create a different class to separate responsibilities, create a new file subclass of NSObject and name it CoreDataStack.

We are going to do it step by step, start by removing the persistentContainer variable and the saveContext() method from AppDelegate, and put them in your CoreData Stack class, next import CoreData to your new class and remove it from AppDelegate.

At this point, you will have one error, and it’s because the saveContext method is now part of your CoreDataStack class, let’s delete that call in AppDelegate for now.

Finally, because we only want to initialize our CoreDataStack class just once, we are going to use a singleton. A singleton is just a design pattern that helps create only one instance for a class, and let you share the same instance in all your app. We can, of course, use dependency injection to keep a reference between classes and just pass the same instance one class to the other, but in this case, a Singleton suits better to our implementation.

Lucky for us In Swift is super easy to create a singleton, we just need to add this two lines to our CoreDataStack class.

static let sharedInstance = CoreDataStack()
private override init() {}

In the first line, we are just creating a class constant called sharedInstance and assigning to it a CoreDataStack initialized object. In the second line, we are making the overriding init method private to restrict access to it. Check this post with more about singletons.

Enough of singletons, now you have a decent CoreDataStack class, let’s start using it, first go to AppDelegate, remember that we delete a method call inside the applicationWillTerminate() method? well, let’s add it back using now our CoreDataStack class, add this line there.

CoreDataStack.sharedInstance.saveContext()

Now run the app, just to check that everything is fine, the Flickr response gives back 20 items in a dictionary structure (assuming that Flickr doesn’t change that by the time you are reading this), we are going to create 20 NSManagedObjects using them and save them in CoreData. To avoid repetition let’s create a helper method that accepts a dictionary and returns a NSManagedObject, copy and paste this in your PhotoVC

private func createPhotoEntityFrom(dictionary: [String: AnyObject]) -> NSManagedObject? {
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
if let photoEntity = NSEntityDescription.insertNewObject(forEntityName: "Photo", into: context) as? Photo {
photoEntity.author = dictionary["author"] as? String
photoEntity.tags = dictionary["tags"] as? String
let mediaDictionary = dictionary["media"] as? [String: AnyObject]
photoEntity.mediaURL = mediaDictionary?["m"] as? String
return photoEntity
}
return nil
}

So what is all this? well, the persistentContainer has a property called viewContext and is the context where our data will be handled; next, we have the NSEntityDescription class and it handles the initialization of a NSManagedObject, the string passed as a name is the entity name that we create in the .xcdatamodeld file. The rest is just following standard conventions to assign values to properties (or to attributes in this case), if you ask where do I get this keys, please just check in the console the structure of the JSON response again. Now let’s save these items into CoreData, copy and paste this method in PhotoVC

private func saveInCoreDataWith(array: [[String: AnyObject]]) {
_ = array.map{self.createPhotoEntityFrom(dictionary: $0)}
do {
try CoreDataStack.sharedInstance.persistentContainer.viewContext.save()
} catch let error {
print(error)
}
}

If you are not familiar with high order functions, the map function, in this case, it’s just a different way to iterate inside the elements inside an array, the $0 represents each of them, and it uses a closure where you can execute other functions to each of those elements ; you can change this for a for loop if you want like this…

for dict in array { 
_ = self.createPhotoEntityFrom(dictionary: dict)
}

But I think that you should give it a try and research more about high order functions, you can find a good video about them here :)

Finally, we are using the save() method of the viewContext to save our objects inside a do clause because the save method can throw an error. Let’s change our service call to save the data, it should look like this now.

let service = APIService()
service.getDataWith { (result) in
switch result {
case .Success(let data):
self.saveInCoreDataWith(array: data)
print(data)
case .Error(let message):
DispatchQueue.main.async {
self.showAlertWith(title: "Error", message: message)
}
}
}

If you run the app now, you won’t see any changes, and it’s because in order to display the data we have to fetch it once is saved, before we go into that I want to show you a tip that will help you see what’s going on when you save into CoreData, you can omit this step if you want.

First, you are going to need to download DB Browser for SQLite, once installed add this method into your CoreDataStack class.

func applicationDocumentsDirectory() {
if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
print(url.absoluteString)
}
}

Call this method in your AppDelegate inside the didFinishLaunchingWithOptions, just like this…

CoreDataStack.sharedInstance.applicationDocumentsDirectory()

Now run the app and you will see in your console that a path was printed, it should look like this…

Copy the path avoiding this first piece file:// then go to Finder on your computer and G0>>Go to Folder>>paste the path and hit Go. You will see some Directories, go inside the one called Application Support, find the file with .sqlite extension, right-click on it and open it using the sqliteBrowser. Now you can see your items in a DataBase!

Now that we know that we have data saved let’s fetch it using a NSFetchedResultcontroller, we are going to create it as a variable, copy and paste this in your photoVC.

lazy var fetchedhResultController: NSFetchedResultsController<NSFetchRequestResult> = {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: String(describing: Photo.self))
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "author", ascending: true)]
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: CoreDataStack.sharedInstance.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
// frc.delegate = self
return frc
}()

The NSFetchedResultscontroller requires a NSFetchRequest and a NSSortDescriptor and a managedObjectContext.

Now, change the implementation of the cellforRowatIndexPath and numberofRownInsection for this …

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! PhotoCell
if let photo = fetchedhResultController.object(at: indexPath) as? Photo {
cell.setPhotoCellWith(photo: photo)
}
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let count = fetchedhResultController.sections?.first?.numberOfObjects {
return count
}
return 0
}

NSFetchedResultsController provides results from fetch requests structured in indexed sections that have indexed items in each of them, is that make a little more sense of what’s going on here? I hope so.

Now, before we continue into coreData let’s create the implementation for the setPhotCellWith() method, go to PhotoCell class and copy and paste this.

func setPhotoCellWith(photo: Photo) {
DispatchQueue.main.async {
self.authorLabel.text = photo.author
self.tagsLabel.text = photo.tags
if let url = photo.mediaURL {
self.photoImageview.loadImageUsingCacheWithURLString(url, placeHolder: UIImage(named: "placeholder"))
}
}
}

So where does the loadImageUsingCacheWithURLString is coming from? well , because we like to do things right and better for the user, we are going to implement a function to cache the images coming from the server; Create an empty file and call it Extensions, inside, we are going to create an UIImageView extension that will cache the images coming from the server, it will also show a placeholder image if there is no image. (I add a placeholder image in the assets file btw :) ). Copy and paste this into your new file.

import UIKit
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage?) {
self.image = nil
if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
self.image = cachedImage
return
}
if let url = URL(string: URLString) {
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//print("RESPONSE FROM API: \(response)")
if error != nil {
print("ERROR LOADING IMAGES FROM URL: \(error)")
DispatchQueue.main.async {
self.image = placeHolder
}
return
}
DispatchQueue.main.async {
if let data = data {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
self.image = downloadedImage
}
}
}
}).resume()
}
}
}

Ok, now that we have our cache function and our cell is ready to display data, let’s talk about in how to fetch data from CoreData, but before that, I need you to make me a favor, remember that we already saved items in CoreData? well, for the good of this exercise we need to delete them first (we need to put the app in a state that replicates the state when the user opens the app for the first time, when there is nothing saved), so go to Simulator>>Reset contents and settings and hit Reset, this will delete all the items saved in our context, don’t worry I’ll show you the correct way to delete items in CoreData in one second.

Now that our context is “clean”, we can continue, the fetchedResultsController have a very explicit method called performFetch that, you guessed it, it will fetch data from CoreData. copy and paste before the API call in viewDidLoad

do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}

Run the app, you will see that you won’t get any update yet and that’s because, as you probably figured it out, we are performing the fetch to an empty context before actually saving data, and we are going to fix that soon, run the app one more time, this time you will see a feed of photos with an author name and a few tags.

All looks good at this moment but we have two problems that we need to fix, the first is to solve that the user don’t have to run the app twice to actually see some results the first time he uses the app, and the second one is that if you keep running the app you will see that we will keep saving the same items over and over, our feed will become longer but with duplicated items, you can see that the count of the fetched results gets incremented too, check the print in the console.

COUNT FETCHED FIRST: Optional(60)

Let’s start with the second issue first, in order to solve this, we need to delete the saved data before update our storage with new data coming from the API, now is the time to show you how to delete items from CoreData correctly, copy and paste this function.

private func clearData() {
do {
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Photo")
do {
let objects = try context.fetch(fetchRequest) as? [NSManagedObject]
_ = objects.map{$0.map{context.delete($0)}}
CoreDataStack.sharedInstance.saveContext()
} catch let error {
print("ERROR DELETING : \(error)")
}
}
}

Here we are using the delete method of viewContext. First, we need to fetch the items and then delete them, we are using the map function to delete each one and finally, we are triggering the saveContext() method of the CoreDataStack class to save the updated context. Now call this method inside your API call, it should look like this now.

let service = APIService()
service.getDataWith { (result) in
switch result {
case .Success(let data):
self.clearData()
self.saveInCoreDataWith(array: data)
//print(data)
case .Error(let message):
DispatchQueue.main.async {
self.showAlertWith(title: "Error", message: message)
}
}
}

Run the app and this time you will see that every time the app runs it won’t give you duplicate content, one problem solved one to go.
To solve our first issue where the user needs to refresh the app twice the first time to actually can see some photos, we need to find a way to update the tableView when the new items are saved in the context. So what I am saying is we need a function that gets triggered when something in the context is changed, and the way to go is using the NSFetchedResultsControllerDelegate protocol, this delegate has convenient methods that will help us solve our issue. Copy and paste this extension in PhotoVC

extension PhotoVC: NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
self.tableView.insertRows(at: [newIndexPath!], with: .automatic)
case .delete:
self.tableView.deleteRows(at: [indexPath!], with: .automatic)
default:
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
}

And also in the fetchedhResultController uncomment this…

//frc.delegate = self

So what is happening here, the first method is aware of the changes of every object in the context, so in order to update the tableView based on the new context, we need to insert new rows for each new item that has been saved on it, and because we are also deleting the “old” items we also need to delete the “old” rows of the table, if you don’t do that update in your tableView your app will crash.

The switch statement takes the NSFetchedResultsChangeType passed as a parameter and decides if was an insertion or a delete and handle each situation inserting or removing cells. The other two methods are called when the context is about to change and when it did change, inside of them we are calling tableView methods that will get ready the tableView for updates.

It’s time to test that our app is working and for that, we are going to make some refactoring to clean up the viewDidLoad; first create a new method called updateTableContent() and take the logic of performFetch and API call from the viewDidLoad and put it inside of it like this.

func updateTableContent() {
do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}
let service = APIService()
service.getDataWith { (result) in
switch result {
case .Success(let data):
self.clearData()
self.saveInCoreDataWith(array: data)
case .Error(let message):
DispatchQueue.main.async {
self.showAlertWith(title: "Error", message: message)
}
}
}
}

Second, we need to delete all the items saved, we can reset the contents and settings in the simulator but it’s easier if we use the clearData() function, use it in viewDidLoad like this.

override func viewDidLoad() {
super.viewDidLoad()
self.title = "Photos Feed"
view.backgroundColor = .white
tableView.register(PhotoCell.self, forCellReuseIdentifier: cellID)
clearData()
}

Finally, let’s put our new method and remove the clearData, like this.

override func viewDidLoad() {
super.viewDidLoad()
self.title = "Photos Feed"
view.backgroundColor = .white
tableView.register(PhotoCell.self, forCellReuseIdentifier: cellID)
updateTableContent()
}

One final tip, if you are asking yourself where is the best place to make calls to a server, on my experience, it will depend if the content coming from the server is fairly static I’ll do it on viewDidLoad, but if the content is dynamic and have constant updates I’ll do it in viewWillAppear.

Now run the app, and you will see the feed updated the first time, and if you keep running it won’t give you duplicated content.

This was a lot of things and I apologize for the extended post, I hope you can find it as a good resource for each of all the topics exposed on it. It will be great if you let me know if something is missing or if you have any kind of suggestion to implement API calls and CoreData together.

Download or clone the full project here.

startappstudio.com

If you are interested in an app for your startup, maybe I can help, send me an email to james@startappstudio.com

--

--