Image for post
Image for post
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

Image for post
Image for post
Image for post
Image for post
let query = “dogs” lazy var endPoint: String = { return “https://api.flickr.com/services/feeds/photos_public.gne?format=json&tags=\(self.query)&nojsoncallback=1#" }()
enum Result <T>{ 
case Success(T)
case Error(String)
}
func getDataWith(completion: @escaping (Result<[String: AnyObject]>) -> Void) { 
}
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()
let service = APIService()  
service.getDataWith { (result) in
print(result)
}
guard let itemsJsonArray = json["items"] as? [[String: AnyObject]] else {  return  }
Result<[String: AnyObject]>
Result<[[String: AnyObject]]>
DispatchQueue.main.async {
completion(.Success(itemsJsonArray))
}
func getDataWith(completion: @escaping (Result<[[String: AnyObject]]>) -> Void) {
//Here goes the implementation
}
return completion(.Error(error.localizedDescription))
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()
}
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)
}
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)
}
}
}
lazy var endPoint: String = {
return "https://api.flickr.com/services/feeds/photos_public.gne?format=json&tags=\(self.query)&nojsoncallback=1#"
}()
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
static let sharedInstance = CoreDataStack()
private override init() {}
CoreDataStack.sharedInstance.saveContext()
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
}
private func saveInCoreDataWith(array: [[String: AnyObject]]) {
_ = array.map{self.createPhotoEntityFrom(dictionary: $0)}
do {
try CoreDataStack.sharedInstance.persistentContainer.viewContext.save()
} catch let error {
print(error)
}
}
for dict in array { 
_ = self.createPhotoEntityFrom(dictionary: dict)
}
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)
}
}
}
func applicationDocumentsDirectory() {
if let url = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).last {
print(url.absoluteString)
}
}
CoreDataStack.sharedInstance.applicationDocumentsDirectory()
Image for post
Image for post
Image for post
Image for post
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
}()
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
}
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"))
}
}
}
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()
}
}
}
Image for post
Image for post
do {
try self.fetchedhResultController.performFetch()
print("COUNT FETCHED FIRST: \(self.fetchedhResultController.sections?[0].numberOfObjects)")
} catch let error {
print("ERROR: \(error)")
}
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)")
}
}
}
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)
}
}
}
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()
}
}
//frc.delegate = self
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)
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Photos Feed"
view.backgroundColor = .white
tableView.register(PhotoCell.self, forCellReuseIdentifier: cellID)
clearData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Photos Feed"
view.backgroundColor = .white
tableView.register(PhotoCell.self, forCellReuseIdentifier: cellID)
updateTableContent()
}
Image for post
Image for post
startappstudio.com

Written by

Senior iOS Engineer #latinintech

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store