Add String to an Array with Objects - iphone

What I need to do is transfer Text from a text field into an array. here is what I am doing for getting the text:NSString *string = [NSString stringWithFormat#"%#", textfield.text];
Now how can I add this string to my array without loosing all of the objects already in the array?

You need to use NSMutableArray instead of a NSArray.
You will find a very useful method
- (void)addObject:(id)anObject
which
Inserts a given object at the end of the array.
A simple use of NSMutableArray
NSMutableArray *arMu = [[NSMutableArray alloc] initWithCapacity:10];
NSString *s = #"Some string";
[arMy addObject:s];
Also if you have a #property define for the IBOutlet I would do this instead
NSString *string = self.textfield.text;
Documentation is always a good place to have answers
writeToFile:atomically:
Writes the contents of the array to a file at a given path.
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
Parameters
path
The path at which to write the contents of the array.
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
flag
If YES, the array is written to an auxiliary file, and then the auxiliary file is renamed to path. If NO, the array is written directly to path. The YES option guarantees that path, if it exists at all, won’t be corrupted even if the system should crash during writing.
Return Value
YES if the file is written successfully, otherwise NO.
Discussion
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
Availability
Available in Mac OS X v10.0 and later.
See Also
– initWithContentsOfFile:
Declared In
NSArray.h

Related

How to save a NSMutableDictionary into a file in documents?

I would like to save the content of a NSMutableDictionary object to a file. How do I do this ? I already know how to do this task with a NSDictionary object but I don't know how to convert/copy this NSMutableDictionary to a NSDictionary...unless there's a method to write directly the content of NSMutableDictionary to a file...I stress that the NSMutableDictionary object contains objects of NSDictionary type.
Thx for helping,
Stephane
NSMutableDictionary is a subclass of NSDictionary: you can use it anywhere you'd use NSDictionary. Literally, just pass the object through to the same code you use for NSDictionary right now.
In a more general sense, if you ever actually need to get a truly immutable NSDictionary from an NSMutableDictionary, just call copy on the NSMutableDictionary.
Other approaches include [NSDictionary dictionaryWithDictionary:] or [NSDictionary alloc] initWithDictionary:], which all amount to essentially the same thing.
If you just swing over to the NSDictionary documents. You will see there is a method for saving a dictionary to a file
writeToFile:atomically:
Writes a property list representation of the contents of the dictionary to a given path.
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
Parameters
path:
The path at which to write the file.
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
flag:
A flag that specifies whether the file should be written atomically.
If flag is YES, the dictionary is written to an auxiliary file, and then the auxiliary file is renamed to path. If flag is NO, the dictionary is written directly to path. The YES option guarantees that path, if it exists at all, won’t be corrupted even if the system should crash during writing.
Return Value
YES if the file is written successfully, otherwise NO.
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
If the dictionary’s contents are all property list objects, the file written by this method can be used to initialize a new dictionary with the class method dictionaryWithContentsOfFile: or the instance method initWithContentsOfFile:.
So the piece of code you are looking for is probably something like:
[myDict writeToFile:path atomically:YES]
where myDict is the dictionary you have and path is the path to the location you want to save it to

Using PLists for Persistence on iPhone

Simple question about property lists within an iphone app. I know you can read data in from a plist, but is there a way to write user-inputted data to a plist? If so, how? It's easy to find tutorials on reading information from plists, but I'm having trouble finding resources on writing to plists.
This is how I write data items to a plist:
[myPlistFile setInteger: myInt forKey: #"someKey"];
Of course, you can change setInteger with setBool, etc for different types.
Hope this helps!
--
Edit:
If your .plist was a member of an important class or similar...
Header of myClass:
NSUserDefaults* myPreferences;
#property (nonatomic, retain) NSUserDefaults* myPreferences;
.m of myClass:
self.myPreferences = [NSUserDefaults standardUserDefaults]; // load our preferences
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile: [[NSBundle mainBundle]pathForResource: #"nameOfFile" ofType: #"plist"]]]; // now load the custom .plist file
In the docs for both NSArray and NSDictionary it shows they each have an instance method:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
For NSDictionary it describes this method as
Writes a property list representation of the contents of the dictionary to a given path.
For NSArray it says this in the discussion
This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
So essentially both of these will write plist's if the items that they contain can be used in plists e.g. Array, Dictionary, Boolean, Data, Date, Number and String

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

NSMutableArray object thinks it is an NSArray :(

So I have a property NSMutableArray *grades. At the only place where I set this property, I am doing this:
NSMutableArray *array = [[NSMutableArray alloc] init];
self.grades = array;
[array release];
[self.grades addObject:#"20"];
The last statement generates an exception: -[NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'.
What in the world am I missing?
It sounds like the property is set to copy, which means the synthesized accessor makes an immutable copy of the array
Make sure grades is a NSMutableArray.
Edit:
copy returns an immutable copy, so you can't make changes. From Apple's Objective-C docs:
Copy
If you use the copy declaration attribute, you specify that a value is copied during assignment. If you synthesize the corresponding accessor, the synthesized method uses the copy method. This is useful for attributes such as string objects where there is a possibility that the new value passed in a setter may be mutable (for example, an instance of NSMutableString) and you want to ensure that your object has its own private immutable copy. For example, if you declare a property as follows:
Although this works well for strings, it may present a problem if the attribute is a collection such as an array or a set. Typically you want such collections to be mutable, but the copy method returns an immutable version of the collection. In this situation, you have to provide your own implementation of the setter method, as illustrated in the following example.
Copying the entire collection on assignment is a heavy operation. Are you sure you don't want to retain the collection, or just assign it?
If you really want a mutable copy, then write your own setter as the docs suggest.
- (void)setGrades:(NSMutableArray *)array {
// make shallow/deep copy here, and assign to `grades`, not `self.grades`
}
What is grades declared as?
From the looks of the error message your declaring grades as an NSArray and while this is valid it does mean that you lose the mutability of the array.
To maintain the array as mutable you'll need to declare grades as an NSMutableArray as well.
edit:
In light of your edit the reason could be that your using the copy keyword in the property, this would mean that when your assigning the array using self.grades the synthesised setter method makes an immutable copy of array
self.grades probably returns an NSArray if declared as #property NSArray* grades seeing this the compiler freaks and does not want to support addObject: method. You have 2 options
cast it
like [(NSMutableArray*)self.grades
addObject:].
add the object before assigning the
array.

How to check if a NSMutableArray contains a value (null) or not?

I have an NSMutableArray instance which contains "N" number of objects and I want to check whether NSMutableArray contains a (null) or any other value.
In NSString there is a method called isEqualToString: Is there a similar method available for NSMutableArray?
Or otherwise, how can I do this?
NSMutableArray can only contain non-nil objects. Objects that print as (null) are typically nil.
The superclass method containsObject: will tell you whether a particular non-nil object is in the array. Note that this will only work if the objects are identical according to isEqual:.