I am very new to iOS, but I am on the verge of completing my app if I can get this error to go away. I'm experienced in C and C++, but objective-c has been rather confusing to me in the way in which things are done.
Header File:
#interface ThirdTableViewController : UITableViewController<MFMailComposeViewControllerDelegate>
-(id) init;
#property (strong, nonatomic) NSMutableArray *csvFileNames;
#property (strong, nonatomic) NSMutableArray *csvFilePaths;
- (IBAction)refreshTableButton:(id)sender;
- (IBAction)sendEmailButton:(id)sender;
void refreshTable();
#end
Implementation File:
void refreshTable(){
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSArray *documentArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil];
NSArray *csvFiles = [documentArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject hasSuffix:#".csv"];
}]];
_csvFileNames = csvFiles;
for (NSString *fileName in csvFiles) {
[_csvFilePaths addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
//NSLog(#"files array %#", _fileNamesArray);
//NSLog(#"files array %#", _filePathsArray);
}
I'm getting the errors where my two NSMutableArrays declared in the .h file are used in the .m file. These are the specific lines:
_csvFileNames = csvFiles;
[_csvFilePaths addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
These are the specific errors: Use of undeclared identifier '_csvFileNames', Use of undeclared identifier '_csvFilePaths'
In C++ if we are to implement a class function we do something like class::myfunction(parameters...). I assume my issue is somewhere along these lines.
The problem is with your refreshTable function. It's a function, not an instance method. Such a function has no access to any instance methods or variable of the class.
In the .h, change:
void refreshTable();
to:
- (void)refreshTable;
Update the .m:
void refreshTable(){
to:
- (void)refreshTable {
Then where ever you call it, change:
refreshTable();
to:
[self refreshTable];
Once you do that you will have other problems. You are attempting to assign an NSArray to a variable of type NSMutableArray. Change this code:
NSArray *csvFiles = [documentArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject hasSuffix:#".csv"];
}]];
_csvFileNames = csvFiles;
for (NSString *fileName in csvFiles) {
[_csvFilePaths addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
to:
NSArray *csvFiles = [documentArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject hasSuffix:#".csv"];
}]];
_csvFileNames = [NSMutableArray array];
for (NSString *fileName in csvFiles) {
[_csvFilePaths addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
Better yet, use your properties:
self.csvFileNames = [NSMutableArray array];
for (NSString *fileName in csvFiles) {
[self.csvFilePaths addObject:[documentsDirectory stringByAppendingPathComponent:fileName]];
}
In your refreshTable method, you declared csvFiles as NSArray and tried to assign it to _csvFileNames which is NSMutableArray.
You can't assign an variable NSArray to a NSMutableArray variable.
Related
I'm trying to add objects to an NSMutableArray but it keeps giving me this error.:
NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object
I have researched this problem, and I'm not doing anything wrong that past people have done, so I have no idea what's wrong. Here is my code:
Group.h
#property (strong, nonatomic) NSString *custom_desc;
#property (strong, nonatomic) NSMutableArray *attributes; //I define the array as mutable
Group.m
#import "Group.h"
#implementation Group
-(id)init
{
self = [super init];
if(self)
{
//do your object initialization here
self.attributes = [NSMutableArray array]; //I initialize the array to be a NSMutableArray
}
return self;
}
#end
GroupBuilder.m
#import "GroupBuilder.h"
#import "Group.h"
#implementation GroupBuilder
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
NSError *localError = nil;
NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
if (localError != nil) {
*error = localError;
return nil;
}
NSMutableArray *groups = [[NSMutableArray alloc] init];
NSDictionary *results = [parsedObject objectForKey:#"result"];
NSArray *items = results[#"items" ];
for (NSDictionary *groupDic in items) {
Group *group = [[Group alloc] init];
for (NSString *key in groupDic) {
if ([group respondsToSelector:NSSelectorFromString(key)]) {
[group setValue:[groupDic valueForKey:key] forKey:key];
}
}
[groups addObject:group];
}
for(NSInteger i = 0; i < items.count; i++) {
//NSLog(#"%#", [[items objectAtIndex:i] objectForKey:#"attributes"]);
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"]; //this returns a NSArray object understandable
Group *g = [groups objectAtIndex:i];
[g.attributes addObjectsFromArray:[att mutableCopy]]; //I use mutable copy here so that i'm adding objects from a NSMutableArray and not an NSArray
}
return groups;
}
#end
Use options:NSJSONReadingMutableContainers on your NSJSONSerialization call.
Then all the dictionaries and arrays it creates will be mutable.
According to the error message you are trying to insert an object into an instance of NSArray, not NSMutableArray.
I think it is here:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attrib`enter code here`utes"]; //this returns a NSArray object understandable
Items is fetched from JSON and therefore not mutable. You can configure JSONSerialization in a way that it creates mutable objects, but how exactly I don't know out of the top of my head. Check the references on how to do that or make a mutable copy:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"] mutableCopy];
Next try, considering your replies to the first attempt:
#import "Group.h"
#implementation Group
-(NSMutableArray*)attributes
{
return [[super attributes] mutableCopy];
}
#end
i have this object.
#interface SeccionItem : NSObject <NSCoding>
{
NSString * title;
NSString * texto;
NSArray * images;
}
#property (nonatomic,strong) NSString * title;
#property (nonatomic,strong) NSString * texto;
#property (nonatomic,strong) NSArray * images;
#end
With this implementation
#implementation SeccionItem
#synthesize title,texto,images;
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:title forKey:#"title"];
[encoder encodeObject:texto forKey:#"texto"];
[encoder encodeObject:images forKey:#"images"];
}
- (id)initWithCoder:(NSCoder *)decoder {
title = [decoder decodeObjectForKey:#"title"];
texto = [decoder decodeObjectForKey:#"texto"];
images = [decoder decodeObjectForKey:#"images"];
return self;
}
#end
I want to save an array filled with this objects to a file on disk.
Im doing this:
to write
[NSKeyedArchiver archiveRootObject:arr toFile:file];
to read
NSArray *entries = [NSKeyedUnarchiver unarchiveObjectWithFile:name];
return entries;
But the readed array is always empty, i dont know why, i have some questions.
What format should i use for file path? on toFile:?
The NSArray on the object is filled with NSData objects, so i can encode them?
Im really lost on this.
Take a look at the documentation of NSKeyedArchiver, especially the archiveWithRootObject:toFile: method.
The path is basically where the file should be stored including the file name. For example you can store your array in your app Documents folder with file name called Storage. The code snippet below is quite common:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString* docFile = [docDir stringByAppendingPathComponent: #"Storage"];
The method NSSearchPathForDirectoriesInDomains is used instead of absolute path because Apple can be changing the Documents folder path as they want it.
You can use the docFile string above to be supplied to the toFile parameter of the archiveWithRootObject:toFile: method.
Use the following method to save data
-(NSString*)saveFilePath {
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathString = [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"data"];
//NSString *pathString = [[NSBundle mainBundle]pathForResource:#"Profile" ofType:#"plist"];
return pathString;
}
-(void)saveProfile {
SeccionItem *data = [[SeccionItem alloc]init]
data. title = #"title";
data. texto = #"fdgdf";
data.images = [NSArray arrayWithObjects:#"dfds", nil];
NSMutableData *pData = [[NSMutableData alloc]init];
NSString *path = [self saveFilePath];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc]initForWritingWithMutableData:pData];
[data encodeWithCoder:archiver];
[archiver finishEncoding];
[pData writeToFile:path atomically:YES];
}
Use the following method to load data
-(void)loadData {
NSString* path = [self saveFilePath];
//NSLog(path);
NSMutableData *pData = [[NSMutableData alloc]initWithContentsOfFile:path];
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc]initForReadingWithData:pData];
data = [[SeccionItem alloc]initWithCoder:unArchiver];
//NSLog(#"%#",data.firstName);
[unArchiver finishDecoding];
}
For those who are seeking the solution in swift, I was able to write and read dictionary to file system as follows :
Write:
let data = NSKeyedArchiver.archivedData(withRootObject: dictionary)
do {
try data.write(to: destinationPath)
} catch let error {
print("\(error.localizedDescription)")
}
Read:
do
{
let data = try Data.init(contentsOf: path)
// path e.g. file:///private/var/ .... /Documents/folder/filename
if let dict = NSKeyedUnarchiver.unarchiveObject(with: data){
return dict
}
}
catch let error
{
print("\(error.localizedDescription)")
}
I have written the following method to encode/decode data..
- (void) encode: (BOOL) encodeBool int: (NSNumber *) integer boolean:(BOOL) boolean key: (NSString *) keyStr {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameData"];
if (encodeBool == YES) {
NSMutableData *gameData = [NSMutableData data];
NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameData];
if (integer) {
[encoder encodeInt:[integer intValue] forKey:keyStr];
}
else if (boolean) {
[encoder encodeBool:boolean forKey:keyStr];
}
[encoder finishEncoding];
[gameData writeToFile:gameStatePath atomically:YES];
[encoder release];
} else {
NSMutableData *gameData = [NSData dataWithContentsOfFile:gameStatePath];
if (gameData) {
NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:gameData];
if (integer) {
NSLog(#"%d", [decoder decodeIntForKey:keyStr]);
}
else if (boolean) {
if ([decoder decodeBoolForKey:keyStr]==YES) {
NSLog(#"YES");
} else {
NSLog(#"NO");
}
}
[decoder finishDecoding];
[decoder release];
}
}
}
And some testing
[[GameData sharedData] encode:YES int: [NSNumber numberWithInt:100] boolean:NO key:#"testInt"];
[[GameData sharedData] encode:YES int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:YES int:[NSNumber numberWithInt:1030] boolean:nil key:#"test"];
[[GameData sharedData] encode:NO int: [NSNumber numberWithInt:1] boolean:nil key:#"testInt"];
[[GameData sharedData] encode:NO int:nil boolean:YES key:#"bool"];
[[GameData sharedData] encode:NO int:[NSNumber numberWithInt:100] boolean:nil key:#"test"];
and output is
0
NO
1030
only the last one is correct.. Can someone tell me what I am doing wrong? Thanks
Your problem is that every time you call your method, you overwrite the file - erasing the values you encoded in previous calls. You should probably rewrite your method so that you encode all the values in a single call.
One alternative is to create a GameState object and have it implement NSCoding, then read and serialize it with +[NSKeyedArchiver archiveRootObject:toFile:] and deserialize it with +[NSKeyedUnarchiver unarchiveObjectWithFile:]. The code to do so looks a bit like this:
#interface GameState : NSObject <NSCoding>
#property (nonatomic) int someInt;
#property (nonatomic) BOOL someBool;
#property (nonatomic, strong) NSString *someString;
#end
static NSString *const BoolKey = #"BoolKey";
static NSString *const StringKey = #"StringKey";
static NSString *const IntKey = #"IntKey";
#implementation GameState
- (id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
_someBool = [coder decodeBoolForKey:BoolKey];
_someInt = [coder decodeIntForKey:IntKey];
_someString = [coder decodeObjectForKey:StringKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.someBool forKey:BoolKey];
[aCoder encodeInt:self.someInt forKey:IntKey];
[aCoder encodeObject:self.someString forKey:StringKey];
}
#end
// Somewhere in your app where reading and saving game state is needed...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = nil;
if ([paths count]) {
documentsDirectory = paths[0];
}
NSString *archivePath = [documentsDirectory stringByAppendingPathComponent:#"archive"];
GameState *gameState = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
if (!gameState) {
gameState = [[GameState alloc] init];
gameState.someString = #"a string";
gameState.someInt = 42;
gameState.someBool = YES;
}
// Make changes to gameState here...
[NSKeyedArchiver archiveRootObject:gameState toFile:archivePath];
The first problem is when you test if (boolean) it's the same as saying if (boolean == YES). Bools aren't objects, and can't be nil. When you pass nil in as a bool, it's the same as passing NO in. I don't think this accounts for all of your issues though. I think the file is not saving as well.
From the NSKeyedUnarchiver docs:
If you invoke one of the decode... methods of this class using a key
that does not exist in the archive, a non-positive value is returned.
This value varies by decoded type. For example, if a key does not
exist in an archive, decodeBoolForKey: returns NO, decodeIntForKey:
returns 0, and decodeObjectForKey: returns nil.
These are the erroneous values you're getting. To start, I note that you're not doing any error checking. Try adding some checks to see what's failing, for example, you could try:
[encoder finishEncoding];
NSError *error;
BOOL success = [gameData writeToFile:gameStatePath options:NSDataWritingAtomic error:&error];
if (success == NO) NSLog(#"Error: %#", [error localizedDescription]);
Once you get an error, we can go from there.
I am trying to load a plist into a UITableView. I am new to working with pLists and tableViews, but I know i need to use something along these lines. My problem is though that where "filePath" is, i don't actually know how to put in my pList?
list = [NSArray arrayWithContentsOfFile:filePath];
Any other suggestions with code how to to do this other than getting the file path would be greatly appreciated. Such as do i need to put anything in my .h file? Thanks.
Assuming you've already added a .plist to your project, I've created a class you can add to your project that will get and save information to a given .plist. It's a functioning singleton, so you can call it from anywhere.
First, create a new NSObject file called "GetAndSaveData", then post the following code into .h:
#interface GetAndSaveData : NSObject{
NSMutableDictionary *allData;
NSString *path;
}
+(GetAndSaveData *)sharedGetAndSave;
-(NSMutableArray *)arrayForKey:(NSString *)dataList;
-(void)setData:(NSMutableArray *)array ForKey:(NSString *)dataList;
#end
and the following code into .m:
static GetAndSaveData *sharedGetAndSave;
#implementation GetAndSaveData
-(id)init{
self = [super init];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); //1
NSString *documentsDirectory = [paths objectAtIndex:0];
path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
if (![fileManager fileExistsAtPath: path])
{
NSString *bundle = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
[fileManager copyItemAtPath:bundle toPath: path error:&error];
}
allData = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
return self;
}
-(NSMutableArray *)arrayForKey:(NSString *)dataList{
NSMutableArray *array = [allData objectForKey:dataList];
return array;
}
-(void)setData:(NSMutableArray *)array ForKey:(NSString *)dataList{
[allData setObject:array forKey:dataList];
[allData writeToFile:path atomically:YES];
if(![allData writeToFile:path atomically:YES])
{
NSLog(#".plist writing was unsuccessful");
}
}
+(GetAndSaveData *)sharedGetAndSave{
if (!sharedGetAndSave) {
sharedGetAndSave = [[GetAndSaveData alloc] init];
}
return sharedGetAndSave;
}
+(id)allocWithZone:(NSZone *)zone{
if (!sharedGetAndSave) {
sharedGetAndSave = [super allocWithZone:zone];
return sharedGetAndSave;
} else {
return nil;
}
}
-(id)copyWithZone:(NSZone *)zone{
return self;
}
#end
You can change the functions up to get and save different types of data. You can use it in view controllers by importing the .h file, and doing the following:
myMutableArray = [[GetAndSaveData sharedGetAndSave]arrayForKey:myKey];
Once more I come to the Internet, hat in hand. :)
I'm attempting to use a class method to return a populated array containing other arrays as elements:
.h:
#interface NetworkData : NSObject {
}
+(NSString*) getCachePath:(NSString*) filename;
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filePath;
+(NSString*) readFromFile:(NSString*) filePath;
+(void) loadParkData:(NSString*) filename;
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename;
#end
.m:
#import "NetworkData.h"
#import "JSON.h"
#import "Utility.h"
#implementation NetworkData
+(NSString*) getCachePath:(NSString*) filename {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *cachePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0], filename];
[paths release];
return cachePath;
}
+(void) writeToFile:(NSString*)text withFilename:(NSString*) filename {
NSMutableArray *array = [[NSArray alloc] init];
[array addObject:text];
[array writeToFile:filename atomically:YES];
[array release];
}
+(NSString*) readFromFile:(NSString*) filename {
NSFileManager* filemgr = [[NSFileManager alloc] init];
NSData* buffer = [filemgr contentsAtPath:filename];
NSString* data = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
[buffer release];
[filemgr release];
return data;
}
+(void) loadParkData:(NSString*) filename {
NSString *filePath = [self getCachePath:filename];
NSURL *url = [NSURL URLWithString:#"http://my.appserver.com"];
NSData *urlData = [NSData dataWithContentsOfURL:url];
[urlData writeToFile:filePath atomically:YES];
}
+(NSArray*) generateColumnArray:(int) column type:(NSString*) type filename:(NSString*) filename {
// NSLog(#"generateColumnArray called: %u %# %#", column, type, filename);
// productArray = [[NSMutableArray alloc] init];
// NSString *filePath = [self getCachePath:filename];
// NSString *fileContent = [self readFromFile:filePath];
// NSString *jsonString = [[NSString alloc] initWithString:fileContent];
// NSDictionary *results = [jsonString JSONValue];
// NSArray *eventsArray = [results objectForKey:type];
// NSInteger* eventsArrayCount = [eventsArray count];
// NSInteger* a;
// for (a = 0; a < eventsArrayCount; a++) {
// NSArray *eventsColSrc = [eventsArray objectAtIndex:a];
// NSArray *blockArray = [eventsColSrc objectAtIndex:column];
// [productArray addObject:blockArray];
// [blockArray release];
// }
// [eventsArray release];
// [results release];
// [jsonString release];
// [fileContent release];
// [filePath release];
// [a release];
// [eventsArrayCount release];
// return productArray;
}
-(void)dealloc {
[super dealloc];
}
#end
.. and the call:
NSArray* dataColumn = [NetworkData generateColumnArray:0 type:#"eventtype_a" filename:#"data.json"];
The code within the method works (isn't pretty, I know - noob at work). It's essentially moot because just calling it (with no active code, as shown) causes the app to quit before the splash screen reveals anything else.
I'm betting this is a headslapper - many thanks for any knowledge you can drop.
If your app crashes, there's very likely a message in the console that tells you why. It's always helpful to include that message when seeking help.
One obvious problem is that your +generateColumnArray... method is supposed to return a pointer to an NSArray, but with all the code in the method commented out, it's not returning anything, and who-knows-what is being assigned to dataColumn. Try just adding a return nil; to the end of the method and see if that fixes the crash. Again, though, look at the error message to see specifically why the code is crashing, and that will lead you to the solution.
Well, you're not returning a valid value from your commented out code. What do you use 'dataColumn' for next? Running under the debugger should point you right to the issue, no?