From a1b664776e53b822e0d59139c9ee565ea5a29027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pin=CC=83era=20Buendi=CC=81a?= Date: Wed, 28 Sep 2016 18:23:48 +0200 Subject: [PATCH] [examples/api] Add an example fetching items from the API and saving them into Realm --- Example/Podfile.lock | 16 +-- Example/SugarRecord.xcodeproj/project.pbxproj | 44 ++++++++ .../Examples/News/Models/RealmNew.swift | 10 ++ .../Examples/News/Services/NewsService.swift | 54 ++++++++++ .../Source/Examples/News/Views/NewsView.swift | 100 ++++++++++++++++++ .../Source/Main/ViewController.swift | 6 +- 6 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 Example/SugarRecord/Source/Examples/News/Models/RealmNew.swift create mode 100644 Example/SugarRecord/Source/Examples/News/Services/NewsService.swift create mode 100644 Example/SugarRecord/Source/Examples/News/Views/NewsView.swift diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 2e6406d..fcd52b3 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -23,15 +23,15 @@ PODS: - Realm (= 1.1.0) - Result (3.0.0) - SnapKit (3.0.1) - - SugarRecord (3.0.0-alpha.1): - - SugarRecord/CoreData (= 3.0.0-alpha.1) - - SugarRecord/CoreData+iCloud (= 3.0.0-alpha.1) - - SugarRecord/Realm (= 3.0.0-alpha.1) - - SugarRecord/CoreData (3.0.0-alpha.1): + - SugarRecord (3.0.0-alpha.2): + - SugarRecord/CoreData (= 3.0.0-alpha.2) + - SugarRecord/CoreData+iCloud (= 3.0.0-alpha.2) + - SugarRecord/Realm (= 3.0.0-alpha.2) + - SugarRecord/CoreData (3.0.0-alpha.2): - Result (~> 3.0) - - SugarRecord/CoreData+iCloud (3.0.0-alpha.1): + - SugarRecord/CoreData+iCloud (3.0.0-alpha.2): - Result (~> 3.0) - - SugarRecord/Realm (3.0.0-alpha.1): + - SugarRecord/Realm (3.0.0-alpha.2): - RealmSwift (~> 1.1) - Result (~> 3.0) @@ -58,7 +58,7 @@ SPEC CHECKSUMS: RealmSwift: 838058b2db95b12cb86bd0cf209df642c33fb60a Result: 1b3e431f37cbcd3ad89c6aa9ab0ae55515fae3b6 SnapKit: f818b8326d45b4e1c777d0ab27b5c0a3624bfdeb - SugarRecord: 13f9d029c077dea63c7f7240dd401ac4af15a84c + SugarRecord: 5d12c70dadef8e036edc795c76e533584b7da453 PODFILE CHECKSUM: 93e4a0d6049261cca889912a17deebad59571a81 diff --git a/Example/SugarRecord.xcodeproj/project.pbxproj b/Example/SugarRecord.xcodeproj/project.pbxproj index 3603583..c567acc 100644 --- a/Example/SugarRecord.xcodeproj/project.pbxproj +++ b/Example/SugarRecord.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 23226F871D9C1ECF006A6769 /* NewsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23226F861D9C1ECF006A6769 /* NewsService.swift */; }; + 23226F891D9C1F1A006A6769 /* RealmNew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23226F881D9C1F1A006A6769 /* RealmNew.swift */; }; + 23226F8B1D9C22B1006A6769 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23226F8A1D9C22B1006A6769 /* NewsView.swift */; }; 23E13E5B1D96896300204C82 /* CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E13E341D96896300204C82 /* CoreData.swift */; }; 23E13E5C1D96896300204C82 /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E13E351D96896300204C82 /* Realm.swift */; }; 23E13E5D1D96896300204C82 /* Track+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E13E381D96896300204C82 /* Track+CoreDataProperties.swift */; }; @@ -57,6 +60,9 @@ /* Begin PBXFileReference section */ 199B6E9AFF9F22F151FCACD5 /* Pods-SugarRecord_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SugarRecord_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SugarRecord_Example/Pods-SugarRecord_Example.debug.xcconfig"; sourceTree = ""; }; + 23226F861D9C1ECF006A6769 /* NewsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsService.swift; sourceTree = ""; }; + 23226F881D9C1F1A006A6769 /* RealmNew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmNew.swift; sourceTree = ""; }; + 23226F8A1D9C22B1006A6769 /* NewsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = ""; }; 23E13E341D96896300204C82 /* CoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreData.swift; sourceTree = ""; }; 23E13E351D96896300204C82 /* Realm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Realm.swift; sourceTree = ""; }; 23E13E381D96896300204C82 /* Track+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Track+CoreDataProperties.swift"; sourceTree = ""; }; @@ -125,6 +131,40 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 23226F811D9C1E9D006A6769 /* News */ = { + isa = PBXGroup; + children = ( + 23226F821D9C1EC6006A6769 /* Models */, + 23226F841D9C1EC6006A6769 /* Services */, + 23226F851D9C1EC6006A6769 /* Views */, + ); + path = News; + sourceTree = ""; + }; + 23226F821D9C1EC6006A6769 /* Models */ = { + isa = PBXGroup; + children = ( + 23226F881D9C1F1A006A6769 /* RealmNew.swift */, + ); + path = Models; + sourceTree = ""; + }; + 23226F841D9C1EC6006A6769 /* Services */ = { + isa = PBXGroup; + children = ( + 23226F861D9C1ECF006A6769 /* NewsService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 23226F851D9C1EC6006A6769 /* Views */ = { + isa = PBXGroup; + children = ( + 23226F8A1D9C22B1006A6769 /* NewsView.swift */, + ); + path = Views; + sourceTree = ""; + }; 23E13E331D96896300204C82 /* Helpers */ = { isa = PBXGroup; children = ( @@ -346,6 +386,7 @@ 23E13E7B1D96899000204C82 /* Examples */ = { isa = PBXGroup; children = ( + 23226F811D9C1E9D006A6769 /* News */, 23E13E7C1D96899000204C82 /* CoreData */, 23E13E831D96899000204C82 /* Realm */, ); @@ -730,10 +771,13 @@ 23E13E931D96899000204C82 /* CoreDataBasicView.swift in Sources */, 23E13E941D96899000204C82 /* BasicObject.swift in Sources */, 23E13E9A1D96899000204C82 /* Random.swift in Sources */, + 23226F8B1D9C22B1006A6769 /* NewsView.swift in Sources */, 23E13E921D96899000204C82 /* AppDelegate.swift in Sources */, 23E13E9B1D96899000204C82 /* ViewController.swift in Sources */, 23E13E981D96899000204C82 /* RealmBasicObject.swift in Sources */, + 23226F871D9C1ECF006A6769 /* NewsService.swift in Sources */, 23E13E901D96899000204C82 /* Basic.xcdatamodeld in Sources */, + 23226F891D9C1F1A006A6769 /* RealmNew.swift in Sources */, 23E13E951D96899000204C82 /* CoreDataBasicEntity.swift in Sources */, 23E13E971D96899000204C82 /* RealmBasicEntity.swift in Sources */, 23E13E961D96899000204C82 /* RealmBasicView.swift in Sources */, diff --git a/Example/SugarRecord/Source/Examples/News/Models/RealmNew.swift b/Example/SugarRecord/Source/Examples/News/Models/RealmNew.swift new file mode 100644 index 0000000..b9c9fbc --- /dev/null +++ b/Example/SugarRecord/Source/Examples/News/Models/RealmNew.swift @@ -0,0 +1,10 @@ +import Foundation +import RealmSwift + +class RealmNew: Object { + + // MARK: - Attributes + + dynamic var title: String = "" + +} diff --git a/Example/SugarRecord/Source/Examples/News/Services/NewsService.swift b/Example/SugarRecord/Source/Examples/News/Services/NewsService.swift new file mode 100644 index 0000000..586fe4c --- /dev/null +++ b/Example/SugarRecord/Source/Examples/News/Services/NewsService.swift @@ -0,0 +1,54 @@ +import Foundation +import SugarRecord +import RealmSwift + +class NewsService { + + // MARK: - Attributes + + private let session: URLSession + private let apiKey: String + private let storage: Storage + + // MARK: - Init + + init(session: URLSession, storage: Storage, apiKey: String) { + self.session = session + self.storage = storage + self.apiKey = apiKey + } + + convenience init(storage: Storage) { + self.init(session: URLSession.shared, storage: storage, apiKey: "c7a6b4e8e221414b88a5883a98bbfbf9") + } + + // MARK: - Sync + + internal func sync(completion: ((Swift.Error?) -> Void)? = nil) { + let url = URL(string: "https://newsapi.org/v1/articles?source=the-next-web&sortBy=latest&apiKey=\(self.apiKey)")! + self.session.dataTask(with: url) { [weak self] (data, _, error) in + self?.save(data: data!) + completion?(error) + }.resume() + } + + // MARK: - Private + + private func save(data: Data) { + guard let dict = try! JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return } + guard let articles = dict["articles"] as? [[String: Any]] else { return } + DispatchQueue.main.async { + _ = try? self.storage.operation { (context, save) -> Void in + let existing = try context.fetch(FetchRequest()) + try context.remove(existing) + for article in articles { + guard let title = article["title"] as? String else { return } + let new: RealmNew = try context.create() + new.title = title + } + save() + } + } + } + +} diff --git a/Example/SugarRecord/Source/Examples/News/Views/NewsView.swift b/Example/SugarRecord/Source/Examples/News/Views/NewsView.swift new file mode 100644 index 0000000..ec3049c --- /dev/null +++ b/Example/SugarRecord/Source/Examples/News/Views/NewsView.swift @@ -0,0 +1,100 @@ +import Foundation +import UIKit +import SugarRecord +import RealmSwift + +class NewsView: UIViewController, UITableViewDelegate, UITableViewDataSource { + + // MARK: - Attributes + lazy var db: RealmDefaultStorage = { + var configuration = Realm.Configuration() + configuration.fileURL = URL(fileURLWithPath: databasePath("realm-news")) + let _storage = RealmDefaultStorage(configuration: configuration) + return _storage + }() + lazy var service: NewsService = { + return NewsService(storage: self.db) + }() + lazy var tableView: UITableView = { + let _tableView = UITableView(frame: CGRect.zero, style: UITableViewStyle.plain) + _tableView.translatesAutoresizingMaskIntoConstraints = false + _tableView.delegate = self + _tableView.dataSource = self + _tableView.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "default-cell") + return _tableView + }() + var entities: [String] = [] { + didSet { + self.tableView.reloadData() + } + } + + // MARK: - Init + + init() { + super.init(nibName: nil, bundle: nil) + self.title = "News" + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + // MARK: - Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setup() + self.service.sync() + let request = FetchRequest() + self.db.observable(request).observe { [weak self] (change) in + switch change { + case .initial(let values): + self?.entities = values.map({$0.title}) + case .update(_, _, _): + do { + self?.entities = try self?.db.fetch(request).map({$0.title}) ?? [] + } catch {} + default: break + } + } + } + + + // MARK: - Private + + fileprivate func setup() { + setupView() + setupTableView() + } + + fileprivate func setupView() { + self.view.backgroundColor = UIColor.white + } + + fileprivate func setupTableView() { + self.view.addSubview(tableView) + self.tableView.snp.makeConstraints { (make) -> Void in + make.edges.equalTo(self.view) + } + } + + + // MARK: - UITableViewDataSource / UITableViewDelegate + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.entities.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "default-cell")! + cell.textLabel?.text = "\(entities[(indexPath as NSIndexPath).row])" + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } + +} diff --git a/Example/SugarRecord/Source/Main/ViewController.swift b/Example/SugarRecord/Source/Main/ViewController.swift index d502bbc..ccd2401 100644 --- a/Example/SugarRecord/Source/Main/ViewController.swift +++ b/Example/SugarRecord/Source/Main/ViewController.swift @@ -56,7 +56,7 @@ class ViewController: UIViewController, UITableViewDataSource, UITableViewDelega // MARK: - UITableViewDataSource / UITableViewDelegate func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 2 + return 3 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -66,6 +66,8 @@ class ViewController: UIViewController, UITableViewDataSource, UITableViewDelega cell.textLabel?.text = "CoreData Basic" case 1: cell.textLabel?.text = "Realm Basic" + case 2: + cell.textLabel?.text = "News from API" default: cell.textLabel?.text = "" } @@ -80,6 +82,8 @@ class ViewController: UIViewController, UITableViewDataSource, UITableViewDelega self.navigationController?.pushViewController(CoreDataBasicView(), animated: true) case 1: self.navigationController?.pushViewController(RealmBasicView(), animated: true) + case 2: + self.navigationController?.pushViewController(NewsView(), animated: true) default: break }