Deleting Core Data objects from NSMutableArray - objective-c

I've got an NSMutableArray that currently holds a bunch of Core Data entities like so:
NSFetchRequest *fetchReq = [[NSFetchRequest alloc]init];
[fetchReq setEntity:[NSEntityDescription entityForName:#"Subject"
inManagedObjectContext:self.managedObjectContext]];
subjectsArray = [self.managedObjectContext executeFetchRequest:fetchReq error:nil];
I then put this array in a dictionary:
_childrenDictionary = [NSMutableDictionary new];
[_childrenDictionary setObject:subjectsArray forKey:#"SUBJECTS"];
This dictionary is then used as the data for a Source List I have implemented. What I'm having issues with is deleting an object from the subjectsArray. I've tried this with the following code:
_selectedSubject = [subjectsArray objectAtIndex:0];
[subjectsArray removeObject:subjectToDelete];
Now this works fine, when I execute the following:
for (Subject *s in subjectsArray) {
NSLog(#"Subjects: %#", [s title]);
}
The Subject I selected to delete is no longer there and the Source List I have updates correctly after calling:
[_sidebarOutlineView reloadData];
The problem I am having though is that when I quit the application and open it up again, the Subject I previously deleted is still there.
The fetching of the core data entities into the array and dictionary is done inside applicationDidFinishLaunching. At the moment all this code is inside the AppDelegate file, which has a .h file that looks like this:
#import <Cocoa/Cocoa.h>
#import "Subject.h"
#interface LTAppDelegate : NSObject <NSApplicationDelegate, NSOutlineViewDelegate, NSOutlineViewDataSource, NSMenuDelegate> {
IBOutlet NSWindow *newSubjectSheet;
IBOutlet NSWindow *newNoteSheet;
IBOutlet NSWindow *newEditSheet;
NSMutableArray *subjectsArray;
}
#property Subject *selectedSubject;
#property (assign)IBOutlet NSOutlineView *sidebarOutlineView;
#property NSArray *topLevelItems;
#property NSViewController *currentContentViewController;
#property NSMutableDictionary *childrenDictionary;
#property NSArray *allSubjects;
Any ideas as to what is causing to deleted Subject to reappear?

You are removing the objects from the local array, but not the CoreData store itself.
To remove an object from the Core Data store, you need to call managedObjectContext
deleteObject:theObject on it, and then call managedObjectContent save:&error to persist it.
If you are using table views, I would recommend checking out the NSFetchedResultsController as well.

You need to commit your changes to the NSManagedObjectContext for it to persist.
check out the save:&errorOut method on that class for details.

Related

Bind an NSArray with NSArrayController and display the results in NSTableView

I work on trivial core data, non document based cocoa app. There are 3 entities in my core data, and they have one to one relationship.
Entities can be seen on the image bellow:
I can manage to display the values in the console log, but I can't display them in the NSTableView.
Here is my code:
.h file:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface CombinedViewController : NSViewController <NSTableViewDataSource>
#property (nonatomic,strong) NSManagedObjectContext *mObjContext;
#property AppDelegate *appDelegate;
#property (strong) IBOutlet NSArrayController *combinedRecordsArrayController;
#property (nonatomic,strong)NSArray *websites;
#property (weak) IBOutlet NSTableView *combinedTableView;
#property(strong,nonatomic)NSString *hostingProvider;
#property(strong,nonatomic)NSString *customerName;
#property(strong,nonatomic)NSString *websiteUrl;
#end
.m file
#import "CombinedViewController.h"
#import "Website.h"
#import "Customer.h"
#import "Hosting.h"
#interface CombinedViewController ()
#end
#implementation CombinedViewController
- (void)viewDidLoad {
[super viewDidLoad];
_appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
self.mObjContext = _appDelegate.managedObjectContext;
}
-(void)viewDidAppear {
[self getCombinedResutls];
}
-(NSArray *)getCombinedResutls {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:self.mObjContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [self.mObjContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error:%#",error);
}
self.websites = [self.mObjContext executeFetchRequest:fetchRequest error:nil];
for (Website *ws in self.websites) {
self.hostingProvider = ws.hostingInfo.hostingProvider;
self.customerName = ws.customerInfo.customerName;
NSLog(#"Website: %#, Customer: %#, Hosting Provider: %#", ws.websiteUrl, self.customerName, self.hostingProvider);
}
return self.websites;
}
#end
I have an NSArrayController, named CombinedRecordsArrayController, which in attributes contoller has the following: Mode = Class, ClassName = CombinedViewContoller and both checkboxes are checked, those are Prepares Content and Editable. In binding, I have set the manage object context to file ownder, Model Key Path self.mObjContext.
NSTable is binded with this NSArrayContoller, and if I left only the first column, that is websiteUrl, I get the results.
NSLog line in my .m file, do print the results. However app crash with following error:
[ valueForUndefinedKey:]: the entity Website is not key value coding-compliant for the key "hostingProvider".
Any help will be deeply appreciated. I am struggling with this 4-5 days, and I can't solve it.
Core Data, NSArrayController and NSTableView is easy, if you know how to do it.
Array Controller:
Set Mode to Entity Name.
Set Entity Name to the name of the entity.
Check Prepares Content and Editable.
Bind Managed Object Context to the managed object context.
Table View:
Bind Content to the array controller, Controller Key arrangedObjects.
Bind Selection Indexes to the array controller, Controller Key selectionIndexes.
Bind Sort Descriptors to the array controller, Controller Key sortDescriptors.
Bind Value of the table view cell to Table Cell View, Model Key Path objectValue.websiteUrl. Check Conditionally Sets Editable.
That's it.
I think you need to generate NSManagedObject subclasses for your data model. If you haven't, load the data model file and look for the option under the Editor menu.
If that's not it, check out the troubleshooting core data page as it specifically mentions your error and a few solutions. Good luck buddy and let me know what works if anything. 4-5 days....I feel your pain.

Can't bind NSArray with NSArrayController

I have an array (_websites) which returns 2 results (i can see the records using NSLog).
What I am trying to do is to display those 2 records in NSTableView that has 3 columns. I make numerous attempts to bind the content of my array with the NSArrayController, without any success.
Here is the .h file
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
#interface CombinedViewController : NSViewController <NSTableViewDataSource>
#property (nonatomic,strong) NSManagedObjectContext *mObjContext;
#property AppDelegate *appDelegate;
#property (strong) IBOutlet NSArrayController *combinedRecordsArrayController;
#property (nonatomic,strong)NSArray *websites;
#property (weak) IBOutlet NSTableView *tableView;
#end
the .m file code:
#import "CombinedViewController.h"
#import "Website.h"
#import "Customer.h"
#import "Hosting.h"
#interface CombinedViewController ()
#end
#implementation CombinedViewController
- (void)viewDidLoad {
[super viewDidLoad];
_appDelegate = (AppDelegate*)[[NSApplication sharedApplication] delegate];
self.mObjContext = _appDelegate.managedObjectContext;
[self getCombinedResutls];
}
-(NSArray *)getCombinedResutls {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Website" inManagedObjectContext:self.mObjContext];
[fetchRequest setEntity:entity];
NSError *error = nil;
NSArray *fetchedObjects = [self.mObjContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Error:%#",error);
}
_websites = [_mObjContext executeFetchRequest:fetchRequest error:nil];
for (Website *ws in _websites) {
Customer *cust = ws.customerInfo;
Hosting *host = ws.hostingInfo;
NSLog(#"Website: %#, Customer: %#, Hosting Provider: %#",ws.websiteUrl, cust.customerName, host.hostingProvider);
}
return fetchedObjects;
}
#end
I am trying to learn how to do it both ways, using cocoa binding and programmatically, so any kind solution will be appreciated. Links with some up to date tutorial will be also very welcomed.
I just forgot to mention...I bind the NSArrayController with ContentArray in Controller Content, and then I bind the NSTableView with the NSArrayController, as well as my Table Column,but I am getting empty NSTableView...no error is shown in console whatsoever.
If you use direct iVar access-- _websites-- to write the websites property's iVar, the KVO notification that the binding depends upon never happens.
If you instead use self.websites = or more explicitly [self setWebsites: ..., then you will trigger a KVO notification to the array controller that the value of the websites property has been updated.
Instead, the array controller in the Xib is unarchived and bound to websites before viewDidLoad, so at that point, websites is nil. And subsequently, you never trigger any KVO notification about websites changing value because you explicitly avoid using the websites accessor setWebsites and instead use direct instance variable access. So the AC never knows that websites changes and the table never reflects any value for websites except nil.
In general never use the instance variable to access a property's value unless you have a very good reason to do so and fully understand why you're doing so.

Problems with an NSMutableArray

I'm trying to write an app that has two scenes in it. The first page is a UITableView that will contain a list of note entries. The second scene has 2 text fields (note summary and note description). I'm entering details on the second scene and then clicking a "Save" button which saves the data:
NoteListViewController.m
- (IBAction)saveAndGoBack:(id)sender {
NSLog(#"NoteDetailViewController.m: %#", NSStringFromSelector(_cmd));
NSString * desc = [[NSString alloc]init];
NSString * detail = [[NSString alloc]init];
desc = _noteTitle.text;
detail = _noteDesc.text;
[[NoteStore sharedStore]createNoteWithDesc:desc AndDetail:detail];
[self.navigationController popViewControllerAnimated:YES];
}
NoteStore is a static sharedStore that I am saving the data into.
NoteStore.m
-(Notes *)createNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail {
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
Notes * newNote = [[Notes alloc]initNoteWithDesc:desc AndDetail:detail];
[self.privateItems addObject:newNote];
return newNote;
}
So, the note is added to an NSMutableArray called "privateItems". I confirmed that the Note object gets added properly.
*****The problem happens when I try to retrieve the Note object (desc and detail) from the privateItems array later on using an accessor method which has a public property in the NoteStore.h file called allItems (it's an NSArray readonly, nonatomic and a copy):
NoteStore.m
-(NSArray *)allItems{
NSLog(#"NoteStore.m: %#", NSStringFromSelector(_cmd));
return [self.privateItems copy];
}
Everytime I try to retrieve it, the first property (desc) comes up as nil while the second property (detail) has the data I saved in the second text field of the second scene. Why is the first field constantly coming up as nil???
Just for clarity, a Note object is declared as follows
#interface Notes : NSObject
// What are the properties of a note?
#property (nonatomic, weak) NSString * noteDesc;
#property (nonatomic, weak) NSString * noteDetail;
#property (nonatomic, weak) NSString * test;
// Designated Initializer
-(instancetype)initNoteWithDesc:(NSString *)desc AndDetail:(NSString *)detail;
#end
Any help would be greatly appreciated.
When you call the designated initialiser you pass in two NSString objects. At this point they are owned by the method where they are created.
When they are assigned to the properties they only have a weak reference and therefor the retain count is not bumped up. Weak references are good for things like delegate objects. In this case you want your objects to stick around, so by declaring them as strong you're saying I want these properties to stick around in memory and take ownership of them.

[NSManagedObject sayHello]: unrecognized selector sent to instance 0x

I try to extend NSManagedObject.
Using XCode I created MyBox.m and MyBox.h (directly from the xcdatamodel file).
Then I modified these files:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface MyBox : NSManagedObject
#property (nonatomic, retain) NSDate * endDate;
#property (nonatomic, retain) NSNumber * globalId;
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSDate * startDate;
-(NSString *)sayHello;
#end
and
#import "MyBox.h"
#implementation MyBox
#dynamic endDate;
#dynamic globalId;
#dynamic name;
#dynamic startDate;
-(NSString *)sayHello {
return #"hello";
}
#end
I can fetch all myBoxes
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"MyBox" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSMutableArray *myBoxes = [context executeFetchRequest:fetchRequest error:&error];
but later I call
MyBox *myBox = [myBoxes objectAtIndex:indexPath.row];
[myBox sayHello];
it compiles but then I get
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSManagedObject sayHello]: unrecognized selector sent to instance 0x8e73fc0'
If I only read a value like
NSLog(#"%#", myBox.name);
it works
I found similar problems here, but no solution.
Thanks for your help.
I've just got the same issue. Solved it by changing class name to the name of my NSManagedObject subclass in myApp.xcdatamodeld -> configurations -> default -> entities -> myEntity.
Assuming you have set the class name correctly on the MyBox entity, I would guess that the app has an older version of your Core Data managed object model. Clean your build and delete the app on the simulator/device for good measure. To be 100% sure, also delete your derived data folder.
If it doesn't work after that, I'll bet that you haven't set the entity class name correctly. Print out your NSEntityDescription and make sure it is what you expect.
For Swift 5.0
This issue is present when you create the CoreData object in this way:
let object = CoreDataClass()
print(object.someProperty) // this is emit crash
I had the right class name set in xcdatamodeld, but I didn't include the class's .m file in my target. I had to click on the .m on the left sidebar, then check the correct box on the right sidebar under Target Membership.
Wrong Xcdatamodel.
I had the wrong xcdatamodel. It's a super dumb mistake but when you assume the latest model is 27 but your coworker changed it to 28 and you added your properties to model 27, it happens.You get these kind of errors and you assume it's something wrong with your Core Data model but it's simply your xcdatamodel number.
Gotta love programming =_=.

Pass array from one Objective-C class to another

Im attempting to pass an array that is created in one class into another class. I can access the data but when I run count on it, it just tells me that I have 0 items inside the array.
This is where peopleArray's data is set up, it's in a different class than the code that is provided below.
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
This is how I get the array from another class, but it always prints count = 0
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
The rest of my code:
data.h
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *peopleArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
data.m(some of it at least)
#implementation data
#synthesize viewController, managedObjectContext, peopleArray;
- (void)fetchRecords {
[self setupContext];
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
[mutableFetchResults release];
[request release];
//NSLog(#"this is an array: %#", eventArray);
}
login.h
#import <UIKit/UIKit.h>
#import "data.h"
#class rootViewController, data;
#interface login : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UIButton *loginButton;
UIButton *newUser;
UITextField *entry;
data *dataClass;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, assign) data *dataClass;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField;
#end
login.m
#import "login.h"
#import "data.h"
#interface login (PrivateMethods)
- (void)setUpFromTheStart;
- (void)loadDataScreen;
-(void)login;
#end
#implementation login
#synthesize viewController, dataClass;
-(void)login{
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
}
Is it the same object? If so, what you have should work. Check to see how you are getting the dataClass instance -- if you alloc a new one, you don't get the array from the other object.
Edit: From your comments below, it appears that you are having some confusion on the difference between classes and objects. I will try to explain (I'm going to simplify it):
A class is what you write in Xcode. It's the description that lets your application know how to create and access objects at run-time. It is used to figure out how much memory to allocate (based on instance variables) and what messages can be sent, and what code to call when they are. Classes are the blueprints for creating objects at runtime.
An object only exists at run-time. For a single class, many objects of that class can be created. Each is assigned its own memory and they are distinct from each other. If you set a property in one object, other objects don't change. When you send a message to an object, only the one you send it to receives it -- not all objects of the same class.
There are exceptions to this -- for example if you create class properties (with a + instead of a - at the beginning), then they are shared between all objects -- there is only one created in memory, and they all refer to the same one.
Also, since everything declared with a * is a pointer -- you could arrange for all pointer properties to point to the same data. The pointer itself is not shared.
Edit (based on more code): dataClass is nil, [dataClass peopleArray] is therefore nil, and then so is the count message call. You can send messages to nil, and not crash, but you don't get anything useful.
I don't see how the login object is created. When it is, you need to set its dataClass property.
Try running the code in the debugger, setting breakpoints, and looking at variables.
From the code, it looks like you are passing a mutable array.
[self setPeopleArray: mutableFetchResults];
Probably the items of the array are removed somewhere in your calling class / method. Or the array is reset by the class from which you get the mutableFetchResults in the first place.