creating a Mutable array that can be added to in later clicks of the same button? - iphone

General noob questions:
(1) How can I create an NSMutable array in a buttonClicked action that I can add more entries to during subsequent clicks of the same button? I always seem to start over with a new array at every click (the array prints with only 1 entry which is the most recent button's tag in an NSLog statement).
I have about 100 buttons (one for each character in my string called "list") generated by a for-loop earlier in my code, and each has been assigned a tag. They are in a scrollview within the view of my ViewController.
I wish to keep track of how many (and which ones) of the buttons have been clicked with the option of removing those entries if they are clicked a second time.
This is what I have so far:
-(void) buttonClicked:(UIButton *)sender
NSMutableArray * theseButtonsHaveBeenClicked = [[NSMutableArray alloc] initWithCapacity: list.length];
NSNumber *sendNum = [NSNumber numberWithInt:sender.tag];
[theseButtonsHaveBeenClicked addObject:sendNum at index:sender.tag];
NSLog(#"%#",theseButtonsHaveBeenClicked);
}
(2) I have read that I may be able to use a plist dictionary but I don't really understand how I would accomplish that in code since I cant type out the items in the dictionary manually (since I don't know which buttons the user will click). Would this be easier if I somehow loaded and replaced the dictionary in a plist file? And how would I do that?
(3) I also have no idea how I should memory manage this since I need to keep updating the array. autorelease?
Thanks for any help you can provide!

