I am teaching myself Objective C, and I am trying to progress with the idea of model-view-controller. My question is: what is the correct way of creating data in a model class? I have an example to illustrate what I mean, it's a bit long but a really simple question.
The following code is from a memory game I just finished. It works fine, I have separated the data from the view controller and so I have a separate class to hold the images for the cards. Here is that class. By the way, it all works, the only thing important for my question is that there is an array declared so feel free to skip most of the code that does the duplicating of the images and randomising. Anyway, here it is:
import "Cards.h"
#implementation Cards
- (NSMutableArray*) createCardImages{
NSMutableArray *Fruits = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"Apple.png"],
[UIImage imageNamed:#"Banana.png"],
[UIImage imageNamed:#"Pear.png"],
[UIImage imageNamed:#"Strawberry.png"],
[UIImage imageNamed:#"Lemon.png"],
[UIImage imageNamed:#"Orange.png"],
[UIImage imageNamed:#"Pineapple.png"],
[UIImage imageNamed:#"Kiwi.png"],
[UIImage imageNamed:#"Prune.png"],
[UIImage imageNamed:#"Peach.png"],
nil];
NSUInteger tileCount = [Fruits count];
for (NSUInteger i = 0; i < tileCount; ++i) {
NSInteger nElements = tileCount - i;
NSInteger n = (arc4random() % nElements) + i;
[FullList exchangeObjectAtIndex:i withObjectAtIndex:n];
}
NSArray *Shortlistprep1 = [Fruits subarrayWithRange:NSMakeRange(0, 10)];
NSArray *ShortListprep2=[Shortlistprep1 arrayByAddingObjectsFromArray:Shortlistprep1];
NSMutableArray *ShortList = [[NSMutableArray alloc]init];
ShortList = [NSMutableArray arrayWithArray:ShortListprep2];
return ShortList;
}
- (NSMutableArray*) shuffleCards{
NSUInteger cardCount = [self.cardImages count];
NSMutableArray *count = [[NSMutableArray alloc]init];
for (int tileID = 0; tileID < cardCount; tileID++){
[count addObject:[NSNumber
numberWithInt:tileID]];
}
return count;
}
Then I call this method from the view controller and it all works.
Next, I would like to add a different list of images so that different sets of images can be chosen when playing the game. Something like:
NSMutableArray *Clothes = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"Hat.png"],
[UIImage imageNamed:#"Coat.png"],
[UIImage imageNamed:#"Gloves.png"],
[UIImage imageNamed:#"Trousers.png"],
[UIImage imageNamed:#"Socks.png"],
[UIImage imageNamed:#"Sweater.png"],
[UIImage imageNamed:#"T-shirt.png"],
[UIImage imageNamed:#"Shirt.png"],
[UIImage imageNamed:#"Jumper.png"],
[UIImage imageNamed:#"Underwear.png"],
nil];
I know I could just duplicate the whole method for each new set, something like this:
- (NSMutableArray*) createCardImagesClothes{
NSMutableArray *Clothes = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"Hat.png"],
[UIImage imageNamed:#"Coat.png"],
[UIImage imageNamed:#"Gloves.png"],
[UIImage imageNamed:#"Trousers.png"],
[UIImage imageNamed:#"Socks.png"],
[UIImage imageNamed:#"Sweater.png"],
[UIImage imageNamed:#"T-shirt.png"],
[UIImage imageNamed:#"Shirt.png"],
[UIImage imageNamed:#"Jumper.png"],
[UIImage imageNamed:#"Underwear.png"],
nil];
NSUInteger tileCount = [FullList count];
for (NSUInteger i = 0; i < tileCount; ++i) {
NSInteger nElements = tileCount - i;
NSInteger n = (arc4random() % nElements) + i;
[FullList exchangeObjectAtIndex:i withObjectAtIndex:n];
}
NSArray *Shortlistprep1 = [FullList subarrayWithRange:NSMakeRange(0, 10)];
NSArray *ShortListprep2=[Shortlistprep1 arrayByAddingObjectsFromArray:Shortlistprep1];
NSMutableArray *ShortList = [[NSMutableArray alloc]init];
ShortList = [NSMutableArray arrayWithArray:ShortListprep2];
return ShortList;
}
And I am fine with doing this if this is the way forward, I just would like to learn best practices. I feel like there should be a way to just write one method and then have the properties declared on the h file as so:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface Cards : NSObject
// Create Cards
#property NSMutableArray *cardImages;
#property NSMutableArray *shuffledCards;
#propery NSMutableArray *Fruits;
#propery NSMutableArray *Clothes;
#propery NSMutableArray *SomeOtherSet;
#propery NSMutableArray *SomeOtherSet;
#propery NSMutableArray *SomeOtherSet;
- (NSMutableArray*) createCardImages;
- (NSMutableArray*) shuffleCards;
#end
and then all I would have to do is modify my method to accept an argument of the type NSMutableArray, like so:
- (NSMutableArray*) createCardImages:(NSMutableArray);
And just call the method with the argument which would represent whatever set I want to load.
My question, and it's a fairly basic one, is where would I actually load information into those arrays. There is no "main" or "viewDidLoad" method on the "m" file to actually load the data -in this case the images- into the array.
Does that make sense?
Thanks for reading.
Arguably you shouldn't be exposing the mutable array, your model should be a better container for the data and actually operate on the data. Shuffling by returning an array of numbers doesn't actually change the data model and it should.
Also, it would be cleaner if your data model dealt with data management, not the explicit creation. If you had a factory class which could be asked to generate a set of images and supply those images for your Cards class to deal with you would separate your concerns better. You could add class methods to Cards, or an instance method which took an enum to decide which images to use, but the images are really a separate issue to the management of those images as cards.
So, the view controller, when it's created to manage a game, asks the factory class for an instance of Cards for a specified image type. Once it has that Cards instance it can shuffle it and then request the card at a specified index, or grid location.
Index and grid location are interesting parts, because that could be different for different game types. Different game types can all use the same set of cards (though with a different Cards instance), but index those cards in a different way. To support this it's good to offer a simple index into the cards and allow the controller which knows the game logic to decide how to use that index system.
In all of the above the controller never accesses the cards as an array. The interface is similar, but all interaction is with Cards and that class hides its internal structure. Internally it would use an array, but it could use something else and the controller shouldn't care.
Related
I am new to Objective-C and learning by trial and error! Please forgive me if this question is somewhat naïve.
I've created an array of images and need to shuffle them. I've used the advice given here:
What's the Best Way to Shuffle an NSMutableArray?
And now I need to implement the method 'shuffle'. I have separate category for my array and its implementation:
#interface theKid : NSObject {
NSMutableArray* _cards;
}
#property(strong, nonatomic, readonly) NSMutableArray *cards;
- (UIImage*) shuffledCard;
#end
But now I can't figure out how to implement 'shuffle':
#implementation theKid
- (NSMutableArray *) cards {
_cards = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"One"],
[UIImage imageNamed:#"Two"],
[UIImage imageNamed:#"Three"],
[UIImage imageNamed:#"Four"], nil];
return _cards;
}
- (UIImage*) shuffledCard {
shuffle *cards;
return [cards objectAtIndex:0];
}
#end
This just effects a blank screen when I try to call 'shuffledCard'. Any advice much appreciated.
Hi i have pasted few lines of code below which will Shuffle an NSMutable Array exactly like what you want.
NSMutableArray * _cards = [[NSMutableArray alloc] init];
[_cards addObject:[UIImage imageNamed:#"One.png"]];
[_cards addObject:[UIImage imageNamed:#"Two.png"]];
[_cards addObject:[UIImage imageNamed:#"Three.png"]];
[_cards addObject:[UIImage imageNamed:#"Four.png"]];
[_cards addObject:[UIImage imageNamed:#"Five.png"]];
// Add the below code in the Shuffle Method
NSUInteger count = [_cards count];
for (NSUInteger i = 0; i < count; ++i)
{
NSInteger remainingCount = count - i;
int exchangeIndex = i + arc4random_uniform(remainingCount);
[_cards exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
UIImage * image = [_cards objectAtIndex:i];
}
Just use this api for shuffling of object exchangeObjectAtIndex:withObjectAtIndex: Refer this how to shuffle object in NSMutableArray?
I am new to Objective C coming from C# .NET, I have this scenario :
Assume I have 5 NSArrays corresponding to 5 UIButtons. the UIButtons have the exact same name
as the NSArray, so for example one UIButton is called mainScreen, and there is an NSArray called mainScreen.
Those Five buttons are linked to one IBAction where I do the following :
- (IBAction)btnClick:(id)sender {
NSString *category = [(UIButton *)sender currentTitle];
NSLog(category);
//Here I need to call the NSArray which has the same name as category
}
Now I can get the actual name of the UIButton, but how can I get the NSArray same as that title? without getting into a lot of if else or switch statements?
What I would do is store the arrays inside an NSDictionary. You can then set the 'key' as the name of your array and then the value would be the array itself.
That way you could say:
- (IBAction)btnClick:(id)sender {
NSString *category = [(UIButton *)sender currentTitle];
NSLog(category);
//Here I need to call the NSArray which has the same name as category
NSArray *theArray = (NSArray*)[self.myDictionary valueForKey:category];
}
Hope this helps!
The easiest way to associate names with objects is using an NSDictionary (or it's mutable subclass NSMutableDictionary). Dictionaries map a unique key to an object. Keys can be any object (that implements the NSCopying protocol), but are very often NSStrings
Have a look at the NSDictionary Reference and the Programming with Objective-C guide.
Note that if you use the button title this might break if you localise your app.
What you do is not the best way. You should provide tag for each button, say from 1 to 5. Also you should put your five arrays into one array. Now all you need is:
- (IBAction)btnClick:(id)sender
{
NSInteger index = [sender tag] - 1;
NSArray *array = [bigArray objectAtIndex:index];
}
That's it.
Assign different tags to all UIButtons and then access them explicitly using their tags.
- (IBAction)btnClick:(id)sender {
int tagIs = [(UIButton *)sender tag];
switch (tagIs) {
case 1:
// Access first button array
break;
case 2:
// Access second button array
break;
default:
break;
}
}
Or you can use AssociationObjects method for associating data with objects as following:
Firstly import :
#import <objc/runtime.h>
then create keys as :
static char * firstBtnKey = "firstBtnKey";
static char * secondBtnKey = "secondBtnKey";
-- - other keys same way ---
then use :
// Do any additional setup after loading the view, typically from a nib.
NSMutableArray *firstArray = [[NSMutableArray alloc] initWithObjects:#"object1",#"object 2", nil];
objc_setAssociatedObject((UIButton *)[self.view viewWithTag:1],
firstBtnKey,
firstArray,
OBJC_ASSOCIATION_RETAIN);
NSMutableArray *secondArray = [[NSMutableArray alloc] initWithObjects:#"object1",#"object 2", nil];
objc_setAssociatedObject((UIButton *)[self.view viewWithTag:2],
secondBtnKey,
secondArray,
OBJC_ASSOCIATION_RETAIN);
`
and then access these arrays as :
- (IBAction)btnClick:(id)sender {
int tagIs = [(UIButton *)sender tag];
switch (tagIs) {
case 1:
// Access first button array
NSMutableArray *tempArr = (NSMutableArray *)objc_getAssociatedObject((UIButton *)sender, firstBtnKey);
break;
case 2:
// Access second button array
NSMutableArray *tempArr = (NSMutableArray *)objc_getAssociatedObject((UIButton *)sender, secondBtnKey);
break;
default:
break;
}
}
Hope it helps.
In most programming languages objects don't have names.[1] Just because UIButtons have the exact same name as the NSArray(mainScreen), doesn't mean that your object is "called" mainScreen.
Use NSDictionary , array as object and button title as key.
or use button tag
title1= [[NSArray alloc] initWithObjects:#"1",nil];
title2= [[NSArray alloc] initWithObjects:#"2",nil];
title3= [[NSArray alloc] initWithObjects:#"3",nil];
title4= [[NSArray alloc] initWithObjects:#"4",nil];
title5= [[NSArray alloc] initWithObjects:#"5",nil];
dict = [[NSDictionary alloc] initWithObjectsAndKeys:title1,#"title1",title2,#"title2",title3,#"title3",title4,#"title4",title5,#"title5",nil];
- (IBAction)btnClick:(id)sender {
NSString *category = [(UIButton *)sender currentTitle];
NSArray *arr = [dict objectForKey:category];
}
I am setting the array for a series of views with the following code. bunnyView1 through bunnyView7 are instances of UIImageView created in the storyboard but I want to automate the process so I can generate the connections with code. How would I set the values inside a loop?
- (void)viewDidLoad
{
NSMutableArray *hopAnimation = [[NSMutableArray alloc] initWithCapacity:20];
for (int i = 1; i <= 20; i++) {
[hopAnimation addObject:[UIImage imageNamed:[NSString stringWithFormat:#"frame-%i.png", i]]];
}
self.bunnyView1.animationImages=hopAnimation;
self.bunnyView2.animationImages=hopAnimation;
self.bunnyView3.animationImages=hopAnimation;
self.bunnyView4.animationImages=hopAnimation;
self.bunnyView5.animationImages=hopAnimation;
self.bunnyView6.animationImages=hopAnimation;
self.bunnyView7.animationImages=hopAnimation;
self.bunnyView1.animationDuration=1;
self.bunnyView2.animationDuration=1;
self.bunnyView3.animationDuration=1;
self.bunnyView4.animationDuration=1;
self.bunnyView5.animationDuration=1;
self.bunnyView6.animationDuration=1;
self.bunnyView7.animationDuration=1;
[super viewDidLoad];
}
Add your variables in an Array. And iterate the array and set values . For ex,
NSArray *collection = [[NSArray alloc]initWithObjects:#"object1",#"object2",#"object3",#"object4",nil];
You should use an IBOutletCollection of UIImageView which every bunnyView should be linked to. Declaration goes like this:
#property (nonatomic,retain) IBOutletCollection(UIImageView) NSArray * bunnyViews;
I have a method which should return a UIImage created from contentsOfFile (to avoid caching), but when it returns, i receive EXC_BAD_ACCESS. Running through Instruments doesn't reveal any results, as it just runs, without stopping on a zombie.
The image is correctly copied in the Bundle Resources phase...
- (UIImage *)imageForName:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"png"];
return [UIImage imageWithContentsOfFile:path];
}
This method was adapted from the PhotoScroller sample, which works correctly.
Thanks
EDIT:
This is the code that uses imageForName, and you can see i added the retain, as per Luke/T's suggestion, but the EXC_BAD_ACCESS is on the return, not my addObject: call:
NSMutableArray *images;
for (NSDictionary *dict in imageListForPage){
[images addObject:[[self imageForName:[dict valueForKey:#"name"]]retain]];
}
ImageWithContentsOfFile will return an auto-released object. If you are not retaining it (on return [edit]) then you will get a bad access.
Edit:
Check the pointer of the NSarray. You need to init the Array either alloc as normal or use the arraywith
e.g.
NSMutableArray *images = [NSMutableArray arrayWithCapacity:ARRAY_CAPACITY];//autoreleased
or
NSMutableArray *images = [[NSMutableArray alloc] init];//release later
Adding an object to an NSMutableArray will implicitly send it a retain, so that's not necessary in your code.
Can you confirm (using NSLog() or a breakpoint) that
[UIImage imageWithContentsOfFile:path]
returns an object in your imageForName: method?
Finally, this code should be:
NSMutableArray *images = [NSMutableArray new]; // new is same as [[alloc] init]
for (NSDictionary *dict in imageListForPage) {
[images addObject:[self imageForName:[dict valueForKey:#"name"]]];
}
// ... do something with images
[images release];
I'm doing some lazy loading of images into an array when the app has loaded. I have tried using an NSMutableArray and an NSArray (I don't need to alter the array once it's been created) but the latter crashes on me.
...
[self performSelectorInBackground:#selector(loadImageArrays) withObject:nil];
...
- (void)loadImageArrays {
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
NSString *fileName;
imageArray = [[NSMutableArray alloc] init];
for(int i = 0; i <= x; i++) {
fileName = [NSString stringWithFormat:#"image_0000%d.png", i];
[imageArray addObject:[UIImage imageNamed:fileName]];
}
[pool drain];
}
vs
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
imageArray = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"image_00000.png"],
[UIImage imageNamed:#"image_00001.png"],
[UIImage imageNamed:#"image_0000X.png"],
nil];
[pool drain];
NSZombieEnabled = YES tells me that [UIImage retain] was sent to deallocated instance when using the latter code-snippet.
Both arrays have (nonatomic, retain) property in my h-file. Why are the images not being retained by the NSArray?
UIImage is part of UIKit, which is not thread safe. For example, the method imageNamed: could corrupt the global name-image dictionary that the UIImage class uses for caching.
You have to use Core Graphics if you want to load images on a background thread.
Edit to answer your comment:
You can load PNG files with:
CGDataProviderRef source = CGDataProviderCreateWithURL((CFURLRef)url);
CGImageRef image = CGImageCreateWithPNGDataProvider(source, NULL, NO, kCGRenderingIntentDefault);
CFRelease(source);
A CGImage is a core foundation object and can be stored in an NSArray. You can then make a UIImage from it (on the main thread, of course):
[[UIImage alloc] initWithCGImage:image]
Although I know there is a big difference in mutable and immutable arrays, I'm in doubt myself. Something tells me this isn't purely a mutable vs immutable issue. Looks like your pool is drained prematurely (sounds nasty). Not that it should make a difference, but could you try to spawn a new thread by doing;
[NSThread detachNewThreadSelector:#selector(loadImageArrays) toTarget:self withObject:nil];
I'm simply curious of the result.. Also try both snippets in a clean environment (i.e: with no other code around). If your second code snippet works there, you should be looking elsewhere for the solution.