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'