Okay, firstly you are creating a locally scoped array that is being re-initialised on every call to buttonClicked:. The variable should be part of the class init cycle.
You will also be better off with an NSMutableDictionary instead of an NSMutableArray. With a dictionary we don't have to specify capacity and we can use the button's tags as dictionary keys.
Here's what you need to do, these three steps always go together: property/synthesize/release. A good one to remember.
//Add property declaration to .h file
#property (nonatomic, retain) NSMutableDictionary * theseButtonsHaveBeenClicked;
//Add the synthesize directive to the top of .m file
#synthesize theseButtonsHaveBeenClicked;
// Add release call to the dealloc method at the bottom of .m file
- (void) dealloc {
self.theseButtonsHaveBeenClicked = nil; // syntactically equiv to [theseButtonsHaveBeenClicked release] but also nulls the pointer
[super dealloc];
}
Next we create a storage object when the class instance is initialised. Add this to your class's init or viewDidLoad method.
self.theseButtonsHaveBeenClicked = [[NSMutableDictionary alloc] dictionary]; // convenience method for creating a dictionary
And your updated buttonClicked: method should look more like this.
-(void) buttonClicked:(UIButton *)sender {
NSNumber *senderTagAsNum = [NSNumber numberWithInt:sender.tag];
NSString *senderTagAsString = [[NSString alloc] initWithFormat:#"%#",senderTagAsNum];
// this block adds to dict on first click, removes if already in dict
if(![self.theseButtonsHaveBeenClicked objectForKey:senderTagAsString]) {
[self.theseButtonsHaveBeenClicked setValue:senderTagAsNum forKey:senderTagAsString];
} else {
[self.theseButtonsHaveBeenClicked removeObjectForKey:senderTagAsString]; }
[senderTagAsString release];
NSLog(#"%#", self.theseButtonsHaveBeenClicked);
}

Related

How can i lazy initialize a NSMutableArray?

How can i lazily initialize a NSMutableArray of Buttons ? I do something like this :
-(NSMutableArray *)roasteryButtons
{
if(!roasteryButtons)
{
roasteryButtons = [ NSMutableArray new];
//other code
}
return roasteryButtons;
}
And don't know what to do to call this lazy initializer ? i.e. I need to initialize the array so that i may set the frame for every button in the array
What u have done is correct. Instead of allocating the array in the init method of class, u are allocating the array only when required. Thus it serves the purpose of lazily allocating.
In the class, Wherever you want the array, you just call,
NSMutableArray *arr = [self roasteryButtons];
Also declare the method in header file as, -(NSMutableArray*)roasteryButtons;.
If you want the reference of the array in other classes, the call like,
[classObj roasteryButtons];
I have shown it as instance method. You can also declare that as class method, if you want like that.
And release that in -(void)dealloc method.
I guess you know when to call this method, right ?
The first thing is that you shouldn't use "new" method, but [[NSMutableArray alloc] init] instead : You should have a look at all existing [Init] methods available for NSArray : there are a bunch of them (with capacity, with objects, etc...)
Anyway, you should add some parameters to your method [roasteryButtons] : parameters that will help the method to know, for instance how many buttons to create, what is the frame where they have to show, etc. So this will look a bit like
-(NSMutableArray *)roasteryButtonsWithFrame:(*Frame) andNumbersOfButtons:(int)
for example...
or instead of parameters, you can pass a reference to a delegate that will be able to give answers to those questions (How many buttons, what's my frame and bounds, etc.) So in this case, the method will look like :
-(NSMutableArray *)roasteryButtonsWithDelegate:(id)
(This delegate should implement a protocol that you will create, containing the different methods that the delegate will have to respond to. ie methods like [howManyButtons]...)
The Perfect Way to Lazy initialize is as follow
in .h file declare your NSMUtableArray as property as follow
#property (nonatomic,strong) NSMutableArray *array;
Now in .m file synthesize it and do lazy initialize in getter like as follow:
#synthesize array=_array;
(NSMutableArray *) array
{
(!_array) _array=[[NSMutableArray alloc]init];
//this line is called lazy intialization..this line will create MutableArray at program //run time.
return _array
}
Now answer why we need this is that it take care about that if no NSMutableArray is created then it create it at programme run time and like this your app will not crash.
You could make your method a class method:
+(NSMutableArray *)roasteryButtons {
in this way you will be able to call it like this:
[MyRoasteryButtonClass roasteryButtons];
and this will return you your object.
Hope this helps.

Accessing NSDictionary problem

I'm only new to iPhone development, so my apologies if this is a silly question. I'm building various apps based on a book I'm reading and one of their suggestion was to build a mini web browser. I thought this would be easy, but while most of it is, I'm seriously struggling with the NSDictionary.
I have a UISegmentedControl used to display various bookmarks. The bookmark name that is displayed on the buttons of the UISegmentedControl is going to be my key and the url is the value associated with it.
I first try to declare an NSDictonary as a private (global variable), but since I could not get it to work, I resorted to declare it in my header file as follows:
#property (nonatomic, retain) NSDictionary *bookmarks;
I synthesize it and I initialized it in the viewDidLoad as follows:
- (void)viewDidLoad
{
bookmarks = [NSDictionary
dictionaryWithObjectsAndKeys:#"http://www.microsoft.com",
#"Microsoft",
#"http://www.google.com",
#"Google",
#"http://www.apple.com",
#"Apple",
#"http://msdn.microsoft.com",
#"MSDN", nil];
[super viewDidLoad];
}
I then associated a control with my segmented control and when the event is triggered and the function is called I've got the following code which is called:
- (IBAction) getShortcut:(id)sender
{
NSString *shortcut;
shortcut = [shortcuts titleForSegmentAtIndex:shortcuts.selectedSegmentIndex];
NSString *url = [bookmarks valueForKey:shortcut];
//[self navigateTo: url];
[url release];
}
When a button from the UISegmentedControl is clicked, I extract the value and stored it into shortcut and then I try to use the shortcut variable as a key to extract the associated value from the NSDictionary "bookmarks" but it keeps crashing on NSString *url = [bookmarks valueForKey:shortcut];
and bombs out of my function and displays the usual error EXC_BAD_ACCESS
Any help would be greatly appreciated.
T.
You have two options. one is to deal with the ivar directly as #Matt S. posted. Note that in this case you need to keep you object with enough retain count. You're using and auto released object and causing the error.
The other option is to use the property you already defined:
self.bookmarks = [[NSDictionary ...]];
And the property retains it.
That dictionary is autoreleased.
Try this:
self.bookmarks = [NSDictionary dictionaryWithObjectsAndKeys...]
You didn't retain the NSDictionary.
Do:
bookmarks = [[NSDictionary
dictionaryWithObjectsAndKeys:#"http://www.microsoft.com",
#"Microsoft", nil] retain];
The problem is, that you do not retain "bookmarks" in the viewDidLoad method. There is an naming convention mentioned somewhere in the Apple docs: If an intialisation method starts with "init..." the returned object is retained, if not you have to do it yourself. The "dictionaryWithObjectsAndKeys" returns and object with retain count 0, which means, that after the scope of assignment (your viewDidLoad method) it is immediatly released again.
So just put a
[bookmarks retain];
after your initalisation and you are done. Another solution which does the retaining for you
bookmarks = [[NSDictionary alloc] initWithObjectsAndKeys ...];
And you shouldn't release the url in your action. It gets released, once you release the dictionary

Passing data in an array from one class to another

hoping for advice on something.
I have a Levels Engine class that creates an NSMutable Array called levelsArray.
I am passing the data to a Levels View Controller which is working just fine.
I also have a Particle Emitter class to which I am hoping to pass the level data.
However I am constantly being told that the count level of the array is 0 when I pass it to the Particle Emitter class.
The array has been setup properly:
**LevelsEngine.h**
#interface
LevelsEngine : NSObject {
NSMutableArray *levelsArray; }
#property (retain) NSMutableArray
*levelsArray;
**LevelsEngine.m**
#synthesize levelsArray;
LevelsArray =[NSMutableArray array];
**Code used in ParticleEmitter.m**
newlevelsArray = [NSMutableArray array];
newlevelsArray=view.levelsArray;
Am I right in thinking I am having this error because I am trying to pass the array data from one NSObject to another and not to a view controller?If so how can I pass the data?
Couple of things.
**Code used in ParticleEmitter.m**
newlevelsArray = [NSMutableArray array];
newlevelsArray=view.levelsArray;
The first line is creating a new array.
The 2nd line is assigning newlevelsArray to be a pointer to the array in view.levelsArray, leaving the object you created in line #1 orphaned.
I think you were intending the 2nd line to be a field by field copy of the array, but assignments of objects don't work that way.
You can fix this by 2 things.
1) Remove the first line newlevelsArray = [NSMutableArray array];
2) Change the 2nd line to `newlevelsArray = [view.levelsArray copy];
This will actually do a copy, which is probably what you want since you can then go ahead and modify newlevelsArray in ParticleEmitter.m without changing the value in view.
Important note: don't forget to create a -dealloc: method in your Particle emitter class which releases newlevelsArray:
-(void)dealloc {
if (newlevelsArray) [newlevelsArray release];
[super dealloc];
}
An alternative solution is to use setters.
Instead of:
2) Change the 2nd line to newlevelsArray = [view.levelsArray copy];
Do:
2) Change the 2nd line to this.newlevelsArray = view.levelsArray;
Where you have to define newlevelsArray to be a property of the ParticleEmitter class using
#property (copy) NSMutableArray * newlevelsArray;
Note the use of "copy" instead of "retain". This will do a field by field copy of the array, which is most likely advisable for containers of mutable objects.
You need to change your code,
call the newlevelarray in the LevelsEngine.h calls.
and your code should look like
Classobject.newlevelsArray =[nsarray arraywitharray: LevlesArray] ;
This should solve your problem.

trying to add dictionary objects to an NSMutableArray - afterward array returns null?

I just did a search for my particular question and although the answers are close, I can't get my head around the answer...so I need some assistance.
I'd like to populate an array (NSMutableArray I suppose) with a group of dictionary objects that are parsed from JSON strings...the dictionary part I got, the JSON parsing I got, but when I try to put these objects into the NSMutableArray and NSLog it I get (null)... here are my steps (in a general way)
edit 1:
-The array I am creating is called NewFinalArray. it is an NSMutableArray, declared at the .h file and synthesized (and now alloc'd and init'd) as noted in the viewDidLoad method of the DetailViewController. It's contents are to be displayed in a UITableView.
-In DetailViewController, I have been successful in creating a plain NSArray/NSMutableArray and populating it with values that display in my UITableView.
-In the new scenario, I am receiving the information to be displayed through JSON strings which are retrievable through dictionary objects. I am using the Stig JSON libraries for iPHone/iPad. I have no problems there.
-All I wanted to do is getting the existing dictionary objects (which I can loop through from the existing array and see) and add them to a new Array to be used for displaying menu items in my UITableview.
I declared my mutableArray in my .h file
#interface blah : ...,...,...{
NSMutableArray *newFinalArray;
// other vars and IBOutlets
}
#property (nonatomic, retain) NSMutableArray *newFinalArray;
// other #property and (IBAction) stuff
#end
I then synthesize it in my .m file... I even alloc/inited it at viewDidLoad (it's a DetailViewController)
#synthesize this,that, newFinalArray; // keep track of newFinalArray, that's the one I want
- (void)viewDidLoad {
// other code
[[newFinalArray alloc] init]; // ya returns a warning, about not responding to alloc, but whatever (for now)
// I also tested of course without having to do that.
in my method that uses newFinalArray, the method is a recursive function that calls itself. each time it calls, it should add the dictionary object to the array (or does it?)
-(void)digTree:(NSArray *)array{
for (NSDictionary *dictionary in array){
// looping through the array
[self newFinalArray addObject:[dictionary]];
// more other code, and somewhere along the way I recurse
[self digTree:anotherArray];
}
}
when I try to NSLog (#"my final array is %#", newFinalArray) I get (null).
I am probably missing something here. I tried to add "nil" at the end. I am a little new/green to this , so if someone can lend a hand and let me know how to populate my newFinalArray with these dictionary objects it would be most appreciated.
[[newFinalArray alloc] init];
should be:
newFinalArray = [[NSMutableArray alloc] init];
This line is wrong too:
[self newFinalArray addObject:[dictionary]];
it should be:
[newFinalArray addObject:dictionary];
The first thing I notice that is wrong, is it should be:
newFinalArray = [[NSMutableArray alloc] init];
in viewDidLoad. See if that fixes it. It looks like there are other things wrong as well, so turn on warnings and see what else the compiler warns you about for hints.
How are the dictionaries stored? An alternative/probably easier way to do this would probably be to use arrayWithObjects:. Also, when using addObject:, there is no need to add nil (in fact, you can't add nil).

replaceObjectAtIndex array problems

Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.