diff --git a/HardcoreData.xcodeproj/project.pbxproj b/HardcoreData.xcodeproj/project.pbxproj index e43666f..f69bbb3 100644 --- a/HardcoreData.xcodeproj/project.pbxproj +++ b/HardcoreData.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ B52B68BC1AAB46BD00CE7F48 /* ManagedObjectListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */; }; B52B68BE1AAB484C00CE7F48 /* DataStack+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */; }; B52B68C01AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */; }; - B52B68C21AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */; }; B5398AA21AA8938D00B66388 /* DetachedDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5398AA11AA8938D00B66388 /* DetachedDataTransaction.swift */; }; B54A9F071AA7654400AFEC05 /* DataStack+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A9F061AA7654400AFEC05 /* DataStack+Querying.swift */; }; B54D53071AB3538500D55BA8 /* QueryClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D53061AB3538500D55BA8 /* QueryClause.swift */; }; @@ -26,6 +25,7 @@ B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B595CAC31A9A11C1009A397F /* NSManagedObjectContext+Setup.swift */; }; B595CAC61A9A1260009A397F /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B595CAC51A9A1260009A397F /* NSManagedObjectContext+Transaction.swift */; }; B595CAC81A9A161B009A397F /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B595CAC71A9A161B009A397F /* WeakObject.swift */; }; + B5BC26DD1AF78A8800276889 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC26DC1AF78A8800276889 /* ManagedObjectObserver.swift */; }; B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFD36D1A0775F000B7885F /* SaveResult.swift */; }; B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; }; B5CFF24019FD383100D6DFC4 /* BaseDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* BaseDataTransaction.swift */; }; @@ -94,7 +94,6 @@ B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListController.swift; sourceTree = ""; }; B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Observing.swift"; sourceTree = ""; }; B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListObserver.swift; sourceTree = ""; }; - B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListSectionInfo.swift; sourceTree = ""; }; B5398AA11AA8938D00B66388 /* DetachedDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetachedDataTransaction.swift; sourceTree = ""; }; B54A9F061AA7654400AFEC05 /* DataStack+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Querying.swift"; sourceTree = ""; }; B54D53061AB3538500D55BA8 /* QueryClause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryClause.swift; sourceTree = ""; }; @@ -104,6 +103,7 @@ B595CAC31A9A11C1009A397F /* NSManagedObjectContext+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Setup.swift"; sourceTree = ""; }; B595CAC51A9A1260009A397F /* NSManagedObjectContext+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Transaction.swift"; sourceTree = ""; }; B595CAC71A9A161B009A397F /* WeakObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakObject.swift; sourceTree = ""; }; + B5BC26DC1AF78A8800276889 /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = ""; }; B5CFD36D1A0775F000B7885F /* SaveResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveResult.swift; sourceTree = ""; }; B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = ""; }; B5CFF23F19FD383100D6DFC4 /* BaseDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseDataTransaction.swift; sourceTree = ""; }; @@ -238,10 +238,10 @@ B52B68B61AAB45FB00CE7F48 /* Observing */ = { isa = PBXGroup; children = ( - B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */, B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */, + B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */, B52B68B91AAB46AD00CE7F48 /* ManagedObjectController.swift */, - B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */, + B5BC26DC1AF78A8800276889 /* ManagedObjectObserver.swift */, B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */, ); name = Observing; @@ -527,6 +527,7 @@ B5D8081A1A3495BD00A44484 /* AssociatedObjects.swift in Sources */, B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */, B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */, + B5BC26DD1AF78A8800276889 /* ManagedObjectObserver.swift in Sources */, B5D10DC21AB4590F004B4EEA /* Select.swift in Sources */, B5E209E01A0726460089C9D4 /* NSManagedObject+Transaction.swift in Sources */, B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */, @@ -540,7 +541,6 @@ B5D399F119FC818E000E91BB /* DataStack.swift in Sources */, B5D808161A34947300A44484 /* NotificationObserver.swift in Sources */, B52B68BE1AAB484C00CE7F48 /* DataStack+Observing.swift in Sources */, - B52B68C21AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift in Sources */, B57078B01A50392D007E33F2 /* FetchClause.swift in Sources */, B5F409ED1A8B200700A228EA /* NSManagedObjectContext+Querying.swift in Sources */, B5D10DC01AB42C85004B4EEA /* GroupBy.swift in Sources */, diff --git a/HardcoreData/BaseDataTransaction.swift b/HardcoreData/BaseDataTransaction.swift index 2335535..fc09afa 100644 --- a/HardcoreData/BaseDataTransaction.swift +++ b/HardcoreData/BaseDataTransaction.swift @@ -77,7 +77,7 @@ public /*abstract*/ class BaseDataTransaction { HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type \(typeName(object)) outside its designated queue.") - object.deleteFromContext() + object.inContext(self.context)?.deleteFromContext() } // MARK: Saving changes diff --git a/HardcoreData/DataStack+Observing.swift b/HardcoreData/DataStack+Observing.swift index 1180df9..d2db2bf 100644 --- a/HardcoreData/DataStack+Observing.swift +++ b/HardcoreData/DataStack+Observing.swift @@ -34,15 +34,25 @@ public extension DataStack { // MARK: Public - public func observeObjectList(from: From, _ queryClauses: FetchClause...) -> ManagedObjectListController { + public func observeObject(object: T) -> ManagedObjectController { - HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside the main queue.") + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to observe objects from \(typeName(self)) outside the main queue.") + + return ManagedObjectController( + dataStack: self, + object: object + ) + } + + public func observeObjectList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ManagedObjectListController { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to observe objects from \(typeName(self)) outside the main queue.") // TODO: sectionNameKeyPath and cacheResults return ManagedObjectListController( dataStack: self, entity: T.self, - sectionNameKeyPath: nil, + sectionNameKeyPath: groupBy?.keyPaths.first, cacheResults: false, queryClauses: queryClauses ) diff --git a/HardcoreData/ManagedObjectController.swift b/HardcoreData/ManagedObjectController.swift index 7cb355f..8aa0940 100644 --- a/HardcoreData/ManagedObjectController.swift +++ b/HardcoreData/ManagedObjectController.swift @@ -25,3 +25,279 @@ import Foundation import CoreData +import GCDKit + + +private let ManagedObjectListControllerWillChangeListNotification = "ManagedObjectListControllerWillChangeListNotification" +private let ManagedObjectListControllerDidChangeListNotification = "ManagedObjectListControllerDidChangeListNotification" + +private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" +private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" + +private let UserInfoKeyObject = "UserInfoKeyObject" + +private struct NotificationKey { + + static var willChangeList: Void? + static var didChangeList: Void? + + static var didDeleteObject: Void? + static var didUpdateObject: Void? +} + + +// MARK: - ManagedObjectController + +public final class ManagedObjectController: FetchedResultsControllerHandler { + + // MARK: Public + + public var object: T? { + + return self.fetchedResultsController.fetchedObjects?.first as? T + } + + public var isObjectDeleted: Bool { + + return self.object?.managedObjectContext == nil + } + + public func addObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + + self.registerChangeNotification( + &NotificationKey.willChangeList, + name: ManagedObjectListControllerWillChangeListNotification, + toObserver: observer, + callback: { [weak observer, weak self] (objectController) -> Void in + + if let observer = observer, let object = self?.object { + + observer.managedObjectWillUpdate(objectController, object: object) + } + } + ) + + self.registerObjectNotification( + &NotificationKey.didDeleteObject, + name: ManagedObjectListControllerDidDeleteObjectNotification, + toObserver: observer, + callback: { [weak observer] (objectController, object) -> Void in + + if let observer = observer { + + observer.managedObjectWasDeleted(objectController, object: object) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didUpdateObject, + name: ManagedObjectListControllerDidUpdateObjectNotification, + toObserver: observer, + callback: { [weak observer] (objectController, object) -> Void in + + if let observer = observer { + + observer.managedObjectWasUpdated(objectController, object: object) + } + } + ) + } + + public func removeObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a \(typeName(observer)) outside the main queue.") + + let nilValue: AnyObject? = nil + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer) + + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer) + } + + + // MARK: FetchedResultsControllerHandler + + private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + + switch type { + + case .Delete: + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerDidDeleteObjectNotification, + object: self, + userInfo: [UserInfoKeyObject: anObject] + ) + + case .Update: + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerDidUpdateObjectNotification, + object: self, + userInfo: [UserInfoKeyObject: anObject] + ) + + default: + break + } + } + + private func controllerWillChangeContent(controller: NSFetchedResultsController) { + + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerWillChangeListNotification, + object: self + ) + } + + private func controllerDidChangeContent(controller: NSFetchedResultsController) { + + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerDidChangeListNotification, + object: self + ) + } + + + // MARK: Internal + + internal init(dataStack: DataStack, object: T) { + + let context = dataStack.mainContext + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = context.entityDescriptionForEntityClass(T.self) + fetchRequest.fetchLimit = 1 + fetchRequest.resultType = .ManagedObjectResultType + + Where("SELF", isEqualTo: object).applyToFetchRequest(fetchRequest) + SortedBy(.Ascending("objectID")).applyToFetchRequest(fetchRequest) + + let fetchedResultsController = NSFetchedResultsController( + fetchRequest: fetchRequest, + managedObjectContext: context, + sectionNameKeyPath: nil, + cacheName: nil + ) + + let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate() + + self.fetchedResultsController = fetchedResultsController + self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate + self.parentStack = dataStack + + fetchedResultsControllerDelegate.handler = self + fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController + + var error: NSError? + if !fetchedResultsController.performFetch(&error) { + + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed to perform fetch on <\(NSFetchedResultsController.self)>.") + } + } + + + // MARK: Private + + private let fetchedResultsController: NSFetchedResultsController + private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate + private weak var parentStack: DataStack? + + private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController) -> Void) { + + setAssociatedRetainedObject( + NotificationObserver( + notificationName: name, + object: self, + closure: { [weak self] (note) -> Void in + + if let strongSelf = self { + + callback(objectController: strongSelf) + } + } + ), + forKey: notificationKey, + inObject: observer + ) + } + + private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController, object: T) -> Void) { + + setAssociatedRetainedObject( + NotificationObserver( + notificationName: name, + object: self, + closure: { [weak self] (note) -> Void in + + if let strongSelf = self, + let userInfo = note.userInfo, + let object = userInfo[UserInfoKeyObject] as? T { + + callback( + objectController: strongSelf, + object: object + ) + } + } + ), + forKey: notificationKey, + inObject: observer + ) + } +} + + +// MARK: - FetchedResultsControllerHandler + +private protocol FetchedResultsControllerHandler: class { + + func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) + + func controllerWillChangeContent(controller: NSFetchedResultsController) + + func controllerDidChangeContent(controller: NSFetchedResultsController) +} + + +// MARK: - FetchedResultsControllerDelegate + +private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate { + + // MARK: NSFetchedResultsControllerDelegate + + @objc func controllerWillChangeContent(controller: NSFetchedResultsController) { + + self.handler?.controllerWillChangeContent(controller) + } + + @objc func controllerDidChangeContent(controller: NSFetchedResultsController) { + + self.handler?.controllerDidChangeContent(controller) + } + + @objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + + self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) + } + + + // MARK: Private + + weak var handler: FetchedResultsControllerHandler? + weak var fetchedResultsController: NSFetchedResultsController? { + + didSet { + + oldValue?.delegate = nil + self.fetchedResultsController?.delegate = self + } + } + + deinit { + + self.fetchedResultsController?.delegate = nil + } +} diff --git a/HardcoreData/ManagedObjectListController.swift b/HardcoreData/ManagedObjectListController.swift index b4ba15b..023e948 100644 --- a/HardcoreData/ManagedObjectListController.swift +++ b/HardcoreData/ManagedObjectListController.swift @@ -30,25 +30,37 @@ import GCDKit private let ManagedObjectListControllerWillChangeListNotification = "ManagedObjectListControllerWillChangeListNotification" private let ManagedObjectListControllerDidChangeListNotification = "ManagedObjectListControllerDidChangeListNotification" + private let ManagedObjectListControllerDidInsertObjectNotification = "ManagedObjectListControllerDidInsertObjectNotification" private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" private let ManagedObjectListControllerDidMoveObjectNotification = "ManagedObjectListControllerDidMoveObjectNotification" +private let ManagedObjectListControllerDidInsertSectionNotification = "ManagedObjectListControllerDidInsertSectionNotification" +private let ManagedObjectListControllerDidDeleteSectionNotification = "ManagedObjectListControllerDidDeleteSectionNotification" + private let UserInfoKeyObject = "UserInfoKeyObject" private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath" private let UserInfoKeyNewIndexPath = "UserInfoKeyNewIndexPath" +private let UserInfoKeySectionInfo = "UserInfoKeySectionInfo" +private let UserInfoKeySectionIndex = "UserInfoKeySectionIndex" + private struct NotificationKey { static var willChangeList: Void? static var didChangeList: Void? + static var didInsertObject: Void? static var didDeleteObject: Void? static var didUpdateObject: Void? static var didMoveObject: Void? + + static var didInsertSection: Void? + static var didDeleteSection: Void? } + // MARK: - ManagedObjectListController public final class ManagedObjectListController: FetchedResultsControllerHandler { @@ -65,153 +77,289 @@ public final class ManagedObjectListController: FetchedResul return self.fetchedResultsController.sections?.count ?? 0 } - public func numberOfItemsInSection(section: Int) -> Int { + public func numberOfObjectsInSection(section: Int) -> Int { return (self.fetchedResultsController.sections?[section] as? NSFetchedResultsSectionInfo)?.numberOfObjects ?? 0 } - public func addObserver(observer: U) { + public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { + + return self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo + } + + public func addObserver(observer: U) { HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerWillChangeListNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in + self.registerChangeNotification( + &NotificationKey.willChangeList, + name: ManagedObjectListControllerWillChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { - if let strongSelf = self, let strongObserver = observer { - - strongObserver.managedObjectListWillChange(strongSelf) - } + observer.managedObjectListWillChange(listController) } - ), - forKey: &NotificationKey.willChangeList, - inObject: observer + } ) - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerDidChangeListNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in + self.registerChangeNotification( + &NotificationKey.didChangeList, + name: ManagedObjectListControllerDidChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { - if let strongSelf = self, let strongObserver = observer { - - strongObserver.managedObjectListDidChange(strongSelf) - } + observer.managedObjectListDidChange(listController) } - ), - forKey: &NotificationKey.willChangeList, - inObject: observer - ) - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerDidInsertObjectNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in - - if let strongSelf = self, - let strongObserver = observer, - let userInfo = note.userInfo, - let object = userInfo[UserInfoKeyObject] as? T, - let newIndexPath = userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath { - - strongObserver.managedObjectList( - strongSelf, - didInsertObject: object, - toIndexPath: newIndexPath - ) - } - } - ), - forKey: &NotificationKey.didInsertObject, - inObject: observer - ) - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerDidDeleteObjectNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in - - if let strongSelf = self, - let strongObserver = observer, - let userInfo = note.userInfo, - let object = userInfo[UserInfoKeyObject] as? T, - let indexPath = userInfo[UserInfoKeyIndexPath] as? NSIndexPath { - - strongObserver.managedObjectList( - strongSelf, - didDeleteObject: object, - fromIndexPath: indexPath - ) - } - } - ), - forKey: &NotificationKey.didDeleteObject, - inObject: observer - ) - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerDidUpdateObjectNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in - - if let strongSelf = self, - let strongObserver = observer, - let userInfo = note.userInfo, - let object = userInfo[UserInfoKeyObject] as? T, - let indexPath = userInfo[UserInfoKeyIndexPath] as? NSIndexPath { - - strongObserver.managedObjectList( - strongSelf, - didUpdateObject: object, - atIndexPath: indexPath - ) - } - } - ), - forKey: &NotificationKey.didUpdateObject, - inObject: observer - ) - setAssociatedRetainedObject( - NotificationObserver( - notificationName: ManagedObjectListControllerDidMoveObjectNotification, - object: self, - closure: { [weak self, weak observer] (note) -> Void in - - if let strongSelf = self, - let strongObserver = observer, - let userInfo = note.userInfo, - let object = userInfo[UserInfoKeyObject] as? T, - let indexPath = userInfo[UserInfoKeyIndexPath] as? NSIndexPath , - let newIndexPath = userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath { - - strongObserver.managedObjectList( - strongSelf, - didMoveObject: object, - fromIndexPath: indexPath, - toIndexPath: newIndexPath - ) - } - } - ), - forKey: &NotificationKey.didMoveObject, - inObject: observer + } ) } - public func removeObserver(observer: U) { + public func addObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + + self.registerChangeNotification( + &NotificationKey.willChangeList, + name: ManagedObjectListControllerWillChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { + + observer.managedObjectListWillChange(listController) + } + } + ) + self.registerChangeNotification( + &NotificationKey.didChangeList, + name: ManagedObjectListControllerDidChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { + + observer.managedObjectListDidChange(listController) + } + } + ) + + self.registerObjectNotification( + &NotificationKey.didInsertObject, + name: ManagedObjectListControllerDidInsertObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didInsertObject: object, + toIndexPath: newIndexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didDeleteObject, + name: ManagedObjectListControllerDidDeleteObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didDeleteObject: object, + fromIndexPath: indexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didUpdateObject, + name: ManagedObjectListControllerDidUpdateObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didUpdateObject: object, + atIndexPath: indexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didMoveObject, + name: ManagedObjectListControllerDidMoveObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didMoveObject: object, + fromIndexPath: indexPath!, + toIndexPath: newIndexPath! + ) + } + } + ) + } + + public func addObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a \(typeName(observer)) outside the main queue.") + + self.registerChangeNotification( + &NotificationKey.willChangeList, + name: ManagedObjectListControllerWillChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { + + observer.managedObjectListWillChange(listController) + } + } + ) + self.registerChangeNotification( + &NotificationKey.didChangeList, + name: ManagedObjectListControllerDidChangeListNotification, + toObserver: observer, + callback: { [weak observer] (listController) -> Void in + + if let observer = observer { + + observer.managedObjectListDidChange(listController) + } + } + ) + + self.registerObjectNotification( + &NotificationKey.didInsertObject, + name: ManagedObjectListControllerDidInsertObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didInsertObject: object, + toIndexPath: newIndexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didDeleteObject, + name: ManagedObjectListControllerDidDeleteObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didDeleteObject: object, + fromIndexPath: indexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didUpdateObject, + name: ManagedObjectListControllerDidUpdateObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didUpdateObject: object, + atIndexPath: indexPath! + ) + } + } + ) + self.registerObjectNotification( + &NotificationKey.didMoveObject, + name: ManagedObjectListControllerDidMoveObjectNotification, + toObserver: observer, + callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didMoveObject: object, + fromIndexPath: indexPath!, + toIndexPath: newIndexPath! + ) + } + } + ) + + self.registerSectionNotification( + &NotificationKey.didInsertSection, + name: ManagedObjectListControllerDidInsertSectionNotification, + toObserver: observer, + callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didInsertSection: sectionInfo, + toSectionIndex: sectionIndex + ) + } + } + ) + self.registerSectionNotification( + &NotificationKey.didDeleteSection, + name: ManagedObjectListControllerDidDeleteSectionNotification, + toObserver: observer, + callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in + + if let observer = observer { + + observer.managedObjectList( + listController, + didDeleteSection: sectionInfo, + fromSectionIndex: sectionIndex + ) + } + } + ) + } + + public func removeObserver(observer: U) { HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a \(typeName(observer)) outside the main queue.") - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.willChangeList, inObject: observer) - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.didChangeList, inObject: observer) - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.didInsertObject, inObject: observer) - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.didDeleteObject, inObject: observer) - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.didUpdateObject, inObject: observer) - setAssociatedRetainedObject(nil as AnyObject?, forKey: &NotificationKey.didMoveObject, inObject: observer) + let nilValue: AnyObject? = nil + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didChangeList, inObject: observer) + + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertObject, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didMoveObject, inObject: observer) + + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertSection, inObject: observer) + setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteSection, inObject: observer) } + // MARK: FetchedResultsControllerHandler private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { @@ -263,6 +411,31 @@ public final class ManagedObjectListController: FetchedResul private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { + switch type { + + case .Insert: + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerDidInsertSectionNotification, + object: self, + userInfo: [ + UserInfoKeySectionInfo: sectionInfo, + UserInfoKeySectionIndex: NSNumber(integer: sectionIndex) + ] + ) + + case .Delete: + NSNotificationCenter.defaultCenter().postNotificationName( + ManagedObjectListControllerDidDeleteSectionNotification, + object: self, + userInfo: [ + UserInfoKeySectionInfo: sectionInfo, + UserInfoKeySectionIndex: NSNumber(integer: sectionIndex) + ] + ) + + default: + break + } } private func controllerWillChangeContent(controller: NSFetchedResultsController) { @@ -336,6 +509,77 @@ public final class ManagedObjectListController: FetchedResul private let fetchedResultsController: NSFetchedResultsController private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate private weak var parentStack: DataStack? + + private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController) -> Void) { + + setAssociatedRetainedObject( + NotificationObserver( + notificationName: name, + object: self, + closure: { [weak self] (note) -> Void in + + if let strongSelf = self { + + callback(listController: strongSelf) + } + } + ), + forKey: notificationKey, + inObject: observer + ) + } + + private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) { + + setAssociatedRetainedObject( + NotificationObserver( + notificationName: name, + object: self, + closure: { [weak self] (note) -> Void in + + if let strongSelf = self, + let userInfo = note.userInfo, + let object = userInfo[UserInfoKeyObject] as? T { + + callback( + listController: strongSelf, + object: object, + indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath, + newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath + ) + } + } + ), + forKey: notificationKey, + inObject: observer + ) + } + + private func registerSectionNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) { + + setAssociatedRetainedObject( + NotificationObserver( + notificationName: name, + object: self, + closure: { [weak self] (note) -> Void in + + if let strongSelf = self, + let userInfo = note.userInfo, + let sectionInfo = userInfo[UserInfoKeySectionInfo] as? NSFetchedResultsSectionInfo, + let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue { + + callback( + listController: strongSelf, + sectionInfo: sectionInfo, + sectionIndex: sectionIndex + ) + } + } + ), + forKey: notificationKey, + inObject: observer + ) + } } @@ -361,16 +605,6 @@ private final class FetchedResultsControllerDelegate: NSFetchedResultsController // MARK: NSFetchedResultsControllerDelegate - @objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { - - self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) - } - - @objc func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { - - self.handler?.controller(controller, didChangeSection: sectionInfo, atIndex: sectionIndex, forChangeType: type) - } - @objc func controllerWillChangeContent(controller: NSFetchedResultsController) { self.handler?.controllerWillChangeContent(controller) @@ -381,6 +615,16 @@ private final class FetchedResultsControllerDelegate: NSFetchedResultsController self.handler?.controllerDidChangeContent(controller) } + @objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + + self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) + } + + @objc func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { + + self.handler?.controller(controller, didChangeSection: sectionInfo, atIndex: sectionIndex, forChangeType: type) + } + @objc func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { return self.handler?.controller(controller, sectionIndexTitleForSectionName: sectionName) diff --git a/HardcoreData/ManagedObjectListObserver.swift b/HardcoreData/ManagedObjectListObserver.swift index 942800b..02f704b 100644 --- a/HardcoreData/ManagedObjectListObserver.swift +++ b/HardcoreData/ManagedObjectListObserver.swift @@ -27,23 +27,21 @@ import Foundation import CoreData -// MARK: - ManagedObjectListObserver +// MARK: - ManagedObjectListChangeObserver -public protocol ManagedObjectListObserver: class { +public protocol ManagedObjectListChangeObserver: class { typealias EntityType: NSManagedObject - + func managedObjectListWillChange(listController: ManagedObjectListController) - -// func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) -// -// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) -// -// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) -// -// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) - + func managedObjectListDidChange(listController: ManagedObjectListController) +} + + +// MARK: - ManagedObjectListObjectObserver + +public protocol ManagedObjectListObjectObserver: ManagedObjectListChangeObserver { func managedObjectList(listController: ManagedObjectListController, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) @@ -54,11 +52,19 @@ public protocol ManagedObjectListObserver: class { func managedObjectList(listController: ManagedObjectListController, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) - func managedObjectListDidChange(listController: ManagedObjectListController) - // // private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { // // return nil // } } + + +// MARK: - ManagedObjectListSectionObserver + +public protocol ManagedObjectListSectionObserver: ManagedObjectListObjectObserver { + + func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) + + func managedObjectList(listController: ManagedObjectListController, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) +} diff --git a/HardcoreData/ManagedObjectListSectionInfo.swift b/HardcoreData/ManagedObjectListSectionInfo.swift deleted file mode 100644 index 795b7e7..0000000 --- a/HardcoreData/ManagedObjectListSectionInfo.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// ManagedObjectListSectionInfo.swift -// HardcoreData -// -// Created by John Rommel Estropia on 2015/03/11. -// Copyright (c) 2015 John Rommel Estropia. All rights reserved. -// - -import UIKit - -class ManagedObjectListSectionInfo: NSObject { - -} diff --git a/HardcoreData/ManagedObjectObserver.swift b/HardcoreData/ManagedObjectObserver.swift new file mode 100644 index 0000000..c89f26a --- /dev/null +++ b/HardcoreData/ManagedObjectObserver.swift @@ -0,0 +1,41 @@ +// +// ManagedObjectObserver.swift +// HardcoreData +// +// Copyright (c) 2015 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation +import CoreData + + +// MARK: - ManagedObjectObserver + +public protocol ManagedObjectObserver: class { + + typealias EntityType: NSManagedObject + + func managedObjectWillUpdate(objectController: ManagedObjectController, object: EntityType) + + func managedObjectWasUpdated(objectController: ManagedObjectController, object: EntityType) + + func managedObjectWasDeleted(objectController: ManagedObjectController, object: EntityType) +} diff --git a/HardcoreData/README.md b/HardcoreData/README.md new file mode 100644 index 0000000..352a3d9 --- /dev/null +++ b/HardcoreData/README.md @@ -0,0 +1,101 @@ +# HardcoreData +[![Version](https://img.shields.io/cocoapods/v/HardcoreData.svg?style=flat)](http://cocoadocs.org/docsets/HardcoreData) +[![Platform](https://img.shields.io/cocoapods/p/HardcoreData.svg?style=flat)](http://cocoadocs.org/docsets/HardcoreData) +[![License](https://img.shields.io/cocoapods/l/HardcoreData.svg?style=flat)](http://cocoadocs.org/docsets/HardcoreData) + +Simple, elegant, and smart Core Data programming with Swift + +## Features +- Supports multiple persistent stores per *data stack*, just the way .xcdatamodeld files are supposed to. HardcoreData will also manage one *data stack* by default, but you can create and manage as many as you need. (see "Setting up") +- Ability to plug-in your own logging framework (or your favorite 3rd-party logger). (see "Logging and error handling") +- Makes it hard to fall into common concurrency mistakes. All Core Data tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability. (see "Saving and processing transactions") +- Provides convenient API for common use cases. (see "Fetching and querying") +- Pleasant API designed around Swift’s code elegance and type safety. (see "TL;DR sample codes") + +#### TL;DR sample codes + +Quick-setup: +```swift +HardcoreData.defaultStack.addSQLiteStore("MyStore.sqlite") +``` + +Simple transactions: +```swift +HardcoreData.beginAsynchronous { (transaction) -> Void in + let object = transaction.create(MyEntity) + object.entityID = 1 + object.name = "test entity" + + transaction.commit { (result) -> Void in + switch result { + case .Success(let hasChanges): println("success!") + case .Failure(let error): println(error) + } + } +} +``` + +Easy fetching: +```swift +let objects = HardcoreData.fetchAll(From(MyEntity)) +``` +```swift +let objects = HardcoreData.fetchAll( + From(MyEntity), + Where("entityID", isEqualTo: 1), + SortedBy(.Ascending("entityID"), .Descending("name")), + CustomizeFetch { (fetchRequest) -> Void in + fetchRequest.includesPendingChanges = true + } +) +``` + +Simple queries: +```swift +let count = HardcoreData.queryValue( + From(MyEntity), + Select(.Count("entityID")) +) +``` + + + +## Architecture +For maximum safety and performance, HardcoreData will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of HardcoreData before you use it in your apps. + +If you are already familiar with the inner workings of CoreData, here is a mapping of `HardcoreData` abstractions: + +| *Core Data* | *HardcoreData* | +| --- | --- | +| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`
(.xcdatamodeld file) | `DataStack` | +| `NSPersistentStore`
("Configuration"s in the .xcdatamodeld file) | `DataStack` configuration
(multiple sqlite / in-memory stores per stack) | +| `NSManagedObjectContext` | `BaseDataTransaction` subclasses
(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `DetachedDataTransaction`) | + +RestKit and MagicalRecord set up their `NSManagedObjectContext`s this way: + +nested contexts + +This ensures maximum data integrity between contexts without blocking the main queue. But as Florian Kugler's investigation found out, merging contexts is still by far faster than saving nested contexts. HardcoreData's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions*: + +nested contexts and merge hybrid + +This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts. + + + +## Setting up + + +## Saving and processing transactions + + +## Fetching and querying + + +## Logging and error handling + + +## Observing changes and notifications (currently in the works) + + + diff --git a/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj b/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj index f13caf5..111f166 100644 --- a/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj +++ b/HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.pbxproj @@ -9,15 +9,15 @@ /* Begin PBXBuildFile section */ B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */; }; B54AAD521AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD501AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld */; }; - B54AAD541AF4D26E00848AE0 /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD531AF4D26E00848AE0 /* MasterViewController.swift */; }; - B54AAD561AF4D26E00848AE0 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD551AF4D26E00848AE0 /* DetailViewController.swift */; }; + B54AAD541AF4D26E00848AE0 /* PalettesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD531AF4D26E00848AE0 /* PalettesViewController.swift */; }; B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; }; B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; }; B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; }; B54AAD6A1AF4D26E00848AE0 /* HardcoreDataDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54AAD691AF4D26E00848AE0 /* HardcoreDataDemoTests.swift */; }; B583A9201AF5F542001F76AF /* HardcoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* HardcoreData.framework */; }; B583A9211AF5F542001F76AF /* HardcoreData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* HardcoreData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - B583A92B1AF5FCE6001F76AF /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = B583A92A1AF5FCE6001F76AF /* Event.swift */; }; + B5BC26E41AF8F67900276889 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC26E31AF8F67900276889 /* PaletteTableViewCell.swift */; }; + B5BC26E81AF8FD9600276889 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BC26E71AF8FD9600276889 /* Palette.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -77,8 +77,7 @@ B54AAD4D1AF4D26E00848AE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B54AAD511AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = HardcoreDataDemo.xcdatamodel; sourceTree = ""; }; - B54AAD531AF4D26E00848AE0 /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; - B54AAD551AF4D26E00848AE0 /* DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = ""; }; + B54AAD531AF4D26E00848AE0 /* PalettesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PalettesViewController.swift; sourceTree = ""; }; B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; @@ -87,7 +86,8 @@ B54AAD691AF4D26E00848AE0 /* HardcoreDataDemoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HardcoreDataDemoTests.swift; sourceTree = ""; }; B583A9141AF5F4F3001F76AF /* HardcoreData.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = HardcoreData.xcodeproj; path = ../HardcoreData.xcodeproj; sourceTree = ""; }; B583A9251AF5F547001F76AF /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = GCDKit.framework; path = "/Users/johnestropia/Library/Developer/Xcode/DerivedData/HardcoreDataDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = ""; }; - B583A92A1AF5FCE6001F76AF /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; + B5BC26E31AF8F67900276889 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = ""; }; + B5BC26E71AF8FD9600276889 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,12 +133,11 @@ isa = PBXGroup; children = ( B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */, - B54AAD531AF4D26E00848AE0 /* MasterViewController.swift */, - B54AAD551AF4D26E00848AE0 /* DetailViewController.swift */, + B5BC26E51AF8F67D00276889 /* DataSources */, + B5BC26E61AF8F68200276889 /* Entities */, B54AAD571AF4D26E00848AE0 /* Main.storyboard */, B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */, B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */, - B583A92A1AF5FCE6001F76AF /* Event.swift */, B54AAD501AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld */, B54AAD4C1AF4D26E00848AE0 /* Supporting Files */, ); @@ -179,6 +178,23 @@ name = Products; sourceTree = ""; }; + B5BC26E51AF8F67D00276889 /* DataSources */ = { + isa = PBXGroup; + children = ( + B54AAD531AF4D26E00848AE0 /* PalettesViewController.swift */, + B5BC26E31AF8F67900276889 /* PaletteTableViewCell.swift */, + ); + name = DataSources; + sourceTree = ""; + }; + B5BC26E61AF8F68200276889 /* Entities */ = { + isa = PBXGroup; + children = ( + B5BC26E71AF8FD9600276889 /* Palette.swift */, + ); + name = Entities; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -307,9 +323,9 @@ files = ( B54AAD521AF4D26E00848AE0 /* HardcoreDataDemo.xcdatamodeld in Sources */, B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */, - B54AAD541AF4D26E00848AE0 /* MasterViewController.swift in Sources */, - B54AAD561AF4D26E00848AE0 /* DetailViewController.swift in Sources */, - B583A92B1AF5FCE6001F76AF /* Event.swift in Sources */, + B5BC26E41AF8F67900276889 /* PaletteTableViewCell.swift in Sources */, + B5BC26E81AF8FD9600276889 /* Palette.swift in Sources */, + B54AAD541AF4D26E00848AE0 /* PalettesViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift b/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift index c36a14e..4b53ebd 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift +++ b/HardcoreDataDemo/HardcoreDataDemo/AppDelegate.swift @@ -10,6 +10,17 @@ import UIKit import HardcoreData +let paletteList: ManagedObjectListController = { + + HardcoreData.defaultStack.addSQLiteStore() + return HardcoreData.defaultStack.observeObjectList( + From(Palette), + GroupBy("colorName"), + SortedBy(.Ascending("hue"), .Ascending("dateAdded")) + ) +}() + + // MARK: - AppDelegate @UIApplicationMain diff --git a/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard b/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard index c223371..fc47c7f 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard +++ b/HardcoreDataDemo/HardcoreDataDemo/Base.lproj/Main.storyboard @@ -1,102 +1,449 @@ - + + + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - + - + + + + + + + + - - + + - - - + + - + - + - + - - + + - - - - - - - - - - - - - - - - - - - + + + + + + + - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HardcoreDataDemo/HardcoreDataDemo/DetailViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/DetailViewController.swift deleted file mode 100644 index 1271414..0000000 --- a/HardcoreDataDemo/HardcoreDataDemo/DetailViewController.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// DetailViewController.swift -// HardcoreDataDemo -// -// Created by John Rommel Estropia on 2015/05/02. -// Copyright (c) 2015 John Rommel Estropia. All rights reserved. -// - -import UIKit - -class DetailViewController: UIViewController { - - @IBOutlet weak var detailDescriptionLabel: UILabel! - - - var detailItem: AnyObject? { - didSet { - // Update the view. - self.configureView() - } - } - - func configureView() { - // Update the user interface for the detail item. - if let detail: AnyObject = self.detailItem { - if let label = self.detailDescriptionLabel { - label.text = detail.valueForKey("timeStamp")!.description - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view, typically from a nib. - self.configureView() - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - -} - diff --git a/HardcoreDataDemo/HardcoreDataDemo/Event.swift b/HardcoreDataDemo/HardcoreDataDemo/Event.swift deleted file mode 100644 index 23ff52c..0000000 --- a/HardcoreDataDemo/HardcoreDataDemo/Event.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Event.swift -// HardcoreDataDemo -// -// Created by John Rommel Estropia on 2015/05/03. -// Copyright (c) 2015 John Rommel Estropia. All rights reserved. -// - -import Foundation -import CoreData - -class Event: NSManagedObject { - - @NSManaged var timeStamp: NSDate - -} diff --git a/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents b/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents index 44419ea..1939d96 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents +++ b/HardcoreDataDemo/HardcoreDataDemo/HardcoreDataDemo.xcdatamodeld/HardcoreDataDemo.xcdatamodel/contents @@ -1,12 +1,16 @@ - - + + + + + + - + \ No newline at end of file diff --git a/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/Contents.json b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/Contents.json new file mode 100644 index 0000000..33a7451 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "first.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/first.pdf b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/first.pdf new file mode 100644 index 0000000..47d911d Binary files /dev/null and b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/first.imageset/first.pdf differ diff --git a/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/Contents.json b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/Contents.json new file mode 100644 index 0000000..03bd9c9 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "second.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/second.pdf b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/second.pdf new file mode 100644 index 0000000..401614e Binary files /dev/null and b/HardcoreDataDemo/HardcoreDataDemo/Images.xcassets/second.imageset/second.pdf differ diff --git a/HardcoreDataDemo/HardcoreDataDemo/Info.plist b/HardcoreDataDemo/HardcoreDataDemo/Info.plist index 9bbdb32..880f2cf 100644 --- a/HardcoreDataDemo/HardcoreDataDemo/Info.plist +++ b/HardcoreDataDemo/HardcoreDataDemo/Info.plist @@ -43,8 +43,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight diff --git a/HardcoreDataDemo/HardcoreDataDemo/MasterViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/MasterViewController.swift deleted file mode 100644 index 5a553e8..0000000 --- a/HardcoreDataDemo/HardcoreDataDemo/MasterViewController.swift +++ /dev/null @@ -1,137 +0,0 @@ -// -// MasterViewController.swift -// HardcoreDataDemo -// -// Created by John Rommel Estropia on 2015/05/02. -// Copyright (c) 2015 John Rommel Estropia. All rights reserved. -// - -import UIKit -import HardcoreData - - -// MARK: - MasterViewController - -class MasterViewController: UITableViewController, ManagedObjectListObserver { - - // MARK: UIViewController - - override func viewDidLoad() { - - super.viewDidLoad() - - self.navigationItem.leftBarButtonItem = self.editButtonItem() - self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "addBarButtonItemTouched:") - - self.list.addObserver(self) - } - - - // MARK: UITableViewController - - required init!(coder aDecoder: NSCoder!) { - - HardcoreData.defaultStack.addSQLiteStore() - self.list = HardcoreData.defaultStack.observeObjectList( - From(Event), - SortedBy(.Ascending("timeStamp")) - ) - super.init(coder: aDecoder) - } - - - // MARK: UITableViewDataSource - - override func numberOfSectionsInTableView(tableView: UITableView) -> Int { - - return self.list.numberOfSections() - } - - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - return self.list.numberOfItemsInSection(section) - } - - override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as! UITableViewCell - cell.textLabel?.text = "\(self.list[indexPath].timeStamp)" - return cell - } - - - // MARK: UITableViewDelegate - -// override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { -// -// super.tableView(tableView, didSelectRowAtIndexPath: indexPath) -// } - - override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { - - switch editingStyle { - - case .Delete: - let event = self.list[indexPath] - HardcoreData.beginAsynchronous{ (transaction) -> Void in - - transaction.delete(transaction.fetch(event)!) - transaction.commit { (result) -> Void in } - } - - default: - break - } - } - -// optional func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) - - - // MARK: ManagedObjectListObserver - - func managedObjectListWillChange(listController: ManagedObjectListController) { - - self.tableView.beginUpdates() - } - - func managedObjectList(listController: ManagedObjectListController, didInsertObject object: Event, toIndexPath indexPath: NSIndexPath) { - - self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) - } - - func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: Event, fromIndexPath indexPath: NSIndexPath) { - - self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) - } - - func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: Event, atIndexPath indexPath: NSIndexPath) { - - self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) - } - - func managedObjectList(listController: ManagedObjectListController, didMoveObject object: Event, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { - - self.tableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: toIndexPath) - } - - func managedObjectListDidChange(listController: ManagedObjectListController) { - - self.tableView.endUpdates() - } - - // MARK: Private - - let list: ManagedObjectListController - - @objc dynamic func addBarButtonItemTouched(sender: AnyObject!) { - - HardcoreData.beginAsynchronous { (transaction) -> Void in - - let event = transaction.create(Event) - event.timeStamp = NSDate() - - transaction.commit { (result) -> Void in } - } - } -} - diff --git a/HardcoreDataDemo/HardcoreDataDemo/Palette.swift b/HardcoreDataDemo/HardcoreDataDemo/Palette.swift new file mode 100644 index 0000000..5ba43e6 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/Palette.swift @@ -0,0 +1,58 @@ +// +// Palette.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/05. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData + +class Palette: NSManagedObject { + + @NSManaged var dateAdded: NSDate + @NSManaged var hue: Int32 + @NSManaged var saturation: Float + @NSManaged var brightness: Float + + @objc dynamic var colorName: String { + + get { + + let key = "colorName" + self.willAccessValueForKey(key) + let value: AnyObject? = self.primitiveValueForKey(key) + self.didAccessValueForKey(key) + + if let colorName = value as? String { + + return colorName + } + + let colorName: String + switch self.hue % 360 { + + case 0 ..< 20: colorName = "Lower Reds" + case 20 ..< 57: colorName = "Oranges and Browns" + case 57 ..< 90: colorName = "Yellow-Greens" + case 90 ..< 159: colorName = "Greens" + case 159 ..< 197: colorName = "Blue-Greens" + case 197 ..< 241: colorName = "Blues" + case 241 ..< 297: colorName = "Violets" + case 297 ..< 331: colorName = "Magentas" + default: colorName = "Upper Reds" + } + + self.setPrimitiveValue(colorName, forKey: key) + return colorName + } + set { + + let key = "colorName" + self.willChangeValueForKey(key) + self.setPrimitiveValue(newValue, forKey: key) + self.didChangeValueForKey(key) + } + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/PaletteTableViewCell.swift b/HardcoreDataDemo/HardcoreDataDemo/PaletteTableViewCell.swift new file mode 100644 index 0000000..a616293 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/PaletteTableViewCell.swift @@ -0,0 +1,26 @@ +// +// PaletteTableViewCell.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/05. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit + +class PaletteTableViewCell: UITableViewCell { + + @IBOutlet weak var colorView: UIView? + @IBOutlet weak var label: UILabel? + + func setHue(hue: Int32, saturation: Float, brightness: Float) { + + let color = UIColor( + hue: CGFloat(hue) / 360.0, + saturation: CGFloat(saturation), + brightness: CGFloat(brightness), + alpha: 1.0) + self.colorView?.backgroundColor = color + self.label?.text = "H: \(hue)˚, S: \(round(saturation * 100.0))%, B: \(round(brightness * 100.0))%" + } +} diff --git a/HardcoreDataDemo/HardcoreDataDemo/PalettesViewController.swift b/HardcoreDataDemo/HardcoreDataDemo/PalettesViewController.swift new file mode 100644 index 0000000..9ffb6d7 --- /dev/null +++ b/HardcoreDataDemo/HardcoreDataDemo/PalettesViewController.swift @@ -0,0 +1,162 @@ +// +// PalettesViewController.swift +// HardcoreDataDemo +// +// Created by John Rommel Estropia on 2015/05/02. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit +import HardcoreData + + +// MARK: - PalettesViewController + +class PalettesViewController: UITableViewController, ManagedObjectListSectionObserver { + + // MARK: NSObject + + deinit { + + paletteList.removeObserver(self) + } + + + // MARK: UIViewController + + override func viewDidLoad() { + + super.viewDidLoad() + + self.navigationItem.leftBarButtonItems = [ + self.editButtonItem(), + UIBarButtonItem(barButtonSystemItem: .Trash, target: self, action: "resetBarButtonItemTouched:") + ] + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "addBarButtonItemTouched:") + paletteList.addObserver(self) + } + + + // MARK: UITableViewDataSource + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + + return paletteList.numberOfSections() + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return paletteList.numberOfObjectsInSection(section) + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCellWithIdentifier("PaletteTableViewCell") as! PaletteTableViewCell + let palette = paletteList[indexPath] + cell.setHue(palette.hue, saturation: palette.saturation, brightness: palette.brightness) + return cell + } + + + // MARK: UITableViewDelegate + + override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { + + switch editingStyle { + + case .Delete: + let palette = paletteList[indexPath] + HardcoreData.beginAsynchronous{ (transaction) -> Void in + + transaction.delete(palette) + transaction.commit { (result) -> Void in } + } + + default: + break + } + } + + override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + + return paletteList.sectionInfoAtIndex(section).name + } + + + // MARK: ManagedObjectListChangeObserver + + func managedObjectListWillChange(listController: ManagedObjectListController) { + + self.tableView.beginUpdates() + } + + func managedObjectListDidChange(listController: ManagedObjectListController) { + + self.tableView.endUpdates() + } + + + // MARK: ManagedObjectListObjectObserver + + func managedObjectList(listController: ManagedObjectListController, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) { + + self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) + } + + func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) { + + self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) + } + + func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) { + + let cell = self.tableView.cellForRowAtIndexPath(indexPath) as! PaletteTableViewCell + let palette = paletteList[indexPath] + cell.setHue(palette.hue, saturation: palette.saturation, brightness: palette.brightness) + } + + func managedObjectList(listController: ManagedObjectListController, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { + + self.tableView.moveRowAtIndexPath(fromIndexPath, toIndexPath: toIndexPath) + } + + + // MARK: ManagedObjectListSectionObserver + + func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { + + self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) + } + + func managedObjectList(listController: ManagedObjectListController, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { + + self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) + } + + + // MARK: Private + + @IBAction dynamic func resetBarButtonItemTouched(sender: AnyObject?) { + + HardcoreData.beginAsynchronous { (transaction) -> Void in + + transaction.deleteAll(From(Palette)) + transaction.commit { (result) -> Void in } + } + } + + @IBAction dynamic func addBarButtonItemTouched(sender: AnyObject?) { + + HardcoreData.beginAsynchronous { (transaction) -> Void in + + let palette = transaction.create(Palette) + palette.hue = Int32(arc4random_uniform(360)) + palette.saturation = 1.0 + palette.brightness = 0.5 + palette.dateAdded = NSDate() + + transaction.commit { (result) -> Void in } + } + } +} +