iOS: Simple question about the app design and how to share a NSDictionary among viewControllers - objective-c

I have a simple design question. This code is in my appDelegate. I use it to load all images in a NSDictionary categories. Successively I pass the dictionary to the init method of the main NavigationViewController. Successively I pass the dictionary through all viewControllers pushed by the NavigationViewController, because I'm using the same icons everywhere.
I was wondering if this is the correct approach, or am I just wasting memory. In other terms, should I pass the dictionary through viewControllers, or should I use a singleton.. or what ? The reason I'm currently adopting this approach is that I don't have any reference from viewControllers to the app delegate.
//load categories pictures
NSArray *categoriesKeys = [[NSArray alloc] initWithObjects:
#"comedy",
#"commercial",
#"education",
#"family",
#"media",
#"music",
#"performing",
#"sport",
nil];
categories = [[NSMutableDictionary alloc] init];
for (NSString *categKey in categoriesKeys) {
UIImage * categImage = [UIImage imageNamed:[[#"icons/" stringByAppendingString:categKey] stringByAppendingString:#".png"]];
[categories setObject:categImage forKey:categKey];
}
Update:
viewController init method
FlickrAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
categories = appDelegate.categories;
categKeys = appDelegate.categoriesKeys;

I would probably create a Singleton. A lot of people like placing shared code in the AppDelegate, but then that file gets cluttered and it becomes unclear what is going on throughout the file. Having a Singleton dedicated to this logic separates the code and makes it easier to change.

put all the code in your appDelegate in a function and call that function in applicationDidFinishLaunching method . Access dictionary anywhere in the application like this
appNameDelegate* appDelegate = [UIApplication sharedApplication].delegate;
[appdelegate.categories objectForKey:#"WhatEver"];

Related

Nested NSCollectionView With Bindings

I am trying to nest NSCollection view inside of one another. I have tried to create a new project using the Apple Quick Start Guide as a base.
I start by inserting a collection view into my nib, to the view that is automatically added I drag another collection view onto it. The sub-collection view added gets some labels. Here is a picture of my nib:
I then go back and build my models:
My second level model .h is
#interface BPG_PersonModel : NSObject
#property(retain, readwrite) NSString * name;
#property(retain, readwrite) NSString * occupation;
#end
My First level model .h is:
#interface BPG_MultiPersonModel : NSObject
#property(retain, readwrite) NSString * groupName;
#property(retain,readwrite) NSMutableArray *personModelArray;
-(NSMutableArray*)setupMultiPersonArray;
#end
I then write out the implementation to make some fake people within the first level controller(building up the second level model):
(edit) remove the awakefromnibcode
/*- (void)awakeFromNib {
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
[self setPersonModelArray:tempArray];
} */
-(NSMutableArray*)setupMultiPersonArray{
BPG_PersonModel * pm1 = [[BPG_PersonModel alloc] init];
pm1.name = #"John Appleseed";
pm1.occupation = #"Doctor";
//similar code here for pm2,pm3
NSMutableArray * tempArray = [NSMutableArray arrayWithObjects:pm1, pm2, pm3, nil];
return tempArray;
}
Finally I do a similar implementation in my appdelegate to build the multiperson array
- (void)awakeFromNib {
self.multiPersonArray = [[NSMutableArray alloc] initWithCapacity:1];
BPG_MultiPersonModel * mpm1 = [[BPG_MultiPersonModel alloc] init];
mpm1.groupName = #"1st list";
mpm1.personModelArray = [mpm1 setupMultiPersonArray];
(I'm not including all the code here, let me know if it would be useful.)
I then bind everything as recommended by the quick start guide. I add two nsarraycontrollers with attributes added to bind each level of array controller to the controller object
I then bind collectionview to the array controller using content bound to arrangedobjects
Finally I bind the subviews:
with the grouptitle label to representedobject.grouptitle object in my model
then my name and occupation labels to their respective representedobjects
I made all the objects kvo compliant by including the necessary accessor methods
I then try to run this app and the first error I get is: NSCollectionView item prototype must not be nil.
(edit) after removing awakefromnib from the first level model I get this
Has anyone been successful at nesting nscollection views? What am I doing wrong here? Here is the complete project zipped up for others to test:
http://db.tt/WPMFuKsk
thanks for the help
EDITED:
I finally contacted apple technical support to see if they could help me out.
Response from them is:
Cocoa bindings will only go so far, until you need some extra code to make it all work.
When using arrays within arrays to populate your collection view the
bindings will not be transferred correctly to each replicated view
without subclassing NSCollectionView and overriding
newItemForRepresentedObject and instantiating the same xib yourself,
instead of using the view replication implementation provided by
NSCollectionView.
So in using the newItemForRepresentedObject approach, you need to
factor our your NSCollectionViewItems into separate xibs so that you
can pass down the subarray of people from the group collection view to
your inner collection view.
So for your grouped collection view your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
BPG_MultiPersonModel *model = object;
MyItemViewController *item = [[MyItemViewController alloc] initWithNibName:#"GroupPrototype" bundle:nil];
item.representedObject = object;
item.personModelArray = [[NSArrayController alloc] initWithContent:model.personModelArray];
return item;
}
And for your inner collection subclass your override looks like this:
- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object
{
PersonViewController *item = [[PersonViewController alloc] initWithNibName:#"PersonPrototype" bundle:nil];
item.representedObject = object;
return item;
}
here is a sample project that they sent back to me -
http://db.tt/WPMFuKsk
I am still unable to get this to work with my own project. Can the project they sent back be simplified further?
Please take a closer look at this answer
Short answer:
Extracting each NSView into its own .xib should solves this issue.
Extended:
The IBOutlet’s specified in your NSCollectionViewItem subclass are not connected when the prototype is copied. So how do we connect the IBOutlet’s specified in our NSCollectionViewItem subclass to the controls in the view?
Interface Builder puts the custom NSView in the same nib as the NSCollectionView and NSCollectionViewItem. This is dumb. The solution is to move the NSView to its own nib and get the controller to load the view programmatically:
Move the NSView into its own nib (thus breaking the connection between the NSCollectionViewItem and NSView).
In I.B., change the Class Identity of File Owner to the NSCollectionViewItem subclass.
Connect the controls to the File Owner outlets.
Finally get the NSCollectionViewItem subclass to load the nib:
Usefull links:
how to create nscollectionview programatically from scratch
nscollectionview tips
attempt to nest an nscollectionview fails
nscollectionview redux

Passing a reference and copying the object from another controller. Objects keep disappearing

I know this is a relativly easy question, but I just can't figure out how to solve this problem:
One of my views will receive a dragOperation and the performDragOperation method should pass the NSURL to my AppDelegate which puts it in an mutArray...
The problem is, that I pass over a reference and the object disappears the moment performDragOperation is done. So I tried several things:
in performDragOperation:
//Puts the reference itself in the folderPaths Array
AppDelegate *appDelegate = [NSApp delegate];
[[appDelegate folderPaths] addObject:referenceTotheNSURLObject];
/* tried creating a NSString and putting it in too. Same result because local variables will disappear */
So I created a method in AppDelegate and tried different things:
- (void)addFilePath:(NSURL *)filePath {
NSURL *copyOfURL = [filePath copy];
[[self folderPaths] addObject:copyOfURL];
}
This makes the most sense to me and the only reason I can think about why this doesn't work is, because copyOfURL itself is a pointer and it points to an localObject that will disappear the moment the addFilePath method is finished? But I'm not allowed to use
NSURL copyOfURL = [filePath copy];
I also tried recreating an NSString object again. Same results.
- (void)addFilePath:(NSURL *)filePath {
NSString *pathString = [filePath absoluteString];
[[self folderPaths] addObject:pathString];
}
I know it will be some relatively simply solution but I'm stuck and can't figure it out. Thanks for your time!

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.

Why is my variable out of scope?

I have an NSMutableArray defined in my AppDelegate class:
NSMutableArray *devices;
I populate the array from a class in the didFinishLaunchingWIthOptions method:
Devices *devs = [[Devices alloc] init];
self.devices = [devs getDevices];
The getDevices method parses a json string and creates a Device object, adding it to the array:
NSMutableArray *retDevices = [[NSMutableArray alloc] initWithCapacity:[jsonDevices count]];
for (NSDiectionary *s in jsonDevices) {
Device *newDevice = [[Device alloc] init];
device.deviceName = [s objectForKey:#"name"];
[retDevices addObject: newDevice];
}
return retDevices;
I then use the AppDelegate class's devices array to populate a tableView. As seen in the cellForRowAtIndexPath method:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Device *d = (Device *)[appDelegate.devices objectAtIndex:indexPath.row];
cell.label.text = d.deviceName;
When the tableview is populated initially, it works. But when I scroll through the list, and the cellForRowAtIndexPath is executed again, d.deviceName throws an error because it has gone out of scope.
Can anyone help me understand why? I'm sure it has something to do with an item being released... but... ??
Thanks in advance.
If it is indeed a problem with memory management, answering these questions should lead you to an answer:
Is deviceName declared as a retain or copy property by the Device interface declaration?
Is -deviceName synthesized? If not, how is it implemented?
Is devices declared as a retain property by the app delegate class's interface declaration?
Is devices synthesized? If not, how is it implemented?
Of course, it might not be a problem with memory management. It would help a lot if you were to provide the actual text of the error message and any provided backtrace.

Accessing property inside non-init methods gives bad access

I've begun work on a side project, so the codebase is very small, very little that could go wrong. Something strange is happening. In viewDidLoad I initialise an array set as a property:
#property (nonatomic, retain) NSMutableArray * story_array;
And fill it with data. This printout is fine:
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
I have a gesture recognizer:
UITapGestureRecognizer * openStory = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(showStory)];
Tapping on it calls a method whose first line is this (i.e. the same NSLog):
NSLog(#"%#", ((ArticlePreview *)[self.story_array objectAtIndex:0]).article);
But this causes a bad access. Accessing story_array itself is fine (it'll say it has however many ArticlePreview objects inside) but accessing their fields is a no-no.
The story_array is init'ed as follows:
self.story_array = [[NSMutableArray alloc] init];
Assignment to the fields of the ARticle Preview object were not done properly. I had:
someField = someValue;
I needed:
self.someField = someValue;
I still find that a bit crazy, but there you go. Solved.