Issues declaring already existing NSMutableArray in new class - objective-c

I have a class (DataImporter) which has the code to download an RSS feed. I also have a view and separate class (TableView) which displays the data in a UITableView and starts the parsing process, storing parsed information in an NSMutableArray (items) which is located in the (TableView) subclass.
Now I wish to add a UIMapView which displays the items in the (items) NSMutableArray. Herein lies the issue - I need to somehow get the data from the (items) NSMutableArray into the new (mapView) subclass which I'm struggling with - and I preferably don't want to have to create a new class to download the data again for the mapView class when it already is in the applications memory. Is there a way I can transfer the information from the NSMutableArray (items) class to the (mapView) class (i.e. how do I declare the NSMutableArray in the (mapView) class)?
Here's a overview of how the system works:
App opened> Data downloaded (using DataImporter class) when (TableView) viewDidLoad runs> Data stored in NSMutableArray accessible by the (TableView) class> And from here I need to access and declare the array from a new (mapView) class.
Any help greatly appreciated, thanks.
Code for viewDidLoad MapKit:
Data *data = nil;
NSString *ilocation = [data locations];
NSString *ilocation2 = #"New Zealand";
NSString *inewlString;
inewlString = [ilocation stringByAppendingString:ilocation2];
NSLog(#"inewlString=%#",inewlString);
if(forwardGeocoder == nil)
{
forwardGeocoder = [[BSForwardGeocoder alloc] initWithDelegate:self];
}
// Forward geocode!
[forwardGeocoder findLocation: inewlString];
Code for parsing data into original NSMutable Array:
- (void)beginParsing {
NSLog(#"Parsing has begun");
//self.navigationItem.rightBarButtonItem.enabled = NO;
// Allocate the array for song storage, or empty the results of previous parses
if (incidents == nil) {
NSLog(#"Grabbing array");
self.datas = [NSMutableArray array];
} else {
[datas removeAllObjects];
[self.tableView reloadData];
}
// Create the parser, set its delegate, and start it.
self.parser = [[DataImporter alloc] init];
parser.delegate = self;
[parser start];
}

Since you have a property self.datas you can get a reference to the data just by calling that from your UIMapView, or by assigning to some target property in your map view from whatever object controls both views:
mapView.someArrayProperty = tableView.datas
However, you might want to rethink your overall program structure. Keeping what is in effect model data inside views is not good practice and tends make things pretty spaghetti-like as you add more objects and functionality.

Related

Objective-c PickerView displays memory locations instead of object variable names from singleton

Overview: I've got a MapKit map, a PickerView, and a static array of weapon objects (Each weapon having some member variable data). The intent is to use the picker view and display some information on my map (range, name).
Need some help with the array of objects stored in my Singleton. I'm passing the array successfully to the map but the picker is displaying the memory locations of the objects instead of the name.
SingletonFile.m where I create the array of objects
static dispatch_once_t pred;
static SingletonFile *shared = nil;
dispatch_once(&pred, ^{
shared = [[SingletonFile alloc] init];
shared.theWeapons = [NSMutableArray arrayWithArray:
#[[[weapon alloc] initWithName:#"M16" weaponPicName:#"M16 Pic Name"],
[[weapon alloc] initWithName:#"M20" weaponPicName:#"M20 Pic Name"],
[[weapon alloc] initWithName:#"M3" weaponPicName:#"MyName"]
]];
//This is my attempt to create a second array with only the weaponPicName's from theWeapons array created above.
shared.theWeaponNameArray = [shared.theWeapons valueForKey:#"weaponPicName"];
//This line produces the error, not key value coding-compliant for the key weaponName
//weaponName and weaponPicName are a part of the weapon object
});
return shared;
Weapon.m constructor for the weapon object
-(id)initWithName:(NSString*)weaponName weaponPicName:(NSString*)weaponPicName {
self = [super init];
if (self) {
_weaponName = weaponName;
_weaponPicName = weaponPicName;
}
return self;
}
MapViewController.m is where I get theWeapons array from my singleton class
//Initialize tableNames to the singleton name array for use in picker
self.tableNames = [SingletonFile weaponSingleton].theWeapons;
MapViewController.m (further down) is my picker code.
- (IBAction) pickType:(id)sender {
arrayPickerRows = self.tableNames;
[ActionSheetStringPicker showPickerWithTitle:#"Select"
rows:arrayPickerRows
initialSelection:0
doneBlock:^(ActionSheetStringPicker *picker, NSInteger selectedIndex, id selectedValue) {
selectedRow = [arrayPickerRows objectAtIndex:selectedIndex];
[self typePickerDone:sender];
}
cancelBlock:^(ActionSheetStringPicker *picker) {
NSLog(#"Block Picker Canceled");
}
origin:sender];
}
- (void) typePickerDone:(UIButton*) sender {
[self.WeaponPickerbutton setTitle:selectedRow forState:UIControlStateNormal];
}
I'm sure the answer has to do with accessing the weaponName of the objects. I've tried to create an array of weaponNames from the array of objects using valueForKey but it was saying my keys were non value compliant.
(Also aware I can do all this with a .plist but I'm trying to learn Singletons and work with arrays of objects)
Your arrayPickerRows stores a array of the weapons. When you set the title for the button, so when you use [arrayPickerRows objectAtIndex:selectedIndex], you will get the address of a weapon at index selectedIndex.
In your code, you don't have the definition for the variable selectedRow. However, it should be of class weapon. When setting the name of the button, you should use the following code:
[self.WeaponPickerbutton setTitle:selectedRow.weaponName forState:UIControlStateNormal];

Editing a local instance of a object changes the object in its main array

In my app I created a bunch of object classes for data that I save in NSUserDefaults.
I get the item by:
LauncherToDoItem *item = [[ActionHelper sharedInstance] actionList][indexPath.row];
then i pass it on to a editing view controller:
LauncherEditActionViewController *editActions = [[LauncherEditActionViewController alloc] initWithToDoItem:item];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:editActions];
[self presentViewController:navController animated:YES completion:nil];
In the view controller I have a table that shows data from a editing version of the item.
- (id)initWithToDoItem:(LauncherToDoItem *)toDoItem {
self=[super initWithStyle:UITableViewStyleGrouped];
if (self) {
item = toDoItem;
editedToDoItem = toDoItem;
}
return self;
}
When I edit the editedToDoItem it also writes the the item, so I assume it's also writing to the version in the array? Why is it that by editing 1 of them affect it all? I dont save it back to the array yet but values save automatically.
That's because both item and editedToDoItem point to the same object — and the same exact location in memory. When you write item = toDoItem, what you're really doing is saving the pointer to toDoItem inside the variable item. It's a reference to the original object, not a copy, because Objective-C object variables are pointers to the objects in memory.
Consider the following code:
NSMutableString *string = [#"Hello, world!" mutableCopy];
NSMutableString *a = string;
NSMutableString *b = string;
// string, a, and b point to the same exact object.
[string appendString:#" Hi again!"];
NSLog(#"%#", a); // => "Hello, world! Hi again!"
[b appendString:#" Whoa!"];
NSLog(#"%#", a); // => "Hello, world! Hi again! Whoa!"
When you fetch the item from your array, then store it twice in the editing controller, you're just passing around references to the same object, which, when edited, will reflect on all other references that you have — because they point to the same location in memory.
If you really want several different copies of the object (so that editing one doesn't affect the others), you have to actually copy the objects by making them conform to the NSCopying Protocol and using [item copy].

Saving an NSArray of custom objects

I've created a subclass of UIImage (UIImageExtra) as I want to include extra properties and methods.
I have an array that contains instances of this custom class.However when I save the array, it appears the extra data in the UIImageExtra class is not saved.
UIImageExtra conforms to NSCoding, but neither initWithCoder or encodeWithCoder are called, as NSLog statements I've added aren't printed.
My method to save the array looks like this:
- (void)saveIllustrations {
if (_illustrations == nil) {
NSLog(#"Nil array");
return;
}
[self createDataPath];
//Serialize the data and write to disk
NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_illustrations forKey:kIllustrationDataKey];
[archiver finishEncoding];
[data writeToFile:illustrationsArrayPath atomically: YES];
}
And the UIImageExtra has the following delegate methods for saving:
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(#"Encoding origin data!");
[super encodeWithCoder:aCoder];
[aCoder encodeObject:originData forKey:kOriginData];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:(NSCoder *) aDecoder]) {
NSLog(#"Decoding origin data");
self.originData = [aDecoder decodeObjectForKey:kOriginData];
}
return self;
}
My code to create the array in the first place looks like this (in case that offers any clues)
for (NSDictionary *illustrationDict in illustrationDicts) {
NSString *illustrationString = [illustrationDict objectForKey:#"Filename"];
NSNumber *xCoord = [illustrationDict objectForKey:#"xCoord"];
NSNumber *yCoord = [illustrationDict objectForKey:#"yCoord"];
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
//Scale the illustration to size it for different devices
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
NSValue *originData = [NSValue valueWithCGPoint:CGPointMake([xCoord intValue], [yCoord intValue])];
[scaledIllustration setOriginData:originData];
[self.illustrations addObject:scaledIllustration];
}
Or am I just going about saving this data the wrong way? Many thanks.
Your code to initialize the array is not actually creating instances of your UIImageExtra subclass.
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
returns a UIImage. Casting it doesn't do what you were intending.
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
is still just a UIImage.
One straightforward-but-verbose way to approach this would be to make UIImageExtra a wrapper around UIImage. The wrapper would have a class method for initializing from a UIImage:
+ (UIImageExtra)imageExtraWithUIImage:(UIImage *)image;
And then every UIImage method you want to call would have to forward to the wrapped UIImage instance-- also being careful to re-wrap the result of e.g. -adjustForResolution lest you again end up with an unwrapped UIImage instance.
A more Objective-C sophisticated approach would be to add the functionality you want in a Category on UIImage, and then use method swizzling to replace the NSCoding methods with your category implementations. The tricky part of this (apart from the required Objective-C runtime gymnastics) is where to store your "extra" data, since you can't add instance variables in a category. [The standard answer is to have a look-aside dictionary keyed by some suitable representation of the UIImage instance (like an NSValue containing its pointer value), but as you can imagine the bookkeeping can get complicated fast.]
Stepping back for a moment, my advice to a new Cocoa programmer would be: "Think of a simpler way. If what you are trying to do is this complicated, try something else." For example, write a simple ImageValue class that has an -image method and an -extraInfo method (and implements NSCoding, etc.), and store instances of that in your array.
You can't add objects to an NSArray after init. Use NSMutableArray, that might be the issue.

Where do I create global variables for an iOS app?

Here is my code:
I want to be able to create a global NSMutableArray that can store Budget* objects that can then be written to a .pList file... I'm only learning what pLists are, and I am a bit hazy about how to implement them...
Where am I going wrong here?
- (IBAction)btnCreateBudget:(id)sender
{
Budget *budget = [[Budget alloc] init];
budget.name = self.txtFldBudgetName.text;
budget.amount = [self.txtFldBudgetAmount.text intValue];
// Write the data to the pList
NSMutableArray *anArray = [[NSMutableArray alloc] init]; // I want this to be a global variable for the entire app. Where do I put this?
[anArray addObject:budget];
[anArray writeToFile:[self dataFilePath] atomically:YES];
/* As you can see, below is where I test the code. Unfortunately,
every time I run this, I get only 1 element in the array. I'm assuming
that this is because everytime the button is pressed, I create a brand new
NSMutableArray *anArray. I want that to be global for the entire app. */
int i = 0;
for (Budget * b in anArray)
{
i++;
}
NSLog(#"There are %d items in anArray",i);
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"BudgetData.plist"];
}
edit: I'd like to add that I am creating the anArray array so that it can be accessible by other views. I understand that this can be done with NSNotification? or Should I do this the appDelegate classes? The end goal is to have the anArray object populate a UITableView that is in a separate View.
Just put the declaration outside the method instead of inside it.
NSMutableArray *anArray = nil;
- (IBAction)btnCreateBudget:(id)sender
{
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
If it's only used inside the one file, make it "static" instead to prevent name collisions with other files:
static NSMutableArray *anArray = nil;
If it's only used inside the one method, make it "static" and put it inside that method:
- (IBAction)btnCreateBudget:(id)sender
{
static NSMutableArray *anArray = nil;
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
Note that people usually use some kind of naming convention for global variables, like "gArray", to easily differentiate them from local variables, instance variables, or method parameters.
Global variable is not necessary in this case. You can do something like this:
Read old data to mutable array (initWithContentsOfFile:).
Add new record to the array.
Save the array to same file.
But the second problem in your code is that if your Budget class is not a property list type (NSString, NSData, NSArray, or NSDictionary objects) writeToFile: will not save it sucessfully.
You need to make sure that your Budget class invokes NSCoder and then the NSCoder initWithCoder: and NSCoder decodeWithCoder: methods. Otherwise, writeToFile: will not work for you NSObject class.
But I digress. The answer to the original question should be the following.
In your .h file you need to do the following.
#interface WhateverClassName : UIViewController
{
NSMutableArray *anArray;
}
#property(nonatomic, retain) NSMutableArray *anArray;
#end
Then, you need to make sure you #synthesize the NSMutableArray so that you don't get any freaky warnings. This is done just after the #implementation line in your .m file.
Then, within the function that you want it to be allocated into memory, simply do the following.
anArray = [[NSMutableArray alloc] initWithObjects:nil];
This is now a global variable. It is global in the sense that it can be used from any function and is not limited to use in one function.
If you would like to have data accessible to the entire application or context ("global"), you can use a singleton. However, do this with care and make sure it is actually necessary and appropriate. I would suggest doing plenty of reading up on it prior to any implementation of a singleton. Carter Allen has a good basic implementation here.
According to "The end goal is to have the anArray object populate a UITableView that is in a separate View" you wouldn't need to write anything to a file, database or singleton. Just set the object. Like stated by Sebastien Peek.
If you wish for offline data storage, look into sqlite, json, plist , etc

How to Return a Custom Objective-C Object from a Class Method?

I have a set of Objective-C files in my app labeled Crop.h/Crop.m. Inside is a custom array of different vegetables from a video game - each vegetable has properties like a name, growing season, number of days to grow, etc.
I need to write a function which loops through this array and checks each Crop name value to see if it matches with my view title (self.title). Below is an example of two crop objects:
Crop *turnip = [[Crop alloc] init];
[turnip setName:#"Turnip"];
[turnip setSeason:#"Spring"];
[turnip setBagPrice:120];
[turnip setSaleValue:60];
[turnip setDaysToGrow:5];
[turnip setRenewableDays:0];
Crop *potato = [[Crop alloc] init];
[potato setName:#"Potato"];
[potato setSeason:#"Spring"];
[potato setBagPrice:150];
[potato setSaleValue:80];
[potato setDaysToGrow:8];
[potato setRenewableDays:0];
Then what I'm thinking is calling a new class function titled returnCropData: which takes the self.title as a parameter. I'm just not sure if this is my best method... I'd appreciate any suggestions from iOS devs.
Here's the simple class method I have so far - no data matching yet as I'm still trying to figure out which loop would be best. I'm also struggling to figure out the syntax to add an NSString parameter onto the function
+ (Crop *)returnCropData
{
Crop *turnip = [[Crop alloc] init];
[turnip setName:#"Turnip"];
[turnip setSeason:#"Spring"];
[turnip setBagPrice:120];
[turnip setSaleValue:60];
[turnip setDaysToGrow:5];
[turnip setRenewableDays:0];
// more crops here....
NSMutableArray *cropListing = [[NSMutableArray alloc] init];
[cropListing addObject:turnip];
[cropListing addObject:potato];
// add the other crops here...
return potato;
// return any Crop value for now
}
Apple's Objective-C docs recommend you use objectWithParams: grammar for class methods (constructors). So in your case, you should change your existing method name to
+ (Crop *)cropWithName:(NSString *)name
And call it with Crop *turnip = [Crop cropWithName:#"Turnip];
and then in your for loop you can check if the passed in name equals the name of your object with [name isEqualToString:turnip.name].
This will work, however it seems as though every time that method is called you're creating a ton of Crop objects - time intensive on your part, and memory intensive on the device. Instead, you should look into making a plist file that uses a dictionary to represent each kind of Crop, and then in your creation method you can use your passed in name to look up all the other information about the specified crop. Then you can just return one instance of Crop instead of instantiating a massive amount.
Here's a link to Apple documentation that explains plists:
https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/PropertyLists/Introduction/Introduction.html
Specifically, in any objects that I want to pull from a plist I define the following method that takes values from a dictionary and sets an object's properties:
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
[self setValuesForKeysWithDictionary:dictionary];
return self;
}
And then in a class where I want to access an array of data, I would create a synthesized #property and then define this method (in this case, I've got Song objects):
- (NSMutableArray *)songs
{
if (_songs == nil)
{
NSArray *songDicts = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ChillMix" ofType:#"plist"]];
_songs = [[NSMutableArray alloc] initWithCapacity:[songDicts count]];
// Fast enumeration //
for (NSDictionary *currDict in songDicts)
{
Song *song = [[Song alloc] initWithDictionary:currDict];
[_songs addObject:song];
}
}
return _songs;
}
Here is a sample project I've developed: https://github.com/Jlawr3nc3/MusicLibraryiOS
[NSArray arrayWithContentsOfFile:] should be your new best friend. It's awesome. If you can format your XML in plist format, you're down to one. single. line. to parse data. (PS normal XML is annoying as hell to parse on iOS so this is a huge time save if you can avoid straight up XML).
Also, I'm not sure what the use case would be for creating a crop object based on a view's title. Since you're already aware of the name of the crop before the view is instantiated, you could create a Crop object and set a Crop property of the view to the crop object you created, and then in viewDidLoad just do self.title = self.crop.name;. Finally, if you've got a UITableView of crop objects you shouldn't populate the table view with static text; instead populate it with objects you create from a plist, and then in didSelectRowAtIndexPath: you can do [self.arrayOfCrops objectAtIndex:indexPath.row] to get the object you tapped on, and pass it in with the view you load. Anyway, check out Apple's sample code for more info on plists and tableviews etc, 'cause they've got a lot of information on this exact stuff.