NSDictionary loses data? - ios

I've seen posts where NSDictionary loses data but in my case its a little wierd. I have a class that includes some data including NSString and NSIntegers and GLfloats. (As far as I know all of these do conform to NSCopying unlike things like CGFloat.)
I access the files quickly in my app and it all works, but then I try to access it again (to reload/refresh) the screen and then the NSInteger values return something between 180000, and a few billion for a value I know is definitely 4 so for some reason the data hasn't persisted.
It feels like the data is being lost/released/changed at some point but I don't know how it possibly can.
Edit:
Heres some of the code, this is where the texturedQuads are created and store, the method is called for each atlas I have in the init method
Also I have changed where I misused the word wrapper
-(void)loadAtlasData:(NSString *)atlasName
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
if (quadLibrary == nil)
quadLibrary = [[NSMutableDictionary alloc] init];
CGSize atlasSize = [self loadTextureImage:[atlasName
stringByAppendingPathExtension:#"png"]
materialKey:atlasName];
NSArray *itemData = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:atlasName ofType:#"plist"]];
for(NSDictionary *record in itemData)
{
TexturedQuad *quad = [self texturedQuadFromAtlasRecord:record
atlasSize:atlasSize
materialKey:atlasName];
[quadLibrary setObject:quad forKey:
[record objectForKey:#"name"]];
}
[apool release];
}
-(TexturedQuad *)texturedQuadFromAtlasRecord:(NSDictionary *)record
atlasSize:(CGSize)atlasSize
materialKey:(NSString *)key
{
TexturedQuad *quad = [[TexturedQuad alloc] init];
GLfloat xLocation = [[record objectForKey:#"xLocation"] floatValue];
GLfloat yLocation = [[record objectForKey:#"yLocation"] floatValue];
GLfloat width = [[record objectForKey:#"width"] floatValue];
GLfloat height = [[record objectForKey:#"height"] floatValue];
//find the normalized texture co-ordinates
GLfloat uMin = xLocation/atlasSize.width;
GLfloat vMin = yLocation/atlasSize.height;
GLfloat uMax = (xLocation + width)/atlasSize.width;
GLfloat vMax = (yLocation + height)/atlasSize.height;
quad.uvCoordinates[0] = uMin;
quad.uvCoordinates[1] = vMax;
quad.uvCoordinates[2] = uMax;
quad.uvCoordinates[3] = vMax;
quad.uvCoordinates[4] = uMin;
quad.uvCoordinates[5] = vMin;
quad.uvCoordinates[6] = uMax;
quad.uvCoordinates[7] = vMin;
quad.materialKey = key;
return [quad autorelease];
}
Second edit:
Added an example of the plist file
dict
(key)name
(string)sync
(key)xLocation
(integer)489
(key)yLocation
(integer)36
(key)width
(integer)21
(key)height
(integer)21
/dict
essentially its an array, consisting of dictionaries, each dictionary holds the data for the picture in the atlas. so theres the name, xLocation, yLocation, width, height.
edit 3:
Here is where I load the object from
I use a [MaterialController sharedMaterialController] to get an instance of this controller
-(TexturedQuad *)quadFromAtlasKey:(NSString *)atlasKey
{
return [quadLibrary objectForKey:atlasKey];
}

See if removing the auto-release pool sorts the issue. As far as I can see you don't need it as quadLibrary seems to be an iVar whilst itemData is auto-released by its parent class, as are all the TextureQuads you return in your for loop.

Related

32/64 bit CGPoint Persistence in iOS across devices

I've inherited some freehand drawing code which records a CGPathRef of points that are then converted to a collection of CGPoints which eventually get saved within our core data DB as NSData.
Current conversion code looks like this:
#interface StoredPath : NSObject
{
CGPathRef path;
}
#implementation StoredPath
- (NSArray *)getPoints
{
// Convert path to an array
NSMutableArray *a = [NSMutableArray arrayWithObject:[NSNumber numberWithBool:YES]];
CGPathApply(path, (__bridge void *)(a), saveApplier);
if (![[a objectAtIndex:0] boolValue])
{
return nil;
}
[a removeObjectAtIndex:0];
return (a);
}
static void saveApplier(void *info, const CGPathElement *element)
{
NSMutableArray *a = (__bridge NSMutableArray*) info;
int nPoints;
switch (element->type)
{
case kCGPathElementMoveToPoint:
nPoints = 1;
break;
case kCGPathElementAddLineToPoint:
nPoints = 1;
break;
case kCGPathElementAddQuadCurveToPoint:
nPoints = 2;
break;
case kCGPathElementAddCurveToPoint:
nPoints = 3;
break;
case kCGPathElementCloseSubpath:
nPoints = 0;
break;
default:
[a replaceObjectAtIndex:0 withObject:[NSNumber numberWithBool:NO]];
return;
}
NSNumber *type = [NSNumber numberWithInt:element->type];
NSData *points = [NSData dataWithBytes:element->points length:nPoints*sizeof(CGPoint)];
[a addObject:[NSDictionary dictionaryWithObjectsAndKeys:type, #"type", points, #"points", nil]];
}
#end
The important part to notice here is the line:
NSData *points = [NSData dataWithBytes:element->points length:nPoints*sizeof(CGPoint)];
It preserves the CGPoint data into a compact NSData object, which then gets saved in our core data DB.
This was fine when the app's data only lived on a single device. But now that this data is synced to other devices, it breaks down due to CGFloat (makes up CGPoint struct) being 32bit or 64bit on various iOS devices.
When the 32bit NSData of CGPoints is read on a 64bit iOS device, the bytes don't quite align and the drawing gets mangled. The same of course happens in vice-versa (64bit NSData of CGPoints is read on a 32bit iOS device).
I've been struggling to refactor this saveApplier: method in a way that is platform independent and can be easily saved in core data.
Update:
I'm considering packing the points into a "|" separated string.
example: pointsToSave = {442, 797.5}|{442, 797.5}|{442, 797.5}
NSString *pointsToSave = [[NSString alloc] init];
// Convert CGPoint's to a "|" separated string.
for (int i=0; i<nPoints; i++)
{
pointsToSave = [pointsToSave stringByAppendingString:NSStringFromCGPoint(element->points[i])];
if ((i+1)<nPoints)
{
pointsToSave = [pointsToSave stringByAppendingString:#"|"];
}
}
It makes it fairly simple to parse between platforms as well.
Thoughts?
You could pick either float or double as the data type to save with. This will explicitly set the size, since float is 32-bit and double is 64-bit.
For example, if you want more compactness, you could do this:
float *floats = [self pointsToFloats:element->points];
NSData *points = [NSData dataWithBytes:floats length:nPoints*sizeof(float)*2];
free(floats); // Note will need to free, since assuming pointsToFloats uses malloc. Use delete if you use new.
Where pointsToFloats would be defined something like:
- (float *)pointsToFloats:(CGPoint *)points; // Assuming this is straight-forward enough to implement
This also means you'll need something to unpack the data too.
- (CGPoint *)floatsToPoints:(float *)floats;
You can also use double if you need more precision.
There are of course others ways of doing this too.

Using a string as name to set property values

I have these big huge repetitive chunks of code in my project I am attempting to shrink them down. Take this piece example:
self.Day11.delegate = (id)self;
self.Day12.delegate = (id)self;
self.Day13.delegate = (id)self;
self.Day14.delegate = (id)self;
self.Day15.delegate = (id)self;
self.Day16.delegate = (id)self;
self.Day17.delegate = (id)self;
self.Day18.delegate = (id)self;
self.Day19.delegate = (id)self;
What I would like to do is make it that I can use a for loop or something similar to shrink it down like this:
for (int i = 1 ; i<=9; i++) {
NSString *var = [NSString stringWithFormat:#"Day1%d",i];
self.var.delegate = (id)self;
}
I know this doesn't work is there a possible way to do something like this?
No, no, no.
#property (nonatomic,strong) NSArray *arrayOfDays;
Now get rid of all those day objects and fill that self.arrayOfDays with whatever all those individual day objects are...
Then...
for(int i=0; i<[self.arrayOfDays count]; ++i) {
[[self.arrayOfDays objectAtIndex:i] setDelegate: self];
}
Or even better, if all those objects are of the same type (I'll assume they're of type Day), we can do:
for(Day *day in self.arrayOfDays) {
day.delegate = self;
}
Best (per Daij-Dan's comment):
[self.arrayOfDays setValue:self forKeyPath:#"delegate"];

UITextfield text in array will not change a label's text in another array

Please help. What am I doing wrong? textfield.text goes into one array and is suppose to change a label that belongs to another array.
I have multiple textfields. Im trying to save the text of each field to an array and then setAlpha to 0. Then I have an equal amount of labels that I want to change to the textfield's text. I have tried using mutable and immutable arrays. I keep getting errors.
I've tried multiple ways and its got to be something simply dumb I'm missing. I've greatly reduced these arrays just to post here.
_namesText = [NSMutableArray arrayWithObjects:_nameLabel1.text, _nameLabel2.text, nil];
_names = [NSMutableArray arrayWithObjects:_nameLabel1, _nameLabel2, nil];
_nameInputs = [NSMutableArray arrayWithObjects:_p1NameTextField, _p2NameTextField, nil];
_playerNameText = [NSArray arrayWithObjects:_p1NameTextField.text, _p2NameTextField.text, nil];
enter code here
- (IBAction)enterNamesButton:(id)sender {
//These don't work.
for (int i = 0; i < _numberOfPlayers; i++) {
[_names[i] setAlpha:1];
NSString *tempString = [[NSString alloc]initWithString:[_nameInputs[i] text]];
[_lastTry addObject:tempString];
}
//Then tried this. This is after trying for 2.5 hours and different coding.
for (int i = 0; i < _numberOfPlayers; i++) {
_namesText[i] = _lastTry[i];
UILabel *cell = [[UILabel alloc] init];
cell.text = _lastTry[i];
//[[_namesText[i] textLabel] text] = #"Idiot";
_namesText[i] = cell;
NSLog(#"%#", _namesText[i]);
// This works (but bad practice) and I want to loop through arrays that each UI element belongs to instead of typing it all out.
// _nameLabel1.text = _p1NameTextField.text;
// _nameLabel2.text = _p2NameTextField.text;
I expect this to work but NOPE!!!!
Assuming the following:
Array _fromTextField is the textfield you want to copy from and set Alpha to 0
Array _toLabelField is the label you want the text to appear in
Array _toStoreStrings is where you keep all the strings, for later maybe ?
NSMutableArray *_fromTextField = [[NSMutableArray alloc] init]; // Put the Text Fields in Here
NSMutableArray *_toLabelField = [[NSMutableArray alloc] init]; // Put the Labels in Here
NSMutableArray *_toStoreStrings = [[NSMutableArray alloc] init];
int count = 0;
int overRun = [_toLabelField count];
for (UITextField *tf in _fromTextField)
{
tf.alpha = 0;
NSString *tempString = [[NSString alloc]initWithString:tf.text];
[_toStoreStrings addObject:tempString];
if (count < overRun) // Check just in case Array sizes not same
{
UILabel *lb = [_toLabelField ObjectAtIndex:count];
lb.text = tempString;
}
else
break;
count++;
}
The issue your code faces is you have an array of NSObject. The compiler has no idea they are UITextField or UILabel unless you cast them with (type_cast *) or point a new typed variable at them. Hence the crashes you were seeing.
I would do it this way:
for (int i = 0; i < _numberOfPlayers; i++) {
_namesText[i] = _lastTry[i];
}
I cannot see why this won't work... does it?

Core Plot 1.0 my touch annotations won't work

I have been working on this for a couple of days and am quite stumped. I am trying to make a graph in core plot that allows the user to push on a data point and get the coordinates. I made a sample App that just makes a linear graph. My problem is that I can not get the touch feature to work. I referenced many of the plots on the core plot website, but I just can't get it to work in my App. Here is what I did;
In my header I made a property CPTPlotSpaceAnnotation *annotation. I also used, interface ViewController : UIViewController .
In my implementation I synthesized annotation. In my ViewDidAppear method I made a data array with CGPointMake.
NSMutableArray *linearstuff = [NSMutableArray array];
for (int i = 0; i < 100; i ++)
{
float y = (b * (l)) + c;
[linearstuff addObject:[NSValue valueWithCGPoint:CGPointMake(l, y)]];
l = l + inc;
}
self.data = linearstuff;
[self initPlot];
My method initPlot configures the graph and has a couple of configure methods. So in a method I called configurePlots I alloc and init a CPTScatterPlot called linearGraph. I used linearGraph.plotSymbolMarginForHitDetection in that method as well. And then for the plotSymbolWasSelectedAtRecordIndex, I did the following.
- (void)scatterPlot:(CPTScatterPlot *)plot plotSymbolWasSelectedAtRecordIndex:(NSUInteger)index
{
NSLog(#"touch");
CPTGraph *graph = self.hostView.hostedGraph;
if (_annotation)
{
[graph.plotAreaFrame.plotArea removeAnnotation:_annotation];
_annotation = nil;
}
CPTMutableTextStyle *annotationTextStyle = [CPTMutableTextStyle textStyle];
annotationTextStyle.color = [CPTColor whiteColor];
annotationTextStyle.fontSize = 16.0f;
annotationTextStyle.fontName = #"Helvetica-Bold";
NSValue *value = [self.data objectAtIndex:index];
CGPoint point = [value CGPointValue];
NSString *number1 = [NSString stringWithFormat:#"%.2f", point.x];
NSString *number2 = [NSString stringWithFormat:#"%.2f", point.y];
NSLog(#"x and y are, %.2f, %.2f", point.x, point.y);
NSLog(#"number1 and number2 are, %.2f, %.2f", point.x, point.y);
NSNumber *x = [NSNumber numberWithFloat:point.x];
NSNumber *y = [NSNumber numberWithFloat:point.y];
NSArray *anchorPoint = [NSArray arrayWithObjects: x, y, nil];
NSString *final = [number1 stringByAppendingString:number2];
NSLog(#"final is %#",final);
CPTTextLayer *textLayer = [[CPTTextLayer alloc] initWithText:final style:annotationTextStyle];
_annotation = [[CPTPlotSpaceAnnotation alloc] initWithPlotSpace:graph.defaultPlotSpace anchorPlotPoint:anchorPoint];
_annotation.contentLayer = textLayer;
_annotation.displacement = CGPointMake(0.0f, 0.0f);
[graph.plotAreaFrame.plotArea addAnnotation:_annotation];
}
I tried to follow what I saw on the core plot website. I took my array and got the index. I placed those values in an NSNumber to make a string for the annotation. However, it does not come up on the graph. Does any one know what I did incorrect? Any insight would be much appreciated. I have read many of the core plot questions here on SO, but can't find something that would help in my situation. Thank you in advance.
For anyone following this. I first want to thank Eric Skroch for all his help, AGAIN!!!! But I finally found what I was doing wrong. I had made the viewController the plot delegate, however, I did not say so when I was making the plot. I added linearGraph.delegate = self; and now the plotSymbol... method is called! Thanks #Eric Skroch.

Setting up a plist to store application data (not settings) for an iPhone game

I am writing an iPhone game and have it working well with my level data hard coded but I would like to store the level data in a plist a load it at launch. I have never used plists and am having trouble understanding how I should set the plist up based on my data model.
Here is how I have it hard coded now:
(in my appDelegate)
- (void)loadLevels {
//setup NSNumber objects to load into sequences
NSNumber * rID = [[[NSNumber alloc] initWithInt:0] autorelease];
NSNumber * bID = [[[NSNumber alloc] initWithInt:1] autorelease];
NSNumber * gID = [[[NSNumber alloc] initWithInt:2] autorelease];
NSNumber * yID = [[[NSNumber alloc] initWithInt:3] autorelease];
NSNumber * rbID = [[[NSNumber alloc] initWithInt:4] autorelease];
NSNumber * rgID = [[[NSNumber alloc] initWithInt:5] autorelease];
NSNumber * ryID = [[[NSNumber alloc] initWithInt:6] autorelease];
NSNumber * bgID = [[[NSNumber alloc] initWithInt:7] autorelease];
NSNumber * byID = [[[NSNumber alloc] initWithInt:8] autorelease];
NSNumber * gyID = [[[NSNumber alloc] initWithInt:9] autorelease];
//Level One's Sequence
NSMutableArray * aSequence = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
[aSequence addObject: rID];
[aSequence addObject: bID];
[aSequence addObject: gID];
[aSequence addObject: yID];
[aSequence addObject: rbID];
[aSequence addObject: rgID];
[aSequence addObject: ryID];
[aSequence addObject: bgID];
[aSequence addObject: byID];
[aSequence addObject: gyID];
// Load level One
_levels = [[[NSMutableArray alloc] init] autorelease];
Level *level1 = [[[Level alloc] initWithLevelNum:1 levelSpeed:1.0 levelSequence:aSequence] autorelease];
[_levels addObject:level1];
//do the same thing for subsequent levels//
}
(this is how I have my Level Class implemented)
#import "Level.h"
#implementation Level
#synthesize levelNum = _levelNum;
#synthesize levelSpeed = _levelSpeed;
#synthesize levelSequence = _levelSequence;
- (id)initWithLevelNum:(int)levelNum levelSpeed:(float)levelSpeed levelSequence:(NSMutableArray *)levelSequence {
if ((self = [super init])) {
self.levelNum = levelNum;
self.levelSpeed = levelSpeed;
self.levelSequence = [[[NSMutableArray alloc] initWithArray:levelSequence] autorelease];
}
return self;
}
- (void)dealloc {
[_levelSequence release];
_levelSequence = nil;
[super dealloc];
}
#end
I'm just not getting how to set up a plist to store my data to match my model. Can anyone give me some advise please?
ADDITION:
(here is how I think I need to setup the plist - the data model is simply the three variables initializing my level (above). If you look at my current plist it might be more clear how it is setup, but each level is comprised of: a level number, a level speed, and an array of numbers denoting the required sequence for that level.)
Now, if I do have this setup correctly how do I load the values into my program?
Add your plist file as a resource file in your project. Say, it's name is config.plist
Get the path for the resource file. (Though, be careful of [NSBundle mainBundle] as it will not return the unit test bundle in a unit test.)
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"config" ofType:#"plist"];
Your root object is an array of levels. Load it in a NSArray.
// this is autoreleased. you can retain it if needed
NSArray *levelArray = [NSArray arrayWithContentsOfFile:plistPath];
Now you have the array of levels. Each level is a dictionary. Load the level i (counting from 0).
NSDictionary *level = [levelArray objectAtIndex:i];
Now you can get the objects from level dictionary by using objectForKey method. For example, to get the sequence array:
NSArray *seq = [level objectForKey:#"levelSequence"];
You can loop through the levelArray to alloc, init and add to levels array for all your levels.
Hope it helps. Please note that I have not compiled the code, so there might be some typos.
What you should do is create an NSDictionary with all of the applicable data that you want, and read it from and write it to NSUserDefaults.

Resources