Does anyone has sample code for ordering in UITableView using NSOrderedSet?
Had read many articles about reordering, but still don't understand how to do this in iOS5.
Hi i implemented it like this. The "currentObject" is root object of the relationship and "subItems" the name of the ordered relationship in the model which is managed by the UITableViewController.
- (void)moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSMutableOrderedSet* orderedSet = [self.currentObject mutableOrderedSetValueForKey:#"subItems"];
NSInteger fromIndex = fromIndexPath.row;
NSInteger toIndex = toIndexPath.row;
// see http://www.wannabegeek.com/?p=74
NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:fromIndex];
if (fromIndex > toIndex) {
// we're moving up
[orderedSet moveObjectsAtIndexes:indexes toIndex:toIndex];
} else {
// we're moving down
[orderedSet moveObjectsAtIndexes:indexes toIndex:toIndex-[indexes count]];
}
[self.dataStore saveObjectContext];
}
Related
Hello All & Thanks in Advance!
I am a noob with Core Data and I need to change data in one of my fields which is named: recid.
I have created a index which again is: recid as int 16 in my core data model.
What I am needing to do is fetch the record and changed recid from we will say 5 to 1 how would I go about doing this?
Here is the code I have built so far & I will take care of my loop after I understand how to change the data in the record.
-(void)awakeFromNib
{
NSMenu *theMenu;
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength] retain];
[statusItem setMenu:statusMenu];
[statusItem setImage:[NSImage imageNamed:#"TheJournal_16x16x32"]];
[statusItem setHighlightMode:YES];
theMenu = [[NSMenu alloc] initWithTitle:#""];
[theMenu addItemWithTitle:#"The Journal" action:#selector(showTheWindow:) keyEquivalent:#"W"];
[theMenu addItemWithTitle:#"Quit" action:#selector(terminate:) keyEquivalent:#"Q"];
[statusItem setMenu:theMenu];
[theMenu release];
NSUInteger count;
count = 0;
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
count = [prefs integerForKey:#"recid"];
NSLog(#"counter is >>>>%lu",(unsigned long)count);
[prefs setInteger:count forKey:#"recid"];
count++;
NSUserDefaults *prefs1 = [NSUserDefaults standardUserDefaults];
[prefs1 setInteger:count forKey:#"recid"];
NSLog(#"counter is >>>>%lu",(unsigned long)count);
// How I fetch the record & change the value from 5 to 1?
}
Here's a code sample that may help you. In this example, the YourManagedClass Core Data entity uses a UUID string as a unique record identifier. The extension contains a static function that fetches the unique record, sets the new recid value and then saves the NSManagedObjectContext.
import Foundation
import CoreData
class YourManagedClass: NSManagedObject {
#NSManaged var uuid: String?
#NSManaged var recid: NSNumber?
}
extension YourManagedClass {
static func set(recID: Int16, forObject uuid: String, `in` context: NSManagedObjectContext) {
let fetchRequest = NSFetchRequest<YourManagedClass>(entityName: "YourManagedClass")
fetchRequest.predicate = NSPredicate(format: "uuid = %#", argumentArray: [uuid])
let object: YourManagedClass
do {
let objects = try context.fetch(fetchRequest)
guard let foundObject = objects.first else {
return
}
object = foundObject
} catch {
// Handle Error
return
}
object.recid = NSNumber(value: recID)
do {
try context.save()
} catch {
// Handle Error
}
}
}
You would then call this function with a reference to your NSManagedObjectContext ('context' here):
YourManagedClass.set(recID: 15, forObject: "909455F3-C812-4399-83B4-F96A5C32A71D", in: context)
Two Entities
Gymnast is one to many to Meet
I would like to when I save a new meet, it gets assigned to as a meet to each gymnast where they can then score their individuals scores for each event
Maybe I completely wrong in my logic, but here is what I am trying to do
let request = NSFetchRequest(entityName: "Gymnast")
do {
let entities = try AD.managedObjectContext.executeFetchRequest(request) as! [Gymnast]
for item in entities {
if let first = item.valueForKey("firstName"), last = item.valueForKey("lastName") {
print("Name: \(first) \(last)")
let myMeet = NSEntityDescription.insertNewObjectForEntityForName("Meet", inManagedObjectContext: AD.managedObjectContext) as! Meet
myMeet.meetName = "Winter Classic"
let myMeets = item.meets!.mutableCopy() as! NSMutableSet
myMeets.addObject(myMeet)
item.meets = myMeets.copy() as? NSSet
AD.saveContext()
}
}
} catch {
}
}
}
I think it is not ideal to replicate the Meet object over and over again for each Gymnast. For example, the meetName will be stored multiple times. I am not sure this is intended.
However, going with your setup, your problem is how you assign the to-many relationship. For a one-to-many it is always easier to simply set the to-one relationship. (Remember, there is always a reverse relationship in the Core Data model.)
Thus,
myMeet.gymnast = item
is all you need.
To add and remove to-many relationships, you can use this extension:
// Support adding to many-to-many relationships
extension NSManagedObject {
func addObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.addObject(value)
}
func removeObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.removeObject(value)
}
}
My managedObjectContext hierarchy is as follows: (PSC)<-(writerMOC -- private)<-(mainMOC -- main)<-(backgroundMOC -- private)
I have an NSManagedObject who "name" property is "Banana".
In the backgroundMOC, I get a reference to the object with backgroundMOC.objectWithID, change the NSManagedObject's "name" property to "Apple", and subsequently set it's "syncStatus" property to 1 (flagged for synchronization), then recursively save the moc's with the following routine:
func saveManagedContext(moc: NSManagedObjectContext, shouldSync: Bool = true, completion: (() -> Void)? = nil)
{
print("\nSaving managed object context...")
do {
try moc.save()
if let parentContext = moc.parentContext {
parentContext.performBlock {
self.saveManagedContext(parentContext, shouldSync: shouldSync, completion: completion)
}
}
else {
if shouldSync { SyncEngine.sharedInstance.synchronize(shouldPushUpdates: true) }
completion?()
}
print("Finished saving managed object context...")
} catch {
logger.error("\(error)")
}
}
Once the last moc is saved, a sync routine is called which does its work on the backgroundMOC, which queries the local store for all objects whose syncStatus is 1, again this fetch is called on the backgroundMOC.
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
This correctly returns the updated object in the array, however, that object's syncStatus property equals 0, and its "name" property is still set to "Banana".
This is really causing me headaches, I felt like i had totally understood how managedObjectContext blocks should work, but this has proven to be quite a puzzle.
UPDATE
Here's the code that prompts the update. This is called from the main thread when the cell is tapped.
func updateNameForCell(cell: UITableViewCell)
{
///gets the object id from the fetchedResultsController
guard let fruitMetaID = tableController.objectIDForCell(cell) else { return }
let backgroundMOC = CoreDataController.sharedInstance.newBackgroundManagedObjectContext()
backgroundMOC.performBlock {
do {
guard let fruit = (backgroundMOC.objectWithID(fruitMetaID) as? FruitMetaData)?.fruit else {
throw //Error
}
print(fruit.name) // "Banana"
fruit.name = "Apple"
fruit.needsSynchronization() //Sets syncStatus to 1
CoreDataController.sharedInstance.saveManagedContext(backgroundMOC)
}
catch {
//handle error
}
}
}
UPDATE AGAIN
Maybe I'm not creating the contexts right. Enlighten me please!
/// The parent to all other NSManagedObjectContexts. Responsible for writting to the store.
lazy var writerManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator
}
return managedObjectContext
}()
lazy var mainManagedObjectContext: NSManagedObjectContext =
{
let managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.performBlockAndWait {
managedObjectContext.parentContext = self.writerManagedObjectContext
}
return managedObjectContext
}()
/// The context associated with background syncing..
func newBackgroundManagedObjectContext() -> NSManagedObjectContext
{
let backgroundManagedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
backgroundManagedObjectContext.performBlockAndWait {
backgroundManagedObjectContext.parentContext = self.mainManagedObjectContext
}
return backgroundManagedObjectContext
}
Holding onto child MOCs (children of the main context) is fraught with issues. I would recommend creating a new child (aka backgroundMOC) for each operation that you do.
Without seeing all of your code this looks like an issue with the child context getting out of sync.
Update
Assuming that your creation of the backgroundMOC sets the mainMOC as its parent then I wonder about the -objectWithID: and what it is returning.
I also wonder about your -performBlock: calls. In my head the threading looks fine but better to test. Try changing to -performBlockAndWait: just to test and see if there is a threading race condition. Not a permanent change but eliminates that part of the code as a source of the issue.
Before fetchRequest is called, you should reset context.
backgroundMOC.reset() // add this line
let fetchRequest = NSFetchRequest(entityName: entity.name)
let syncPredicate = NSPredicate(format: "%K == %d", JSONKey.SyncStatus.rawValue, 1)
fetchRequest.predicate = syncPredicate
return try backgroundMOC.executeFetchRequest(fetchRequest) as? [SyncableManagedObject] ?? []
The reason is FruitMetaData is an object(or class) so changing one of its properties/Core Data attributes does not register as a change to the results array ... the object references in the array remain the same.
And NSFetchRequest still returns the same result(by using cache). When use context.reset().This tells the context in the extension to fetch new data every time and ignore the cache.
I've just upgraded my project from cocos2d 1.0.1 to 2.0 and after a lot of tweaking and all, I'm unable to change the default color of a CCLabelTTF like I did before (And this way I avoid one line of code for each label I create). Before, I was doing like that :
In CCLabelTTF.m :
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions alignment:(CCTextAlignment)alignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size
{
if( (self=[super init]) ) {
dimensions_ = CGSizeMake( dimensions.width * CC_CONTENT_SCALE_FACTOR(), dimensions.height * CC_CONTENT_SCALE_FACTOR() );
alignment_ = alignment;
fontName_ = [name retain];
fontSize_ = size * CC_CONTENT_SCALE_FACTOR();
lineBreakMode_ = lineBreakMode;
color_ = ccBLACK;
[self setString:str];
}
return self;
}
I was changing the color inside this method since every "initWithString..." methods are returning this one, but even if I do so in cocos2D 2.0, it doesn't work.
Here's my new CCLabelTTF.m :
- (id) initWithString:(NSString*)str dimensions:(CGSize)dimensions hAlignment:(CCTextAlignment)alignment vAlignment:(CCVerticalTextAlignment) vertAlignment lineBreakMode:(CCLineBreakMode)lineBreakMode fontName:(NSString*)name fontSize:(CGFloat)size
{
if( (self=[super init]) ) {
// shader program
self.shaderProgram = [[CCShaderCache sharedShaderCache] programForKey:SHADER_PROGRAM];
dimensions_ = dimensions;
hAlignment_ = alignment;
vAlignment_ = vertAlignment;
fontName_ = [name retain];
fontSize_ = size;
lineBreakMode_ = lineBreakMode;
color_ = ccBLACK;
[self setString:str];
}
return self;
}
Is it because of the "ShaderProgram" thingy that wasn't there before 2.0? Please help I've tried everything so far :(
I even searched in all my project if there was a file containing "ccWHITE" or "{255,255,255}" but there's none related to CCLabelTTF (except for CCSprite, but if I change it to ccBLACK, all my sprites becomes black)
Instead of setting the ivar, use the accessor for the property:
self.color = ccBlack;
Also, you should not modify CCLabelTTF. If you want to change behaviour, make a subclass.
Using the Employees-Departments example what I want to do is bind a column to "Departments.arrangedObjects.employees.#sum.hoursWorked" as outlined below:
Entity Employee
attributes: "firstName", "lastName", "hoursWorked"
relationship: "departments"
Entity Department
attributes: "name"
relationship: "employees"
I want a table which will display some summary info about departments.
I bind the first column to my "Departments" array controller, "arrangedObjects.name".
I can have a column displaying the number of employees in a department by binding to "arrangedObjects.employees.#count"
However I can't get a sum of the hoursWorked by employees as I assume I might by binding to "arrangedObjects.employees.#sum.hoursWorked"
The error I get is along the lines of "[<_NSFaultingMutableSet 0x1acea0> addObserver:forKeyPath:options:context:] is not supported. Key path: #sum.hoursWorked"
I believe this is because it is not possible to bind to the many end of a to-many relationship. If so how can I do what I want to do?
For extra credit, say each employee also has another attribute, "race", I would also like my summaries table to show the number of unique races in each department.
Thanks in advance.
I encountered the same errors you did. It seems that while you can get the set of employees and perform some set of aggregate operations on it by doing something like:
Department* dept = ;
NSSet* employees = dept.employees;
NSNumber* sumOfHoursWorked = [employees valueForKeyPath: #"#sum.hoursWorked"];
There's a difference when you bind. Bindings are asking to observe the key path, not evaluate it once. Given that, it kinda, sorta makes sense why you can't bind to these key paths. Kinda. Sorta.
Now. As for a solution, what I usually do in cases like this is write a tiny little NSValueTransformer subclass to do just what I need, and then plug that into IB. This way, I write the ten lines of code I need, but don't end up doing the whole NSTableView data source spiel for want of a simple aggregate. In this case, you might do something like this:
// Declaration
#interface MySumOfHoursWorkedTransformer : NSValueTransformer
#end
#interface MyNumberOfRacesTransformer : NSValueTransformer
#end
// Implementation
#implementation MySumOfHoursWorkedTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
double hoursWorked = 0.0;
for (Employee* employee in [value valueForKey: #"employees"])
{
NSNumber* hoursWorkedNumber = employee.hoursWorked;
hoursWorked += hoursWorkedNumber ? [hoursWorkedNumber doubleValue] : 0.0;
}
retVal = [NSNumber numberWithDouble: hoursWorked];
}
return retVal;
}
#end
#implementation MyNumberOfRacesTransformer
+ (Class)transformedValueClass { return [NSNumber class]; } // class of the "output" objects, as returned by transformedValue:
+ (BOOL)allowsReverseTransformation { return NO; } // flag indicating whether transformation is read-only or not
- (id)transformedValue:(id)value // by default returns value
{
NSNumber* retVal = nil;
if ([value isMemberOfClass: [Department class]])
{
NSMutableSet* raceSet = [NSMutableSet set];
for (Employee* employee in [value valueForKey: #"employees"])
{
id raceVal = employee.race;
if (raceVal)
[raceSet addObject: raceVal];
}
retVal = [NSNumber numberWithUnsignedInteger: raceSet.count];
}
return retVal;
}
#end
Then, just bind those TableColumns to ArrayController.arrangedObjects and plug in the appropriate value transformer subclass. Now, you won't be able to edit those values, but what would it mean to edit an aggregate value anyway?
Hope that helps. I've used this approach a bunch, and it sure beats giving up on bindings.