diff --git a/AppNet/AppDelegate.swift b/AppNet/AppDelegate.swift
new file mode 100644
index 00000000..e49a2849
--- /dev/null
+++ b/AppNet/AppDelegate.swift
@@ -0,0 +1,33 @@
+import UIKit
+import CoreData
+import DATAStack
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+ lazy var dataStack: DATAStack = DATAStack()
+
+ func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
+ application.setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
+
+ let appearance = UINavigationBar.appearance()
+ appearance.barTintColor = UIColor(red:0.83, green:0.43, blue:0.36, alpha:1)
+ appearance.titleTextAttributes = [NSFontAttributeName : UIFont(name: "AvenirNext-DemiBold", size: 20)!,
+ NSForegroundColorAttributeName : UIColor.whiteColor()]
+
+ window = UIWindow(frame: UIScreen.mainScreen().bounds)
+
+ let initialViewController = ViewController(dataStack: dataStack)
+
+ window?.rootViewController = UINavigationController(rootViewController: initialViewController)
+ window?.makeKeyAndVisible()
+
+ return true
+ }
+
+ func applicationWillTerminate(application: UIApplication) {
+ dataStack.persistWithCompletion(nil)
+ }
+}
+
diff --git a/AppNet/AppNet.xcdatamodeld/.xccurrentversion b/AppNet/AppNet.xcdatamodeld/.xccurrentversion
new file mode 100644
index 00000000..7e91ee51
--- /dev/null
+++ b/AppNet/AppNet.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+
+
+
+
+ _XCCurrentVersionName
+ AppNet.xcdatamodel
+
+
diff --git a/AppNet/AppNet.xcdatamodeld/AppNet.xcdatamodel/contents b/AppNet/AppNet.xcdatamodeld/AppNet.xcdatamodel/contents
new file mode 100644
index 00000000..32de8051
--- /dev/null
+++ b/AppNet/AppNet.xcdatamodeld/AppNet.xcdatamodel/contents
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AppNet/Assets.xcassets/AppIcon.appiconset/Contents.json b/AppNet/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..118c98f7
--- /dev/null
+++ b/AppNet/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/AppNet/Base.lproj/LaunchScreen.storyboard b/AppNet/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..2e721e18
--- /dev/null
+++ b/AppNet/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AppNet/Data.swift b/AppNet/Data.swift
new file mode 100644
index 00000000..573a9d89
--- /dev/null
+++ b/AppNet/Data.swift
@@ -0,0 +1,13 @@
+import Foundation
+import CoreData
+
+@objc(Data)
+
+class Data: NSManagedObject {
+
+ @NSManaged var text: String
+ @NSManaged var remoteID: String
+ @NSManaged var createdAt: NSTimeInterval
+ @NSManaged var user: User
+
+}
diff --git a/AppNet/Images/app.png b/AppNet/Images/app.png
new file mode 100644
index 00000000..2560d881
Binary files /dev/null and b/AppNet/Images/app.png differ
diff --git a/AppNet/Images/model.png b/AppNet/Images/model.png
new file mode 100644
index 00000000..6ba2ebd6
Binary files /dev/null and b/AppNet/Images/model.png differ
diff --git a/AppNet/Info.plist b/AppNet/Info.plist
new file mode 100644
index 00000000..d39a4daf
--- /dev/null
+++ b/AppNet/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/AppNet/Networking.swift b/AppNet/Networking.swift
new file mode 100644
index 00000000..7946c480
--- /dev/null
+++ b/AppNet/Networking.swift
@@ -0,0 +1,34 @@
+import UIKit
+import DATAStack
+import Sync
+
+class Networking {
+ let AppNetURL = "https://api.app.net/posts/stream/global"
+ let dataStack: DATAStack
+
+ required init(dataStack: DATAStack) {
+ self.dataStack = dataStack
+ }
+
+ func fetchItems(completion: (NSError?) -> Void) {
+
+ if let urlAppNet = NSURL(string: AppNetURL) {
+ let request = NSURLRequest(URL: urlAppNet)
+ let operationQueue = NSOperationQueue()
+
+ NSURLConnection.sendAsynchronousRequest(request, queue: operationQueue) { [unowned self] _, data, error in
+ if let data = data, json = (try? NSJSONSerialization.JSONObjectWithData(data,
+ options: NSJSONReadingOptions.MutableContainers)) as? Dictionary {
+ Sync.changes(json["data"] as! Array,
+ inEntityNamed: "Data",
+ dataStack: self.dataStack,
+ completion: { error in
+ completion(error)
+ })
+ } else {
+ completion(error)
+ }
+ }
+ }
+ }
+}
diff --git a/AppNet/README.md b/AppNet/README.md
new file mode 100644
index 00000000..167da682
--- /dev/null
+++ b/AppNet/README.md
@@ -0,0 +1,51 @@
+## App
+
+[Reference (Swift)](https://github.com/hyperoslo/Sync/tree/master/Examples/AppNet)
+
+![Model](https://raw.githubusercontent.com/hyperoslo/Sync/master/Examples/AppNet/Images/app.png)
+
+## JSON
+
+[Reference](https://api.app.net/posts/stream/global)
+
+```json
+{
+ "meta":{
+ "min_id":"57030525",
+ "code":200,
+ "max_id":"57030547",
+ "more":true
+ },
+ "data":[
+ {
+ "created_at":"2015-04-06T15:07:06Z",
+ "text":"Hello World!",
+ "id":"57030547",
+ "user":{
+ "username":"albarjeel1",
+ "created_at":"2015-03-28T13:01:31Z",
+ "id":"347326"
+ }
+ }
+ ]
+}
+```
+
+## Model
+
+![Model](https://raw.githubusercontent.com/hyperoslo/Sync/master/Examples/AppNet/Images/model.png)
+
+## Sync
+
+[Reference](https://github.com/hyperoslo/Sync/blob/master/Examples/AppNet/AppNet/Networking.swift#L32-L34)
+
+```swift
+Sync.changes(
+ json["data"] as Array,
+ inEntityNamed: "Data",
+ dataStack: self.dataStack,
+ completion: { error in
+ completion()
+ }
+)
+```
diff --git a/AppNet/User.swift b/AppNet/User.swift
new file mode 100644
index 00000000..20c175af
--- /dev/null
+++ b/AppNet/User.swift
@@ -0,0 +1,13 @@
+import Foundation
+import CoreData
+
+@objc(User)
+
+class User: NSManagedObject {
+
+ @NSManaged var name: String
+ @NSManaged var remoteID: String
+ @NSManaged var username: String
+ @NSManaged var data: NSSet
+
+}
diff --git a/AppNet/ViewController.swift b/AppNet/ViewController.swift
new file mode 100644
index 00000000..f89d9112
--- /dev/null
+++ b/AppNet/ViewController.swift
@@ -0,0 +1,77 @@
+import UIKit
+import DATAStack
+import CoreData
+
+class ViewController: UITableViewController {
+ let CellIdentifier = "CellID"
+
+ let dataStack: DATAStack
+ lazy var networking: Networking = { [unowned self] in Networking(dataStack: self.dataStack) }()
+ var items = [Data]()
+
+ // MARK: Initializers
+
+ required init(dataStack: DATAStack) {
+ self.dataStack = dataStack
+ super.init(nibName: nil, bundle: nil);
+ }
+
+ required init!(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // MARK: View Lifecycle
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = "AppNet"
+ tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
+
+ setUpRefreshControl()
+ fetchCurrentObjects()
+ fetchNewData()
+ }
+
+ func setUpRefreshControl() {
+ refreshControl = UIRefreshControl()
+ refreshControl?.addTarget(self, action: "fetchNewData", forControlEvents: .ValueChanged)
+ }
+
+ // MARK: Networking methods
+
+ func fetchNewData() {
+ networking.fetchItems { _ in
+ self.fetchCurrentObjects()
+
+ self.refreshControl?.endRefreshing()
+ }
+ }
+
+ // MARK: Model methods
+
+ func fetchCurrentObjects() {
+ let request = NSFetchRequest(entityName: "Data")
+ request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
+ items = (try! dataStack.mainContext.executeFetchRequest(request)) as! [Data]
+
+ tableView.reloadData()
+ }
+
+ override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return items.count
+ }
+
+ override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
+ let cell = self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier)
+ let data = self.items[indexPath.row]
+ cell?.textLabel?.text = data.text
+
+ // Workaround: The proper value of `numberOfLines` should be 0
+ // but there's a weird bug that causes UITableView to go crazy
+ cell?.textLabel?.numberOfLines = 1
+ cell?.detailTextLabel?.text = data.user.username
+
+ return cell!
+ }
+}
diff --git a/Demo.xcodeproj/project.pbxproj b/Demo.xcodeproj/project.pbxproj
index 179eb9b1..bbe6a983 100644
--- a/Demo.xcodeproj/project.pbxproj
+++ b/Demo.xcodeproj/project.pbxproj
@@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
+ 1405FA4E1BDD654600F10DFA /* _DNComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 1405FA471BDD654600F10DFA /* _DNComment.m */; };
+ 1405FA4F1BDD654600F10DFA /* _DNStory.m in Sources */ = {isa = PBXBuildFile; fileRef = 1405FA491BDD654600F10DFA /* _DNStory.m */; };
+ 1405FA501BDD654600F10DFA /* DNComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 1405FA4B1BDD654600F10DFA /* DNComment.m */; };
+ 1405FA511BDD654600F10DFA /* DNStory.m in Sources */ = {isa = PBXBuildFile; fileRef = 1405FA4D1BDD654600F10DFA /* DNStory.m */; };
141894031BDD62E900EE52CE /* NSArray+Sync.m in Sources */ = {isa = PBXBuildFile; fileRef = 141893FC1BDD62E900EE52CE /* NSArray+Sync.m */; };
141894041BDD62E900EE52CE /* NSEntityDescription+Sync.m in Sources */ = {isa = PBXBuildFile; fileRef = 141893FE1BDD62E900EE52CE /* NSEntityDescription+Sync.m */; };
141894051BDD62E900EE52CE /* NSManagedObject+Sync.m in Sources */ = {isa = PBXBuildFile; fileRef = 141894001BDD62E900EE52CE /* NSManagedObject+Sync.m */; };
@@ -51,10 +55,38 @@
141894641BDD62F600EE52CE /* Unique.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1418943B1BDD62F600EE52CE /* Unique.xcdatamodeld */; };
141894651BDD62F600EE52CE /* NSArray+Sync_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1418943D1BDD62F600EE52CE /* NSArray+Sync_Tests.m */; };
141894661BDD62F600EE52CE /* SyncTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1418943E1BDD62F600EE52CE /* SyncTests.m */; };
+ 1418946F1BDD634F00EE52CE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1418946E1BDD634F00EE52CE /* main.m */; };
+ 141894721BDD634F00EE52CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 141894711BDD634F00EE52CE /* AppDelegate.m */; };
+ 141894751BDD634F00EE52CE /* DesignerNews.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 141894731BDD634F00EE52CE /* DesignerNews.xcdatamodeld */; };
+ 141894771BDD634F00EE52CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 141894761BDD634F00EE52CE /* Assets.xcassets */; };
+ 1418947A1BDD634F00EE52CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 141894781BDD634F00EE52CE /* LaunchScreen.storyboard */; };
+ 141894861BDD635800EE52CE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141894851BDD635800EE52CE /* AppDelegate.swift */; };
+ 141894891BDD635800EE52CE /* AppNet.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 141894871BDD635800EE52CE /* AppNet.xcdatamodeld */; };
+ 1418948B1BDD635800EE52CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1418948A1BDD635800EE52CE /* Assets.xcassets */; };
+ 1418948E1BDD635800EE52CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1418948C1BDD635800EE52CE /* LaunchScreen.storyboard */; };
+ 1418949D1BDD643100EE52CE /* APIClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 141894941BDD643100EE52CE /* APIClient.m */; };
+ 1418949E1BDD643100EE52CE /* StoriesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 141894961BDD643100EE52CE /* StoriesViewController.m */; };
+ 1418949F1BDD643100EE52CE /* StoryTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 141894981BDD643100EE52CE /* StoryTableViewCell.m */; };
+ 141894A01BDD643100EE52CE /* StoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1418949A1BDD643100EE52CE /* StoryViewController.m */; };
+ 141894A11BDD643100EE52CE /* UIFont+DNStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 1418949C1BDD643100EE52CE /* UIFont+DNStyle.m */; };
+ 141894A61BDD64AF00EE52CE /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141894A21BDD64AF00EE52CE /* Data.swift */; };
+ 141894A71BDD64AF00EE52CE /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141894A31BDD64AF00EE52CE /* Networking.swift */; };
+ 141894A81BDD64AF00EE52CE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141894A41BDD64AF00EE52CE /* User.swift */; };
+ 141894A91BDD64AF00EE52CE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 141894A51BDD64AF00EE52CE /* ViewController.swift */; };
8C1190CE4B3821813B6A3421 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E482492041434DB374B1948 /* Pods.framework */; };
+ BD2A2CB4A6B4EC694D5E358A /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E482492041434DB374B1948 /* Pods.framework */; };
+ E9FABD68F0CA454B8D54104E /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E482492041434DB374B1948 /* Pods.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 1405FA461BDD654600F10DFA /* _DNComment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _DNComment.h; sourceTree = ""; };
+ 1405FA471BDD654600F10DFA /* _DNComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _DNComment.m; sourceTree = ""; };
+ 1405FA481BDD654600F10DFA /* _DNStory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _DNStory.h; sourceTree = ""; };
+ 1405FA491BDD654600F10DFA /* _DNStory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _DNStory.m; sourceTree = ""; };
+ 1405FA4A1BDD654600F10DFA /* DNComment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNComment.h; sourceTree = ""; };
+ 1405FA4B1BDD654600F10DFA /* DNComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNComment.m; sourceTree = ""; };
+ 1405FA4C1BDD654600F10DFA /* DNStory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNStory.h; sourceTree = ""; };
+ 1405FA4D1BDD654600F10DFA /* DNStory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DNStory.m; sourceTree = ""; };
141893FB1BDD62E900EE52CE /* NSArray+Sync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Sync.h"; sourceTree = ""; };
141893FC1BDD62E900EE52CE /* NSArray+Sync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Sync.m"; sourceTree = ""; };
141893FD1BDD62E900EE52CE /* NSEntityDescription+Sync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSEntityDescription+Sync.h"; sourceTree = ""; };
@@ -104,6 +136,34 @@
1418943C1BDD62F600EE52CE /* Unique.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Unique.xcdatamodel; sourceTree = ""; };
1418943D1BDD62F600EE52CE /* NSArray+Sync_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Sync_Tests.m"; sourceTree = ""; };
1418943E1BDD62F600EE52CE /* SyncTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SyncTests.m; sourceTree = ""; };
+ 1418946B1BDD634F00EE52CE /* DesignerNews.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DesignerNews.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 1418946E1BDD634F00EE52CE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ 141894701BDD634F00EE52CE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ 141894711BDD634F00EE52CE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ 141894741BDD634F00EE52CE /* DesignerNews.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DesignerNews.xcdatamodel; sourceTree = ""; };
+ 141894761BDD634F00EE52CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 141894791BDD634F00EE52CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 1418947B1BDD634F00EE52CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 141894831BDD635800EE52CE /* AppNet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppNet.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 141894851BDD635800EE52CE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 141894881BDD635800EE52CE /* AppNet.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AppNet.xcdatamodel; sourceTree = ""; };
+ 1418948A1BDD635800EE52CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 1418948D1BDD635800EE52CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 1418948F1BDD635800EE52CE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 141894931BDD643100EE52CE /* APIClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APIClient.h; sourceTree = ""; };
+ 141894941BDD643100EE52CE /* APIClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = APIClient.m; sourceTree = ""; };
+ 141894951BDD643100EE52CE /* StoriesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoriesViewController.h; sourceTree = ""; };
+ 141894961BDD643100EE52CE /* StoriesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoriesViewController.m; sourceTree = ""; };
+ 141894971BDD643100EE52CE /* StoryTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryTableViewCell.h; sourceTree = ""; };
+ 141894981BDD643100EE52CE /* StoryTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryTableViewCell.m; sourceTree = ""; };
+ 141894991BDD643100EE52CE /* StoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryViewController.h; sourceTree = ""; };
+ 1418949A1BDD643100EE52CE /* StoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryViewController.m; sourceTree = ""; };
+ 1418949B1BDD643100EE52CE /* UIFont+DNStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIFont+DNStyle.h"; sourceTree = ""; };
+ 1418949C1BDD643100EE52CE /* UIFont+DNStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIFont+DNStyle.m"; sourceTree = ""; };
+ 141894A21BDD64AF00EE52CE /* Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; };
+ 141894A31BDD64AF00EE52CE /* Networking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = ""; };
+ 141894A41BDD64AF00EE52CE /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
+ 141894A51BDD64AF00EE52CE /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
146D72AC1AB782920058798C /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
146D72B11AB782920058798C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
14C0AF7F1BD6D4230009ECBE /* .slather.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .slather.yml; sourceTree = ""; };
@@ -117,6 +177,22 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 141894681BDD634F00EE52CE /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ BD2A2CB4A6B4EC694D5E358A /* Pods.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 141894801BDD635800EE52CE /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ E9FABD68F0CA454B8D54104E /* Pods.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
146D72A91AB782920058798C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -128,6 +204,21 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 1405FA451BDD654600F10DFA /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ 1405FA461BDD654600F10DFA /* _DNComment.h */,
+ 1405FA471BDD654600F10DFA /* _DNComment.m */,
+ 1405FA481BDD654600F10DFA /* _DNStory.h */,
+ 1405FA491BDD654600F10DFA /* _DNStory.m */,
+ 1405FA4A1BDD654600F10DFA /* DNComment.h */,
+ 1405FA4B1BDD654600F10DFA /* DNComment.m */,
+ 1405FA4C1BDD654600F10DFA /* DNStory.h */,
+ 1405FA4D1BDD654600F10DFA /* DNStory.m */,
+ );
+ path = Model;
+ sourceTree = "";
+ };
141894071BDD62F600EE52CE /* Helpers */ = {
isa = PBXGroup;
children = (
@@ -188,12 +279,63 @@
path = Models;
sourceTree = "";
};
+ 1418946C1BDD634F00EE52CE /* DesignerNews */ = {
+ isa = PBXGroup;
+ children = (
+ 1405FA451BDD654600F10DFA /* Model */,
+ 141894701BDD634F00EE52CE /* AppDelegate.h */,
+ 141894711BDD634F00EE52CE /* AppDelegate.m */,
+ 141894931BDD643100EE52CE /* APIClient.h */,
+ 141894941BDD643100EE52CE /* APIClient.m */,
+ 141894951BDD643100EE52CE /* StoriesViewController.h */,
+ 141894961BDD643100EE52CE /* StoriesViewController.m */,
+ 141894971BDD643100EE52CE /* StoryTableViewCell.h */,
+ 141894981BDD643100EE52CE /* StoryTableViewCell.m */,
+ 141894991BDD643100EE52CE /* StoryViewController.h */,
+ 1418949A1BDD643100EE52CE /* StoryViewController.m */,
+ 1418949B1BDD643100EE52CE /* UIFont+DNStyle.h */,
+ 1418949C1BDD643100EE52CE /* UIFont+DNStyle.m */,
+ 141894761BDD634F00EE52CE /* Assets.xcassets */,
+ 141894781BDD634F00EE52CE /* LaunchScreen.storyboard */,
+ 1418947B1BDD634F00EE52CE /* Info.plist */,
+ 141894731BDD634F00EE52CE /* DesignerNews.xcdatamodeld */,
+ 1418946D1BDD634F00EE52CE /* Supporting Files */,
+ );
+ path = DesignerNews;
+ sourceTree = "";
+ };
+ 1418946D1BDD634F00EE52CE /* Supporting Files */ = {
+ isa = PBXGroup;
+ children = (
+ 1418946E1BDD634F00EE52CE /* main.m */,
+ );
+ name = "Supporting Files";
+ sourceTree = "";
+ };
+ 141894841BDD635800EE52CE /* AppNet */ = {
+ isa = PBXGroup;
+ children = (
+ 141894851BDD635800EE52CE /* AppDelegate.swift */,
+ 141894A21BDD64AF00EE52CE /* Data.swift */,
+ 141894A31BDD64AF00EE52CE /* Networking.swift */,
+ 141894A41BDD64AF00EE52CE /* User.swift */,
+ 141894A51BDD64AF00EE52CE /* ViewController.swift */,
+ 1418948A1BDD635800EE52CE /* Assets.xcassets */,
+ 1418948C1BDD635800EE52CE /* LaunchScreen.storyboard */,
+ 1418948F1BDD635800EE52CE /* Info.plist */,
+ 141894871BDD635800EE52CE /* AppNet.xcdatamodeld */,
+ );
+ path = AppNet;
+ sourceTree = "";
+ };
146D728A1AB782920058798C = {
isa = PBXGroup;
children = (
14C136501AB7849300B7B07A /* Metadata */,
14C0AF841BD6D4370009ECBE /* Source */,
146D72AF1AB782920058798C /* Tests */,
+ 1418946C1BDD634F00EE52CE /* DesignerNews */,
+ 141894841BDD635800EE52CE /* AppNet */,
146D72941AB782920058798C /* Products */,
7022AAD62B9688833050CCBB /* Pods */,
515545DD1958EE5C50CA007A /* Frameworks */,
@@ -206,6 +348,8 @@
isa = PBXGroup;
children = (
146D72AC1AB782920058798C /* Tests.xctest */,
+ 1418946B1BDD634F00EE52CE /* DesignerNews.app */,
+ 141894831BDD635800EE52CE /* AppNet.app */,
);
name = Products;
sourceTree = "";
@@ -278,6 +422,46 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 1418946A1BDD634F00EE52CE /* DesignerNews */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 1418947C1BDD634F00EE52CE /* Build configuration list for PBXNativeTarget "DesignerNews" */;
+ buildPhases = (
+ 48F77971EECF0ECCBF886AE4 /* Check Pods Manifest.lock */,
+ 141894671BDD634F00EE52CE /* Sources */,
+ 141894681BDD634F00EE52CE /* Frameworks */,
+ 141894691BDD634F00EE52CE /* Resources */,
+ E7D43265083ECA100BF4311F /* Embed Pods Frameworks */,
+ C6865CFA9362B8B04206AF34 /* Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = DesignerNews;
+ productName = DesignerNews;
+ productReference = 1418946B1BDD634F00EE52CE /* DesignerNews.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 141894821BDD635800EE52CE /* AppNet */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 141894901BDD635800EE52CE /* Build configuration list for PBXNativeTarget "AppNet" */;
+ buildPhases = (
+ 21BD1AF33E7EEE50DBEFAA89 /* Check Pods Manifest.lock */,
+ 1418947F1BDD635800EE52CE /* Sources */,
+ 141894801BDD635800EE52CE /* Frameworks */,
+ 141894811BDD635800EE52CE /* Resources */,
+ 42437B3C05667742CA7C962F /* Embed Pods Frameworks */,
+ BD62ED40A7A9137251E429C9 /* Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = AppNet;
+ productName = AppNet;
+ productReference = 141894831BDD635800EE52CE /* AppNet.app */;
+ productType = "com.apple.product-type.application";
+ };
146D72AB1AB782920058798C /* Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 146D72B91AB782920058798C /* Build configuration list for PBXNativeTarget "Tests" */;
@@ -309,6 +493,12 @@
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "";
TargetAttributes = {
+ 1418946A1BDD634F00EE52CE = {
+ CreatedOnToolsVersion = 7.1;
+ };
+ 141894821BDD635800EE52CE = {
+ CreatedOnToolsVersion = 7.1;
+ };
146D72AB1AB782920058798C = {
CreatedOnToolsVersion = 6.2;
};
@@ -328,11 +518,31 @@
projectRoot = "";
targets = (
146D72AB1AB782920058798C /* Tests */,
+ 1418946A1BDD634F00EE52CE /* DesignerNews */,
+ 141894821BDD635800EE52CE /* AppNet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
+ 141894691BDD634F00EE52CE /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 141894771BDD634F00EE52CE /* Assets.xcassets in Resources */,
+ 1418947A1BDD634F00EE52CE /* LaunchScreen.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 141894811BDD635800EE52CE /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1418948B1BDD635800EE52CE /* Assets.xcassets in Resources */,
+ 1418948E1BDD635800EE52CE /* LaunchScreen.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
146D72AA1AB782920058798C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -368,6 +578,21 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 21BD1AF33E7EEE50DBEFAA89 /* Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
2FEA8BC47318CB82011879D0 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -383,6 +608,36 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
+ 42437B3C05667742CA7C962F /* Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 48F77971EECF0ECCBF886AE4 /* Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Check Pods Manifest.lock";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
+ showEnvVarsInLog = 0;
+ };
9E64283FD27F0AFF04836E8F /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -413,9 +668,86 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
+ BD62ED40A7A9137251E429C9 /* Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ C6865CFA9362B8B04206AF34 /* Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Copy Pods Resources";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ E7D43265083ECA100BF4311F /* Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Embed Pods Frameworks";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 141894671BDD634F00EE52CE /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 1418949E1BDD643100EE52CE /* StoriesViewController.m in Sources */,
+ 1405FA501BDD654600F10DFA /* DNComment.m in Sources */,
+ 1405FA4F1BDD654600F10DFA /* _DNStory.m in Sources */,
+ 1405FA511BDD654600F10DFA /* DNStory.m in Sources */,
+ 141894A01BDD643100EE52CE /* StoryViewController.m in Sources */,
+ 1418949F1BDD643100EE52CE /* StoryTableViewCell.m in Sources */,
+ 141894721BDD634F00EE52CE /* AppDelegate.m in Sources */,
+ 1405FA4E1BDD654600F10DFA /* _DNComment.m in Sources */,
+ 141894751BDD634F00EE52CE /* DesignerNews.xcdatamodeld in Sources */,
+ 1418946F1BDD634F00EE52CE /* main.m in Sources */,
+ 141894A11BDD643100EE52CE /* UIFont+DNStyle.m in Sources */,
+ 1418949D1BDD643100EE52CE /* APIClient.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 1418947F1BDD635800EE52CE /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 141894A91BDD64AF00EE52CE /* ViewController.swift in Sources */,
+ 141894861BDD635800EE52CE /* AppDelegate.swift in Sources */,
+ 141894A61BDD64AF00EE52CE /* Data.swift in Sources */,
+ 141894891BDD635800EE52CE /* AppNet.xcdatamodeld in Sources */,
+ 141894A81BDD64AF00EE52CE /* User.swift in Sources */,
+ 141894A71BDD64AF00EE52CE /* Networking.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
146D72A81AB782920058798C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -444,7 +776,87 @@
};
/* End PBXSourcesBuildPhase section */
+/* Begin PBXVariantGroup section */
+ 141894781BDD634F00EE52CE /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 141894791BDD634F00EE52CE /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+ 1418948C1BDD635800EE52CE /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 1418948D1BDD635800EE52CE /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
/* Begin XCBuildConfiguration section */
+ 1418947D1BDD634F00EE52CE /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = C13281341A77530FC5AE626A /* Pods.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = DesignerNews/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = demo.DesignerNews;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 1418947E1BDD634F00EE52CE /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = E80F7331DFE41909E28F2702 /* Pods.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = DesignerNews/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = demo.DesignerNews;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+ 141894911BDD635800EE52CE /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = C13281341A77530FC5AE626A /* Pods.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = AppNet/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = demo.AppNet;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 141894921BDD635800EE52CE /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = E80F7331DFE41909E28F2702 /* Pods.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ GCC_NO_COMMON_BLOCKS = YES;
+ INFOPLIST_FILE = AppNet/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.1;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = demo.AppNet;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
146D72B41AB782920058798C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -556,6 +968,24 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 1418947C1BDD634F00EE52CE /* Build configuration list for PBXNativeTarget "DesignerNews" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 1418947D1BDD634F00EE52CE /* Debug */,
+ 1418947E1BDD634F00EE52CE /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 141894901BDD635800EE52CE /* Build configuration list for PBXNativeTarget "AppNet" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 141894911BDD635800EE52CE /* Debug */,
+ 141894921BDD635800EE52CE /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
146D728E1AB782920058798C /* Build configuration list for PBXProject "Demo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -697,6 +1127,26 @@
sourceTree = "";
versionGroupType = wrapper.xcdatamodel;
};
+ 141894731BDD634F00EE52CE /* DesignerNews.xcdatamodeld */ = {
+ isa = XCVersionGroup;
+ children = (
+ 141894741BDD634F00EE52CE /* DesignerNews.xcdatamodel */,
+ );
+ currentVersion = 141894741BDD634F00EE52CE /* DesignerNews.xcdatamodel */;
+ path = DesignerNews.xcdatamodeld;
+ sourceTree = "";
+ versionGroupType = wrapper.xcdatamodel;
+ };
+ 141894871BDD635800EE52CE /* AppNet.xcdatamodeld */ = {
+ isa = XCVersionGroup;
+ children = (
+ 141894881BDD635800EE52CE /* AppNet.xcdatamodel */,
+ );
+ currentVersion = 141894881BDD635800EE52CE /* AppNet.xcdatamodel */;
+ path = AppNet.xcdatamodeld;
+ sourceTree = "";
+ versionGroupType = wrapper.xcdatamodel;
+ };
/* End XCVersionGroup section */
};
rootObject = 146D728B1AB782920058798C /* Project object */;
diff --git a/DesignerNews/APIClient.h b/DesignerNews/APIClient.h
new file mode 100644
index 00000000..59857c3f
--- /dev/null
+++ b/DesignerNews/APIClient.h
@@ -0,0 +1,8 @@
+@import Foundation;
+@import DATAStack;
+
+@interface APIClient : NSObject
+
+- (void)fetchStoryUsingDataStack:(DATAStack *)dataStack;
+
+@end
diff --git a/DesignerNews/APIClient.m b/DesignerNews/APIClient.m
new file mode 100644
index 00000000..265ae5fa
--- /dev/null
+++ b/DesignerNews/APIClient.m
@@ -0,0 +1,46 @@
+#import "APIClient.h"
+#import "Sync.h"
+@import UIKit;
+
+static NSString * const HYPBaseURL = @"https://www.designernews.co/?format=json";
+
+@interface APIClient ()
+
+@property (nonatomic, weak) DATAStack *dataStack;
+
+@end
+
+@implementation APIClient
+
+- (void)fetchStoryUsingDataStack:(DATAStack *)dataStack
+{
+ [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
+
+ NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:HYPBaseURL]];
+ NSOperationQueue *queue = [NSOperationQueue new];
+ [NSURLConnection sendAsynchronousRequest:urlRequest
+ queue:queue
+ completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
+ if (connectionError) {
+ NSLog(@"There was an error: %@", connectionError);
+ } else {
+ NSError *serializationError = nil;
+ NSJSONSerialization *JSON = [NSJSONSerialization JSONObjectWithData:data
+ options:NSJSONReadingMutableContainers
+ error:&serializationError];
+
+ if (serializationError) {
+ NSLog(@"Error serializing JSON: %@", serializationError);
+ } else {
+ [Sync changes:[JSON valueForKey:@"stories"]
+ inEntityNamed:@"Story"
+ dataStack:dataStack
+ completion:^(NSError *error) {
+ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
+ }];
+ }
+ }
+ }];
+}
+
+@end
diff --git a/DesignerNews/AppDelegate.h b/DesignerNews/AppDelegate.h
new file mode 100644
index 00000000..5928b246
--- /dev/null
+++ b/DesignerNews/AppDelegate.h
@@ -0,0 +1,9 @@
+@import UIKit;
+@import CoreData;
+
+@interface AppDelegate : UIResponder
+
+@property (nonatomic) UIWindow *window;
+
+@end
+
diff --git a/DesignerNews/AppDelegate.m b/DesignerNews/AppDelegate.m
new file mode 100644
index 00000000..6257ec30
--- /dev/null
+++ b/DesignerNews/AppDelegate.m
@@ -0,0 +1,53 @@
+#import "AppDelegate.h"
+
+#import "StoriesViewController.h"
+@import DATAStack;
+
+#import "UIFont+DNStyle.h"
+
+@interface AppDelegate ()
+
+@property (nonatomic) DATAStack *dataStack;
+
+@end
+
+@implementation AppDelegate
+
+#pragma mark - Getters
+
+- (DATAStack *)dataStack
+{
+ if (_dataStack) return _dataStack;
+
+ _dataStack = [[DATAStack alloc] initWithModelName:@"DesignerNews"];
+
+ return _dataStack;
+}
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
+
+ [UINavigationBar appearance].barTintColor = [UIColor colorWithRed:0.2 green:0.46 blue:0.84 alpha:1];
+
+ [UINavigationBar appearance].titleTextAttributes = @{NSForegroundColorAttributeName : [UIColor whiteColor],
+ NSFontAttributeName : [UIFont appTitleFont]};
+
+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+
+ StoriesViewController *mainController = [[StoriesViewController alloc] initWithDataStack:self.dataStack];
+ UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:mainController];
+ self.window.rootViewController = navigationController;
+ [self.window makeKeyAndVisible];
+
+ return YES;
+}
+
+#pragma mark - Core Data stack
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ [self.dataStack persistWithCompletion:nil];
+}
+
+@end
diff --git a/DesignerNews/Assets.xcassets/AppIcon.appiconset/Contents.json b/DesignerNews/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..118c98f7
--- /dev/null
+++ b/DesignerNews/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/DesignerNews/Base.lproj/LaunchScreen.storyboard b/DesignerNews/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 00000000..2e721e18
--- /dev/null
+++ b/DesignerNews/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DesignerNews/DesignerNews.xcdatamodeld/.xccurrentversion b/DesignerNews/DesignerNews.xcdatamodeld/.xccurrentversion
new file mode 100644
index 00000000..ccdb4c12
--- /dev/null
+++ b/DesignerNews/DesignerNews.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+
+
+
+
+ _XCCurrentVersionName
+ DesignerNews.xcdatamodel
+
+
diff --git a/DesignerNews/DesignerNews.xcdatamodeld/DesignerNews.xcdatamodel/contents b/DesignerNews/DesignerNews.xcdatamodeld/DesignerNews.xcdatamodel/contents
new file mode 100644
index 00000000..eb107e8c
--- /dev/null
+++ b/DesignerNews/DesignerNews.xcdatamodeld/DesignerNews.xcdatamodel/contents
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DesignerNews/Images/app.png b/DesignerNews/Images/app.png
new file mode 100644
index 00000000..c1da2a3c
Binary files /dev/null and b/DesignerNews/Images/app.png differ
diff --git a/DesignerNews/Images/model.png b/DesignerNews/Images/model.png
new file mode 100644
index 00000000..7b4ad477
Binary files /dev/null and b/DesignerNews/Images/model.png differ
diff --git a/DesignerNews/Info.plist b/DesignerNews/Info.plist
new file mode 100644
index 00000000..d39a4daf
--- /dev/null
+++ b/DesignerNews/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/DesignerNews/Model/DNComment.h b/DesignerNews/Model/DNComment.h
new file mode 100755
index 00000000..7ff5e4d2
--- /dev/null
+++ b/DesignerNews/Model/DNComment.h
@@ -0,0 +1,5 @@
+#import "_DNComment.h"
+
+@interface DNComment : _DNComment {}
+
+@end
diff --git a/DesignerNews/Model/DNComment.m b/DesignerNews/Model/DNComment.m
new file mode 100755
index 00000000..f666c1ab
--- /dev/null
+++ b/DesignerNews/Model/DNComment.m
@@ -0,0 +1,9 @@
+#import "DNComment.h"
+
+@interface DNComment ()
+
+@end
+
+@implementation DNComment
+
+@end
diff --git a/DesignerNews/Model/DNStory.h b/DesignerNews/Model/DNStory.h
new file mode 100755
index 00000000..fb854041
--- /dev/null
+++ b/DesignerNews/Model/DNStory.h
@@ -0,0 +1,5 @@
+#import "_DNStory.h"
+
+@interface DNStory : _DNStory {}
+
+@end
diff --git a/DesignerNews/Model/DNStory.m b/DesignerNews/Model/DNStory.m
new file mode 100755
index 00000000..2ecf980f
--- /dev/null
+++ b/DesignerNews/Model/DNStory.m
@@ -0,0 +1,9 @@
+#import "DNStory.h"
+
+@interface DNStory ()
+
+@end
+
+@implementation DNStory
+
+@end
diff --git a/DesignerNews/Model/_DNComment.h b/DesignerNews/Model/_DNComment.h
new file mode 100755
index 00000000..187f9312
--- /dev/null
+++ b/DesignerNews/Model/_DNComment.h
@@ -0,0 +1,98 @@
+// DO NOT EDIT. This file is machine-generated and constantly overwritten.
+// Make changes to DNComment.h instead.
+
+#import
+
+extern const struct DNCommentAttributes {
+ __unsafe_unretained NSString *body;
+ __unsafe_unretained NSString *depth;
+ __unsafe_unretained NSString *upvotesCount;
+ __unsafe_unretained NSString *userDisplayName;
+} DNCommentAttributes;
+
+extern const struct DNCommentRelationships {
+ __unsafe_unretained NSString *comments;
+ __unsafe_unretained NSString *story;
+} DNCommentRelationships;
+
+@class DNComment;
+@class DNStory;
+
+@interface DNCommentID : NSManagedObjectID {}
+@end
+
+@interface _DNComment : NSManagedObject {}
++ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_;
++ (NSString*)entityName;
++ (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc_;
+@property (nonatomic, readonly) DNCommentID* objectID;
+
+@property (nonatomic) NSString* body;
+
+//- (BOOL)validateBody:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSNumber* depth;
+
+@property (atomic) int16_t depthValue;
+- (int16_t)depthValue;
+- (void)setDepthValue:(int16_t)value_;
+
+//- (BOOL)validateDepth:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSNumber* upvotesCount;
+
+@property (atomic) int32_t upvotesCountValue;
+- (int32_t)upvotesCountValue;
+- (void)setUpvotesCountValue:(int32_t)value_;
+
+//- (BOOL)validateUpvotesCount:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSString* userDisplayName;
+
+//- (BOOL)validateUserDisplayName:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSSet *comments;
+
+- (NSMutableSet*)commentsSet;
+
+@property (nonatomic) DNStory *story;
+
+//- (BOOL)validateStory:(id*)value_ error:(NSError**)error_;
+
+@end
+
+@interface _DNComment (CommentsCoreDataGeneratedAccessors)
+- (void)addComments:(NSSet*)value_;
+- (void)removeComments:(NSSet*)value_;
+- (void)addCommentsObject:(DNComment*)value_;
+- (void)removeCommentsObject:(DNComment*)value_;
+
+@end
+
+@interface _DNComment (CoreDataGeneratedPrimitiveAccessors)
+
+- (NSString*)primitiveBody;
+- (void)setPrimitiveBody:(NSString*)value;
+
+- (NSNumber*)primitiveDepth;
+- (void)setPrimitiveDepth:(NSNumber*)value;
+
+- (int16_t)primitiveDepthValue;
+- (void)setPrimitiveDepthValue:(int16_t)value_;
+
+- (NSNumber*)primitiveUpvotesCount;
+- (void)setPrimitiveUpvotesCount:(NSNumber*)value;
+
+- (int32_t)primitiveUpvotesCountValue;
+- (void)setPrimitiveUpvotesCountValue:(int32_t)value_;
+
+- (NSString*)primitiveUserDisplayName;
+- (void)setPrimitiveUserDisplayName:(NSString*)value;
+
+- (NSMutableSet*)primitiveComments;
+- (void)setPrimitiveComments:(NSMutableSet*)value;
+
+- (DNStory*)primitiveStory;
+- (void)setPrimitiveStory:(DNStory*)value;
+
+@end
diff --git a/DesignerNews/Model/_DNComment.m b/DesignerNews/Model/_DNComment.m
new file mode 100755
index 00000000..93d6fc4c
--- /dev/null
+++ b/DesignerNews/Model/_DNComment.m
@@ -0,0 +1,116 @@
+// DO NOT EDIT. This file is machine-generated and constantly overwritten.
+// Make changes to DNComment.m instead.
+
+#import "_DNComment.h"
+
+const struct DNCommentAttributes DNCommentAttributes = {
+ .body = @"body",
+ .depth = @"depth",
+ .upvotesCount = @"upvotesCount",
+ .userDisplayName = @"userDisplayName",
+};
+
+const struct DNCommentRelationships DNCommentRelationships = {
+ .comments = @"comments",
+ .story = @"story",
+};
+
+@implementation DNCommentID
+@end
+
+@implementation _DNComment
+
++ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_ {
+ NSParameterAssert(moc_);
+ return [NSEntityDescription insertNewObjectForEntityForName:@"Comment" inManagedObjectContext:moc_];
+}
+
++ (NSString*)entityName {
+ return @"Comment";
+}
+
++ (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc_ {
+ NSParameterAssert(moc_);
+ return [NSEntityDescription entityForName:@"Comment" inManagedObjectContext:moc_];
+}
+
+- (DNCommentID*)objectID {
+ return (DNCommentID*)[super objectID];
+}
+
++ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key {
+ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
+
+ if ([key isEqualToString:@"depthValue"]) {
+ NSSet *affectingKey = [NSSet setWithObject:@"depth"];
+ keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKey];
+ return keyPaths;
+ }
+ if ([key isEqualToString:@"upvotesCountValue"]) {
+ NSSet *affectingKey = [NSSet setWithObject:@"upvotesCount"];
+ keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKey];
+ return keyPaths;
+ }
+
+ return keyPaths;
+}
+
+@dynamic body;
+
+@dynamic depth;
+
+- (int16_t)depthValue {
+ NSNumber *result = [self depth];
+ return [result shortValue];
+}
+
+- (void)setDepthValue:(int16_t)value_ {
+ [self setDepth:[NSNumber numberWithShort:value_]];
+}
+
+- (int16_t)primitiveDepthValue {
+ NSNumber *result = [self primitiveDepth];
+ return [result shortValue];
+}
+
+- (void)setPrimitiveDepthValue:(int16_t)value_ {
+ [self setPrimitiveDepth:[NSNumber numberWithShort:value_]];
+}
+
+@dynamic upvotesCount;
+
+- (int32_t)upvotesCountValue {
+ NSNumber *result = [self upvotesCount];
+ return [result intValue];
+}
+
+- (void)setUpvotesCountValue:(int32_t)value_ {
+ [self setUpvotesCount:[NSNumber numberWithInt:value_]];
+}
+
+- (int32_t)primitiveUpvotesCountValue {
+ NSNumber *result = [self primitiveUpvotesCount];
+ return [result intValue];
+}
+
+- (void)setPrimitiveUpvotesCountValue:(int32_t)value_ {
+ [self setPrimitiveUpvotesCount:[NSNumber numberWithInt:value_]];
+}
+
+@dynamic userDisplayName;
+
+@dynamic comments;
+
+- (NSMutableSet*)commentsSet {
+ [self willAccessValueForKey:@"comments"];
+
+ NSMutableSet *result = (NSMutableSet*)[self mutableSetValueForKey:@"comments"];
+
+ [self didAccessValueForKey:@"comments"];
+ return result;
+}
+
+@dynamic story;
+
+@end
+
diff --git a/DesignerNews/Model/_DNStory.h b/DesignerNews/Model/_DNStory.h
new file mode 100755
index 00000000..09b56105
--- /dev/null
+++ b/DesignerNews/Model/_DNStory.h
@@ -0,0 +1,89 @@
+// DO NOT EDIT. This file is machine-generated and constantly overwritten.
+// Make changes to DNStory.h instead.
+
+#import
+
+extern const struct DNStoryAttributes {
+ __unsafe_unretained NSString *commentsCount;
+ __unsafe_unretained NSString *createdAt;
+ __unsafe_unretained NSString *remoteID;
+ __unsafe_unretained NSString *title;
+} DNStoryAttributes;
+
+extern const struct DNStoryRelationships {
+ __unsafe_unretained NSString *comments;
+} DNStoryRelationships;
+
+@class DNComment;
+
+@interface DNStoryID : NSManagedObjectID {}
+@end
+
+@interface _DNStory : NSManagedObject {}
++ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_;
++ (NSString*)entityName;
++ (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc_;
+@property (nonatomic, readonly) DNStoryID* objectID;
+
+@property (nonatomic) NSNumber* commentsCount;
+
+@property (atomic) int32_t commentsCountValue;
+- (int32_t)commentsCountValue;
+- (void)setCommentsCountValue:(int32_t)value_;
+
+//- (BOOL)validateCommentsCount:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSDate* createdAt;
+
+//- (BOOL)validateCreatedAt:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSNumber* remoteID;
+
+@property (atomic) int32_t remoteIDValue;
+- (int32_t)remoteIDValue;
+- (void)setRemoteIDValue:(int32_t)value_;
+
+//- (BOOL)validateRemoteID:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSString* title;
+
+//- (BOOL)validateTitle:(id*)value_ error:(NSError**)error_;
+
+@property (nonatomic) NSSet *comments;
+
+- (NSMutableSet*)commentsSet;
+
+@end
+
+@interface _DNStory (CommentsCoreDataGeneratedAccessors)
+- (void)addComments:(NSSet*)value_;
+- (void)removeComments:(NSSet*)value_;
+- (void)addCommentsObject:(DNComment*)value_;
+- (void)removeCommentsObject:(DNComment*)value_;
+
+@end
+
+@interface _DNStory (CoreDataGeneratedPrimitiveAccessors)
+
+- (NSNumber*)primitiveCommentsCount;
+- (void)setPrimitiveCommentsCount:(NSNumber*)value;
+
+- (int32_t)primitiveCommentsCountValue;
+- (void)setPrimitiveCommentsCountValue:(int32_t)value_;
+
+- (NSDate*)primitiveCreatedAt;
+- (void)setPrimitiveCreatedAt:(NSDate*)value;
+
+- (NSNumber*)primitiveRemoteID;
+- (void)setPrimitiveRemoteID:(NSNumber*)value;
+
+- (int32_t)primitiveRemoteIDValue;
+- (void)setPrimitiveRemoteIDValue:(int32_t)value_;
+
+- (NSString*)primitiveTitle;
+- (void)setPrimitiveTitle:(NSString*)value;
+
+- (NSMutableSet*)primitiveComments;
+- (void)setPrimitiveComments:(NSMutableSet*)value;
+
+@end
diff --git a/DesignerNews/Model/_DNStory.m b/DesignerNews/Model/_DNStory.m
new file mode 100755
index 00000000..c827ff20
--- /dev/null
+++ b/DesignerNews/Model/_DNStory.m
@@ -0,0 +1,113 @@
+// DO NOT EDIT. This file is machine-generated and constantly overwritten.
+// Make changes to DNStory.m instead.
+
+#import "_DNStory.h"
+
+const struct DNStoryAttributes DNStoryAttributes = {
+ .commentsCount = @"commentsCount",
+ .createdAt = @"createdAt",
+ .remoteID = @"remoteID",
+ .title = @"title",
+};
+
+const struct DNStoryRelationships DNStoryRelationships = {
+ .comments = @"comments",
+};
+
+@implementation DNStoryID
+@end
+
+@implementation _DNStory
+
++ (id)insertInManagedObjectContext:(NSManagedObjectContext*)moc_ {
+ NSParameterAssert(moc_);
+ return [NSEntityDescription insertNewObjectForEntityForName:@"Story" inManagedObjectContext:moc_];
+}
+
++ (NSString*)entityName {
+ return @"Story";
+}
+
++ (NSEntityDescription*)entityInManagedObjectContext:(NSManagedObjectContext*)moc_ {
+ NSParameterAssert(moc_);
+ return [NSEntityDescription entityForName:@"Story" inManagedObjectContext:moc_];
+}
+
+- (DNStoryID*)objectID {
+ return (DNStoryID*)[super objectID];
+}
+
++ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString*)key {
+ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
+
+ if ([key isEqualToString:@"commentsCountValue"]) {
+ NSSet *affectingKey = [NSSet setWithObject:@"commentsCount"];
+ keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKey];
+ return keyPaths;
+ }
+ if ([key isEqualToString:@"remoteIDValue"]) {
+ NSSet *affectingKey = [NSSet setWithObject:@"remoteID"];
+ keyPaths = [keyPaths setByAddingObjectsFromSet:affectingKey];
+ return keyPaths;
+ }
+
+ return keyPaths;
+}
+
+@dynamic commentsCount;
+
+- (int32_t)commentsCountValue {
+ NSNumber *result = [self commentsCount];
+ return [result intValue];
+}
+
+- (void)setCommentsCountValue:(int32_t)value_ {
+ [self setCommentsCount:[NSNumber numberWithInt:value_]];
+}
+
+- (int32_t)primitiveCommentsCountValue {
+ NSNumber *result = [self primitiveCommentsCount];
+ return [result intValue];
+}
+
+- (void)setPrimitiveCommentsCountValue:(int32_t)value_ {
+ [self setPrimitiveCommentsCount:[NSNumber numberWithInt:value_]];
+}
+
+@dynamic createdAt;
+
+@dynamic remoteID;
+
+- (int32_t)remoteIDValue {
+ NSNumber *result = [self remoteID];
+ return [result intValue];
+}
+
+- (void)setRemoteIDValue:(int32_t)value_ {
+ [self setRemoteID:[NSNumber numberWithInt:value_]];
+}
+
+- (int32_t)primitiveRemoteIDValue {
+ NSNumber *result = [self primitiveRemoteID];
+ return [result intValue];
+}
+
+- (void)setPrimitiveRemoteIDValue:(int32_t)value_ {
+ [self setPrimitiveRemoteID:[NSNumber numberWithInt:value_]];
+}
+
+@dynamic title;
+
+@dynamic comments;
+
+- (NSMutableSet*)commentsSet {
+ [self willAccessValueForKey:@"comments"];
+
+ NSMutableSet *result = (NSMutableSet*)[self mutableSetValueForKey:@"comments"];
+
+ [self didAccessValueForKey:@"comments"];
+ return result;
+}
+
+@end
+
diff --git a/DesignerNews/README.md b/DesignerNews/README.md
new file mode 100644
index 00000000..34db6195
--- /dev/null
+++ b/DesignerNews/README.md
@@ -0,0 +1,53 @@
+## App
+
+[Reference](https://github.com/hyperoslo/Sync/tree/master/Examples/DesignerNews)
+
+![Model](https://raw.githubusercontent.com/hyperoslo/Sync/master/Examples/DesignerNews/Images/app.png)
+
+## JSON
+
+[Reference](https://news.layervault.com/?format=json)
+
+```json
+{
+ "stories":[
+ {
+ "id":47333,
+ "title":"Site Design: Aquest",
+ "vote_count":6,
+ "created_at":"2015-04-06T13:16:36Z",
+ "num_comments":6,
+ "submitter_display_name":"Chris A.",
+ "comments":[
+ {
+ "body":"Beautiful.",
+ "created_at":"2015-04-06T13:45:20Z",
+ "depth":0,
+ "user_display_name":"Sam M.",
+ "upvotes_count":0,
+ "comments":[
+
+ ]
+ }
+ ]
+ }
+ ]
+}
+```
+
+## Model
+
+![Model](https://raw.githubusercontent.com/hyperoslo/Sync/master/Examples/DesignerNews/Images/model.png)
+
+## Sync
+
+[Reference](https://github.com/hyperoslo/Sync/blob/master/Examples/DesignerNews/DesignerNews/Source/APIClient.m#L35-L40)
+
+```objc
+[Sync changes:JSON[@"stories"]
+inEntityNamed:@"Story"
+ dataStack:dataStack
+ completion:^(NSError *error) {
+ [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
+ }];
+```
diff --git a/DesignerNews/StoriesViewController.h b/DesignerNews/StoriesViewController.h
new file mode 100644
index 00000000..12650644
--- /dev/null
+++ b/DesignerNews/StoriesViewController.h
@@ -0,0 +1,11 @@
+@import UIKit;
+@import CoreData;
+
+@class DATAStack;
+
+@interface StoriesViewController : UITableViewController
+
+- (instancetype)initWithDataStack:(DATAStack *)dataStack;
+
+@end
+
diff --git a/DesignerNews/StoriesViewController.m b/DesignerNews/StoriesViewController.m
new file mode 100644
index 00000000..97aadc62
--- /dev/null
+++ b/DesignerNews/StoriesViewController.m
@@ -0,0 +1,94 @@
+#import "StoriesViewController.h"
+#import "StoryViewController.h"
+#import "StoryTableViewCell.h"
+#import "APIClient.h"
+#import "DNStory.h"
+@import DATAStack;
+@import DATASource;
+
+@interface StoriesViewController ()
+
+@property (nonatomic, weak) DATAStack *dataStack;
+@property (nonatomic) DATASource *dataSource;
+
+@end
+
+@implementation StoriesViewController
+
+#pragma mark - Initializers
+
+- (instancetype)initWithDataStack:(DATAStack *)dataStack
+{
+ self = [super initWithStyle:UITableViewStylePlain];
+ if (!self) return nil;
+
+ _dataStack = dataStack;
+
+ return self;
+}
+
+#pragma mark - Getters
+
+- (DATASource *)dataSource
+{
+ if (_dataSource) return _dataSource;
+
+ NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Story"];
+ request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"createdAt"
+ ascending:NO]];
+
+ _dataSource = [[DATASource alloc] initWithTableView:self.tableView
+ cellIdentifier:StoryTableViewCellIdentifier
+ fetchRequest:request
+ mainContext:self.dataStack.mainContext
+ sectionName:nil
+ configuration:^(UITableViewCell * _Nonnull cell, NSManagedObject * _Nonnull item, NSIndexPath * _Nonnull indexPath) {
+ StoryTableViewCell *storyCell = (StoryTableViewCell *)cell;
+ DNStory *story = (DNStory *)item;
+
+ [storyCell updateWithStory:story];
+ }];
+
+ return _dataSource;
+}
+
+#pragma mark - View lifecycle
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ APIClient *client = [APIClient new];
+ [client fetchStoryUsingDataStack:self.dataStack];
+
+ [self.tableView registerClass:[StoryTableViewCell class]
+ forCellReuseIdentifier:StoryTableViewCellIdentifier];
+ self.tableView.dataSource = self.dataSource;
+ self.tableView.rowHeight = StoryTableViewCellHeight;
+
+ self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
+
+ self.title = @"Designer News";
+}
+
+#pragma mark - UIViewController
+
+- (UIStatusBarStyle)preferredStatusBarStyle
+{
+ return UIStatusBarStyleLightContent;
+}
+
+#pragma mark - UITableViewDataSource
+
+- (void)tableView:(UITableView *)tableView
+didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ DNStory *story = (DNStory *)[self.dataSource objectAtIndexPath:indexPath];
+ StoryViewController *viewController = [[StoryViewController alloc] initWithStory:story
+ andDataStack:self.dataStack];
+ [self.navigationController pushViewController:viewController
+ animated:YES];
+}
+
+
+@end
diff --git a/DesignerNews/StoryTableViewCell.h b/DesignerNews/StoryTableViewCell.h
new file mode 100644
index 00000000..4e843297
--- /dev/null
+++ b/DesignerNews/StoryTableViewCell.h
@@ -0,0 +1,12 @@
+@import UIKit;
+
+@class DNStory;
+
+static NSString * const StoryTableViewCellIdentifier = @"StoryTableViewCellIdentifier";
+static const CGFloat StoryTableViewCellHeight = 65.0;
+
+@interface StoryTableViewCell : UITableViewCell
+
+- (void)updateWithStory:(DNStory *)story;
+
+@end
diff --git a/DesignerNews/StoryTableViewCell.m b/DesignerNews/StoryTableViewCell.m
new file mode 100644
index 00000000..35d23c33
--- /dev/null
+++ b/DesignerNews/StoryTableViewCell.m
@@ -0,0 +1,128 @@
+#import "StoryTableViewCell.h"
+
+#import "DNStory.h"
+
+#import "UIFont+DNStyle.h"
+
+static const CGFloat HYPTitleLabelMargin = 10.0;
+static const CGFloat HYPTitleLabelHeight = 30.0;
+
+static const CGFloat HYPUpdatedLabelHeight = 30.0;
+static const CGFloat HYPUpdatedLabelWidth = 90.0;
+static const CGFloat HYPUpdatedLabelTopMargin = 10.0;
+
+static const CGFloat HYPCommentsCountMargin = 10.0;
+static const CGFloat HYPCommentsCountHeight = 20.0;
+
+@interface StoryTableViewCell ()
+
+@property (nonatomic) UILabel *titleLabel;
+@property (nonatomic) UILabel *updatedLabel;
+@property (nonatomic) UILabel *commentCountLabel;
+@end
+
+@implementation StoryTableViewCell
+
+#pragma mark - Initializers
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+ if (!self) return nil;
+
+ [self.contentView addSubview:self.titleLabel];
+ [self.contentView addSubview:self.updatedLabel];
+ [self.contentView addSubview:self.commentCountLabel];
+
+ return self;
+}
+
+#pragma mark - Getters
+
+- (UILabel *)titleLabel
+{
+ if (_titleLabel) return _titleLabel;
+
+ _titleLabel = [UILabel new];
+ _titleLabel.font = [UIFont headerFont];
+
+ return _titleLabel;
+}
+
+- (UILabel *)updatedLabel
+{
+ if (_updatedLabel) return _updatedLabel;
+
+ _updatedLabel = [UILabel new];
+ _updatedLabel.font = [UIFont asideFont];
+ _updatedLabel.textAlignment = NSTextAlignmentCenter;
+
+ return _updatedLabel;
+}
+
+- (UILabel *)commentCountLabel
+{
+ if (_commentCountLabel) return _commentCountLabel;
+
+ _commentCountLabel = [UILabel new];
+ _commentCountLabel.font = [UIFont subtitleFont];
+
+ return _commentCountLabel;
+}
+
+#pragma mark - Public methods
+
+- (void)updateWithStory:(DNStory *)story
+{
+ static dispatch_once_t onceToken;
+ static NSDateFormatter *formatter = nil;
+ dispatch_once(&onceToken, ^{
+ formatter = [NSDateFormatter new];
+ formatter.dateStyle = NSDateFormatterLongStyle;
+ formatter.timeStyle = NSDateFormatterNoStyle;
+ });
+
+ self.titleLabel.text = story.title;
+ self.commentCountLabel.text = [NSString stringWithFormat:@"%@ comments", story.commentsCount];
+ self.updatedLabel.text = [formatter stringFromDate:story.createdAt];
+}
+
+#pragma mark - Layout
+
+- (CGRect)titleLabelFrame
+{
+ CGRect screenFrame = [UIScreen mainScreen].bounds;
+ return CGRectMake(HYPTitleLabelMargin,
+ HYPTitleLabelMargin,
+ CGRectGetWidth(screenFrame) - HYPUpdatedLabelWidth - HYPTitleLabelMargin,
+ HYPTitleLabelHeight);
+}
+
+- (CGRect)updatedLabelFrame
+{
+ CGRect screenFrame = [UIScreen mainScreen].bounds;
+ return CGRectMake(CGRectGetWidth(screenFrame) - HYPUpdatedLabelWidth,
+ HYPUpdatedLabelTopMargin,
+ HYPUpdatedLabelWidth,
+ HYPUpdatedLabelHeight);
+}
+
+- (CGRect)commentCountLabelFrame
+{
+ CGRect screenFrame = [UIScreen mainScreen].bounds;
+ return CGRectMake(HYPCommentsCountMargin,
+ CGRectGetMaxY(self.updatedLabel.frame),
+ CGRectGetWidth(screenFrame) - HYPCommentsCountMargin * 2.0f,
+ HYPCommentsCountHeight);
+}
+
+- (void)setNeedsLayout
+{
+ [super setNeedsLayout];
+
+ self.titleLabel.frame = [self titleLabelFrame];
+ self.updatedLabel.frame = [self updatedLabelFrame];
+ self.commentCountLabel.frame = [self commentCountLabelFrame];
+}
+
+@end
diff --git a/DesignerNews/StoryViewController.h b/DesignerNews/StoryViewController.h
new file mode 100644
index 00000000..563c5342
--- /dev/null
+++ b/DesignerNews/StoryViewController.h
@@ -0,0 +1,11 @@
+@import UIKit;
+
+@class DNStory;
+@class DATAStack;
+
+@interface StoryViewController : UITableViewController
+
+- (instancetype)initWithStory:(DNStory *)story
+ andDataStack:(DATAStack *)dataStack;
+
+@end
diff --git a/DesignerNews/StoryViewController.m b/DesignerNews/StoryViewController.m
new file mode 100644
index 00000000..4da3ca3c
--- /dev/null
+++ b/DesignerNews/StoryViewController.m
@@ -0,0 +1,79 @@
+#import "StoryViewController.h"
+
+#import "DNStory.h"
+#import "DNComment.h"
+@import DATAStack;
+@import DATASource;
+
+#import "UIFont+DNStyle.h"
+
+static NSString * const CommentTableViewCellIdentifier = @"CommentTableViewCellIdentifier";
+static const CGFloat CommentTableViewCellHeight = 50.0;
+static const CGFloat CommentTableViewCellOffset = 40.0;
+
+@interface StoryViewController ()
+
+@property (nonatomic, weak) DNStory *story;
+@property (nonatomic, weak) DATAStack *dataStack;
+@property (nonatomic) DATASource *dataSource;
+
+@end
+
+@implementation StoryViewController
+
+#pragma mark - Initializers
+
+- (instancetype)initWithStory:(DNStory *)story
+ andDataStack:(DATAStack *)dataStack
+{
+ self = [super initWithStyle:UITableViewStylePlain];
+ if (!self) return nil;
+
+ _story = story;
+ _dataStack = dataStack;
+
+ return self;
+}
+
+#pragma mark - Getters
+
+- (DATASource *)dataSource
+{
+ if (_dataSource) return _dataSource;
+
+ NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Comment"];
+ request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"upvotesCount"
+ ascending:NO],
+ [NSSortDescriptor sortDescriptorWithKey:@"body"
+ ascending:NO]];
+ request.predicate = [NSPredicate predicateWithFormat:@"story = %@", self.story];
+
+ _dataSource = [[DATASource alloc] initWithTableView:self.tableView
+ cellIdentifier:CommentTableViewCellIdentifier
+ fetchRequest:request
+ mainContext:self.dataStack.mainContext
+ sectionName:nil
+ configuration:^(UITableViewCell * _Nonnull cell, NSManagedObject * _Nonnull item, NSIndexPath * _Nonnull indexPath) {
+ DNComment *comment = (DNComment *)item;
+ cell.textLabel.text = comment.body;
+ cell.textLabel.font = [UIFont commentFont];
+ cell.textLabel.numberOfLines = 0;
+ }];
+
+ return _dataSource;
+}
+
+#pragma mark - View lifecycle
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ self.title = self.story.title;
+ self.tableView.dataSource = self.dataSource;
+ self.tableView.rowHeight = CommentTableViewCellHeight;
+ [self.tableView registerClass:[UITableViewCell class]
+ forCellReuseIdentifier:CommentTableViewCellIdentifier];
+}
+
+@end
diff --git a/DesignerNews/UIFont+DNStyle.h b/DesignerNews/UIFont+DNStyle.h
new file mode 100644
index 00000000..0ab98da3
--- /dev/null
+++ b/DesignerNews/UIFont+DNStyle.h
@@ -0,0 +1,11 @@
+@import UIKit;
+
+@interface UIFont (DNStyle)
+
++ (UIFont *)appTitleFont;
++ (UIFont *)commentFont;
++ (UIFont *)headerFont;
++ (UIFont *)subtitleFont;
++ (UIFont *)asideFont;
+
+@end
diff --git a/DesignerNews/UIFont+DNStyle.m b/DesignerNews/UIFont+DNStyle.m
new file mode 100644
index 00000000..b8c64b55
--- /dev/null
+++ b/DesignerNews/UIFont+DNStyle.m
@@ -0,0 +1,30 @@
+#import "UIFont+DNStyle.h"
+
+@implementation UIFont (DNStyle)
+
++ (UIFont *)appTitleFont
+{
+ return [UIFont fontWithName:@"Avenir-Medium" size:20.0f];;
+}
+
++ (UIFont *)commentFont
+{
+ return [UIFont fontWithName:@"Avenir-Medium" size:14.0f];;
+}
+
++ (UIFont *)headerFont
+{
+ return [UIFont fontWithName:@"Avenir-Medium" size:16.0f];;
+}
+
++ (UIFont *)subtitleFont
+{
+ return [UIFont fontWithName:@"Avenir-Medium" size:13.0f];
+}
+
++ (UIFont *)asideFont
+{
+ return [UIFont fontWithName:@"Avenir-Light" size:11.0f];
+}
+
+@end
diff --git a/DesignerNews/main.m b/DesignerNews/main.m
new file mode 100644
index 00000000..1a9a6af0
--- /dev/null
+++ b/DesignerNews/main.m
@@ -0,0 +1,16 @@
+//
+// main.m
+// DesignerNews
+//
+// Created by Elvis Nuñez on 25/10/15.
+//
+//
+
+#import
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/Podfile b/Podfile
index 4289dc3e..6ad9898a 100644
--- a/Podfile
+++ b/Podfile
@@ -1,7 +1,7 @@
use_frameworks!
-# When using more than one target in your project
-# link_with 'DemoProject', 'Tests'
+link_with 'Tests', 'AppNet', 'DesignerNews'
pod 'Sync', path: "."
-pod 'NSJSONSerialization-ANDYJSONFile'
\ No newline at end of file
+pod 'NSJSONSerialization-ANDYJSONFile'
+pod 'DATASource'