Advice on dynamically altering default core data model content - objective-c

I've added the following image to help illustrate the problem better:
Hi,
I'm looking for the best starting point to alter the data stored my core data model directly - speaking as someone who's new to the area. From my reading I'm pretty confident I shouldn't touch my NSArrayController, which was my natural instinct, and that I should always tackle the model. This makes sense but because I've used bindings and core data, xcode has generated everything for me and I don't have a sense of building up a class from scratch myself.
For my initial task, I have a 'jobs' entity and NSArrayController. It has a jobTotalHours attribute that's a string in the 00:00:00 format and has a corresponding 'Hours' column for each job in an NSTableView. Separate to this, I have a stopwatch button that's linked to a text field next to it, displaying time as a 00:00:00 string. I have a class working that starts and stops a timer counting and displays it in increments of hours, minutes and seconds.
What I need to do is to make the timer add time onto the jobTotalHours attribute for the current job highlighted in the NSTableView. The separate textfield has now been bound to display the time of the current highlighted hours column so that part's taken care of. In other words, the timer was originally adding time to a test variable and displaying it in an autonomous text field for testing reasons. Now I need it to add time onto whatever job is highlighted in a table view and I need to access the model programmatically without being sure of what step to take first.
Thanks in advance for any advice. I'll include the timer class below if it's any use. I'm pretty sure it's rough and bad but it works:
timerController.h:
#import <Cocoa/Cocoa.h>
BOOL timerStarted;
int timerCount;
int timerSeconds;
int timerMinutes;
int timerHours;
NSString *timerString;
NSString *timerFieldSeconds;
NSString *timerFieldMinutes;
NSString *timerFieldHours;
#interface timerController : NSObject <NSApplicationDelegate> {
NSWindow *window;
NSTimer *timerNoOne;
IBOutlet NSCell *timerOneOutputLabel;
IBOutlet id timerClockField;
}
-(IBAction)toggleTimerClock:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#end
timerController.m:
#import "timerController.h"
#implementation timerController
-(IBAction)toggleTimerClock:(id)sender
{
if (timerStarted==FALSE) {
timerStarted = TRUE;
} else {
timerStarted = FALSE;
}
}
#synthesize window;
- (void) awakeFromNib {
// clear timer
[timerClockField setStringValue:#"00:00:00"];
// initialize timer to count each second
timerNoOne = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateTimerNoOne:)
userInfo:nil
repeats:YES];
}
- (void) updateTimerNoOne:(NSTimer *) timer {
if (timerStarted==FALSE) {
// do nothing. Timer is switched off.
} else {
timerCount = timerCount + 1;
timerSeconds = fmod(timerCount, 60);
timerMinutes = floor(timerCount / 60);
timerHours = floor(timerCount / 3600);
if (timerSeconds < 10) { // add a leading 0 for formatting reasons.
timerFieldSeconds = [NSString stringWithFormat:#"0%d",timerSeconds];
} else {
timerFieldSeconds = [NSString stringWithFormat:#"%d",timerSeconds];
}
if (timerMinutes < 10) {
timerFieldMinutes = [NSString stringWithFormat:#"0%d",timerMinutes];
} else {
timerFieldMinutes = [NSString stringWithFormat:#"%d",timerMinutes];
}
if (timerHours < 10) {
timerFieldHours = [NSString stringWithFormat:#"0%d",timerHours];
} else {
timerFieldHours = [NSString stringWithFormat:#"%d",timerHours];
}
NSString *timerString = [NSString stringWithFormat:#"%#:%#:%#",timerFieldHours,timerFieldMinutes,timerFieldSeconds];
//[timerClockField setStringValue:timerString];
}
}
#end
Update:
From reading some more, I'm wondering if it's a better approach for me to update the string in the textcell itself on each second of timer change and then only commit changes to the model on the timer finishing (e.g. the clock was stopped). Previously I was thinking of saving the model's jobTotalHours string second by second as this was directly altering the model and avoiding controllers, which I thought was the advised route to take.
Update:
I had a subclass set up for NSTableView and NSArrayController. I was able to use them to detect selection changes to the rows in the table and print them out to the console. The subclass was called:
#interface modelUtilController : NSObject
Which performed the above tasks fine. I now wanted an outlet to the NSManagedObject so that I could directly manipulate assets in it while keeping outlets to the NSTableView to detect changed in row selection. I read that the subclass should be
#interface modelUtilController : NSManagedObject
which I changed it to and included an outlet to the data model. This crashes the original detection for changes in row selection, so I'm doing something wrong now. Perhaps I have to separate the subclass into 2?
Update : Possibly Complete
Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.

Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.

Related

Coredata & NSPersistentDocument: Sum of column numbers crash

I have a problem with a #sum binding of a column in my program:
I'm doing a Coredata, NSPersistentDocument based program. I'm doing mostly everything from IB, the creation of the data model, NSArrayController and NSTableView...
I have just 1 entity with 62 attributes (61 NSString and 1 NSNumber). I import a CSV file with 12722 records. Import works well, can save to xml, binary, sqlite... I've double checked that the overall process works perfect. Can save/load. Everything is there.
The problem that I have: I've created a label that I BIND to #sum of the column with the NSNumber property. This is how I did
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: selection
> Model Key Path: #sum.myNumericAttribute
When I run the program, click on Import, Select ALL the rows, the #sum works well. It's fast, however and here is the first problem: once I save the file (tried all... binary/xml/sqlite) and later load it and try to Select ALL again, the program crash without error.
Tried through "Profile"->Allocations. I noticed:
I don't have memory leaks
When loading from disk and then select all: Goes extremelly slow. After 5 minutes didn't yet finished (I stopped it) and I saw +45MB of CFNumber (Live Bytes) and >1.500.00#Overall. So, something is wrong here, as I'm talking about 12722 rows/registers of type Interger32.
The second problem is the same but reproduced from a different angle. Instead of using "selection" I've tried to use "arrangedObjects".
In this case the problem appears even while importing from CSV, it goes extremely slow and it finally crash. Trying to open an already created file also crash.
This is how I did label->Bindings Inspector->Value
> label->Bindings Inspector->Value
> Bind to: My_Entity_NSArrayController
> Controller Key: arrangedObjects
> Model Key Path: #sum.myNumericAttribute
Can you please help me with some light on what to look for or ideas that can help me find where the problem is?.
Thanks a lot.
Luis
---- NEW EDIT AFTER MORE RESEARCH ----
I've found a workaround which I DONT' UNDERSTAND, please comments/answers really appreciated.
My program uses Coredata (SQLite), NSPersistentDocument, NSTableView and an NSArrayController. I want to have a working NSTextField bound to a #sum Collection Operation
Problem: As soon as I open an existing document with SQLite DB populated and I try to bind to the arrangedObjects.#sum.t_24_Bookings from the NSWindowController, the program crash.
My initial guess it's related to the Cannot access contents of an object controller after a nib is loaded however I've followed the recommendation of performing a first Fetch like this without success:
- (void) awakeFromNib
{
:
BOOL ok = [[self out_CtEn_Transaction] fetchWithRequest:nil merge:NO error:&error];
:
Continuing with this idea I've found that if I create a "real" complete Fetch + I perform a #sum access from the Document subclass, then it works.
Here is the code with comments I've put in place in order to have the workaround working.
ABDocument interface (a NSPersistentDocument subclass)
#interface ABDocument : NSPersistentDocument {
BOOL ivNewDocument;
NSArray *ivFetchedTransactions;
NSNumber *ivTotalBookings;
}
#property (nonatomic, getter=isNewDocument) BOOL newDocument;
#property (nonatomic, retain) NSArray *fetchedTransactions;
#property (nonatomic, retain) NSNumber *totalBookings;
:
ABDocument implementation
#import "ABDocument.h"
#import "ABWindowController.h"
#implementation ABDocument
#synthesize newDocument = ivNewDocument;
#synthesize totalBookings = ivTotalBookings;
#synthesize fetchedTransactions = ivFetchedTransactions;
:
/** #brief Create one instance of my custom NSWindowController subclass (ABWindowController)
*
* In my NSPersistentDocument I do override makeWindowControllers, where I create
* one instance of my custom NSWindowController subclass and use addWindowController:
* to add it to the document.
*
*/
- (void) makeWindowControllers
{
// Existing Document?
if ( ![self isNewDocument]) {
// NSLog(#"%#:%# OPENING EXISTING DOCUMENT", [self class], NSStringFromSelector(_cmd));
// Opening existing document (also has an existing DDBB (SQLite)), so
// make sure I do perform a first complete "fetch + #sum" to void issues
// with my NIB bind's.
[self firstFetchPreventsProblems];
}
// Now I can create the Window Controller using my "MainWindow.xib".
ABWindowController *windowController = [[ABWindowController alloc] init];
[self addWindowController:windowController];
[windowController release];
}
/** #brief First complete "fetch + #sum" to void issues with my NIB bind's.
*
* Before I create the Window Controller with "MainWindow.xib" I have to perform a
* first Fetch AND also retrieve a #sum of an NSNumber column.
*
* My NIB has an NSTextField BOUND to #arrangedObjects.#sum.<property> through a NSArrayController
* If I don't call this method before the NIB is loaded, then the program will crash.
*
*/
- (void) firstFetchPreventsProblems {
// Prepare the Fetch
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Transaction"];
// 1) Perform the Fetch
NSError *error = nil;
[self setFetchedTransactions:[[self managedObjectContext ] executeFetchRequest:request error:&error]];
if ([self fetchedTransactions] == nil)
{
NSLog(#"Error while fetching\n%#",
([error localizedDescription] != nil) ? [error localizedDescription] : #"Unknown Error");
exit(1);
}
// 2) Execute Collection Operation #sum
[self setTotalBookings:[[self fetchedTransactions] valueForKeyPath:#"#sum.t_24_Bookings"]];
}
ABWindowController (The controller that loads my NIB)
- (void)windowDidLoad
{
:
// PROGRAM CRASH HERE
// IF [self firstFetchToPreventsProblems]; is NOT CALLED
// ABDocument's "makeWindowControllers:"
[[self totalSumField] bind: #"value" toObject: [self out_CtEn_Transaction]
withKeyPath:#"arrangedObjects.#sum.t_24_Bookings" options:nil];
}
Please If you can comment really appreciated, I've got a solution but I don't understand why.
Tanks,
Luis
I found the problem myself after several days researching. It was easy (now that I know):
In parallel I was creating a secondary thread and happened that I was accessing the data model from two different threads. As it's been explained in several Q&As here in Stackoverflow, it's very dangerous.
I've applied the commented solutions in several posts of creating a secondary MOC in the secondary thread.
Now my code is thread safe as per coredata related actions, so program is not crashing.
Thanks again to the community.
Luis

Objective-C: Redrawing objects on screen

I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.

Subtract one from integer displayed in UILabel

I have a UILabel that adds one to its value -- basically it counts up -- when a button is pressed. Could someone please help me with a minus button? That way if the user accidentally presses the add button more than they needed, they can subtract their mistake. I've tried this, but the label's text is set to -1 now. and I want it to just subtract one each time its pressed:
- (IBAction)subtractButton:(id)sender {
static int integerSaved;
integerSaved = integer;
integerSaved -= 1;
[label2 setText:[NSString stringWithFormat:#"%i", integerSaved]];
}
Try this:
- (IBAction)subtractButton:(id)sender {
[label2 setText:[NSString stringWithFormat:#"%d",[label2.text intValue] - 1]];
}
-(void)subtract
{
label2.text = [NSString stringWithFormat:#"%d", [label2.text intValue]-1];
}
This code assumes that you are not using Interface Builder, and that you are manually linking "subtract" to the UIButton. If you are using Interface Builder, try this code.
-(IBAction)subtract:(id)sender
{
label2.text = [NSString stringWithFormat:#"%d", [label2.text intValue]-1];
}
I have not tested this but I think it should work. Good luck!
Using the value of the UIView instance to do arithmetic is not the MVC-way. You should really be separating your data model from your view.
Expose an int, NSInteger or NSNumber property in a class somewhere, where that class is dedicated to holding data values for your app.
When a touch event comes in for a given button, increment the data property and fire a notification that updates the view based on what's in the property. Or add an observer to the property. The observer then updates the view when the property value changes.
Following the MVC pattern would be a more "Apple"-ish or "iOS"-ish way of doing things, than getting the UIView's value, converting it to an integer, and then converting it back to a string.

Core-Data willSave: method

I have an attribute modificationDate in my Entity A. I want to set its value whenever NSManagedObject is saved. However, if i try to do that in NSManagedObject willSave: method, i get an error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to process pending changes before save. The context is still dirty after 100 attempts. Typically this recursive dirtying is caused by a bad validation method, -willSave, or notification handler.' ***
So, i'm wondering, what's the best way to set the value of modificationDate?
In fact the apple docs (which are only half read in the accepted answer) don't recommend this method. They explicitly say you should use NSManagedObjectContextWillSaveNotification. An example might be:
#interface TrackedEntity : NSManagedObject
#property (nonatomic, retain) NSDate* lastModified;
#end
#implementation TrackedEntity
#dynamic lastModified;
+ (void) load {
#autoreleasepool {
[[NSNotificationCenter defaultCenter] addObserver: (id)[self class]
selector: #selector(objectContextWillSave:)
name: NSManagedObjectContextWillSaveNotification
object: nil];
}
}
+ (void) objectContextWillSave: (NSNotification*) notification {
NSManagedObjectContext* context = [notification object];
NSSet* allModified = [context.insertedObjects setByAddingObjectsFromSet: context.updatedObjects];
NSPredicate* predicate = [NSPredicate predicateWithFormat: #"self isKindOfClass: %#", [self class]];
NSSet* modifiable = [allModified filteredSetUsingPredicate: predicate];
[modifiable makeObjectsPerformSelector: #selector(setLastModified:) withObject: [NSDate date]];
}
#end
I use this (with a few other methods: primary key for example) as an abstract base class for most core data projects.
From the NSManagedObject docs for willSave:
If you want to update a persistent property value, you should typically test for equality of any new value with the existing value before making a change. If you change property values using standard accessor methods, Core Data will observe the resultant change notification and so invoke willSave again before saving the object’s managed object context. If you continue to modify a value in willSave, willSave will continue to be called until your program crashes.
For example, if you set a last-modified timestamp, you should check whether either you previously set it in the same save operation, or that the existing timestamp is not less than a small delta from the current time. Typically it’s better to calculate the timestamp once for all the objects being saved (for example, in response to an NSManagedObjectContextWillSaveNotification).
So maybe something along the lines of:
-(void)willSave {
NSDate *now = [NSDate date];
if (self.modificationDate == nil || [now timeIntervalSinceDate:self.modificationDate] > 1.0) {
self.modificationDate = now;
}
}
Where you can adjust the 1.0 to reflect the minimum delta between your expected save requests.
Actually a much better way than the accepted answer would be to use primitive accessors, as suggested in NSManagedObject's Documentation
`
- (void)willSave
{
if (![self isDeleted])
{
[self setPrimitiveValue:[NSDate date] forKey:#"updatedAt"];
}
[super willSave];
}
`
Also, check whether the object is marked for deletion with -isDeleted, as -willSave gets called for those too.
There are obviously several good solutions to this question already, but I wanted to throw out a new one that worked best for one particular scenario I encountered.
(In Swift:)
override func willSave() {
if self.changedValues()["modificationDate"] == nil {
self.modificationDate = NSDate()
}
super.willSave()
}
The reason I needed this is because I have the peculiar requirement of needing to sometimes set the modificationDate manually. (The reason I sometimes set the time stamp manually is because I try to keep it in sync with a time stamp on the server.)
This solution:
Prevents the infinite willSave() loop because once the time stamp is set, it will appear in changedValues()
Doesn't require using observation
Allows for setting the time stamp manually
Swift 4 solution which is a combination of zmit and Richard answer without the need of recurring to NSNotification:
override func willSave() {
let expectedNewValue = "Your new value"
if customField != expectedNewValue, changedValues()[#keyPath(Entity.customField)] == nil, !isDeleted {
customField = expectedNewValue
}
super.willSave()
}

Objective C: constantly calling a function

I'm trying to make a pretty simple game and I'm stuck. Basically I want to make a UIImageView appear every 2 seconds. My problem is I can't keep track of cumulative time. Right now I have this:
NSDate *date = [NSDate date];
NSTimeInterval secondsSinceNow = [date timeIntervalSinceNow];
NSLog(#"date = %#", date);
NSLog(#"secondsSinceNow = %f", secondsSinceNow);
It's in my button function so its called when the user taps the button. It returns a decimal number always less than 1. I've tried it in the viewDidLoad method as well as it's own method but neither work.
I think it would work if its in it's own method that is check constantly, but I don't know how to do that.
In short, I need a timer/counter that updates every second.
#interface className
{
NSTimer * timer;
}
#property (nonatomic, retain) NSTimer * timer;
#end
#implementation className
#synthesize timer;
...
/*factory method was corrected here. should work without warnings by copying and pasting */
-(void) applicationDidFinishLaunching : (UIApplication *) application {
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(targetMethod:) userInfo:nil repeats: YES];
}
//define the target method
/*method was corrected because it needed parentheses around NSTimer */
-(void) targetMethod: (NSTimer *) theTimer {
NSLog(#"Me is here at 1 minute delay");
}
..
#end
taken from here
http://www.iphonedevsdk.com/forum/iphone-sdk-development/14403-nstimer-examples.html
If those first two lines are called immediately after each other, then it (should) always be less than a second. The date is being instantiated right there, and then the timeIntervalSinceNow is called immediately on it, when little/no time has occured between them. The goal is to make the date when first called, and then call the timeIntervalSinceNow on that to get times more than 0. However, this still has no creation of a updating timer as you want.
You could simply use an NSTimer to call a selector within your class at the required two second interval.
That said, you could possibly also make use of a CABasicAnimation to fade the opacity of the UIImageView, pending on the effect you require.