I am creating a MKMapView application and in need to save a couple MKMapRect type variables in a plist so as to refer them when need.
I know that MKMapRect has MKMapPoint origin and MKMapSize size. And they each have 2 double values that can be saved as nsnumber but saving all of them seems to be a lot of work and top of that i have to read the values back and convert them into a MKMapRect variable.
So my question is that, is there any easy way to store a MKMapRect and retrive it back from a plist.
Thanks,
Robin.
Use MKStringFromMapRect to turn it into a string.
There:
- (NSString *)save:(MKMapRect)rect
{
return MKStringFromMapRect(rect);
}
- (MKMapRect)load:(NSString *)str
{
MKMapRect mapRect;
CGRect rect = CGRectFromString(str);
mapRect.origin.x = rect.origin.x;
mapRect.origin.y = rect.origin.y;
mapRect.size.width = rect.size.width;
mapRect.size.height = rect.size.height;
return mapRect;
}
I made a category to save the map rect to the user defaults:
NSUserDefaults+MKMapRect.h
#interface NSUserDefaults (MKMapRect)
//stores a map rect in user defaults
-(void)setMapRect:(MKMapRect)mapRect forKey:(NSString*)key;
//retrieves the stored map rect or returns the world rect if one wasn't previously set.
-(MKMapRect)mapRectForKey:(NSString*)key;
#end
NSUserDefaults+MKMapRect.m
#implementation NSUserDefaults (MKMapRect)
-(void)setMapRect:(MKMapRect)mapRect forKey:(NSString*)key{
NSMutableDictionary *d = [NSMutableDictionary dictionary];
[d setObject:[NSNumber numberWithDouble:mapRect.origin.x] forKey:#"x"];
[d setObject:[NSNumber numberWithDouble:mapRect.origin.y] forKey:#"y"];
[d setObject:[NSNumber numberWithDouble:mapRect.size.width] forKey:#"width"];
[d setObject:[NSNumber numberWithDouble:mapRect.size.height] forKey:#"height"];
[self setObject:d forKey:key];
}
-(MKMapRect)mapRectForKey:(NSString*)key{
NSDictionary *d = [self dictionaryForKey:key];
if(!d){
return MKMapRectWorld;
}
return MKMapRectMake([[d objectForKey:#"x"] doubleValue],
[[d objectForKey:#"y"] doubleValue],
[[d objectForKey:#"width"] doubleValue],
[[d objectForKey:#"height"] doubleValue]);
}
#end
You could probably copy the MKMapRect data in a CGRect, then store the CGRect as a string with NSStringFromgCGRect() and CGRectFromString()
Related
I am using a standard AppKit NSPersistentDocument document base application and would like a document's window to remember its location and open in the same position it was last closed in.
Note that setting the autosavename in IB on the Window will result in all documents opening in the same position. I want a document to remember its position based on the documents filename.
I have subclassed NSPersistentDocument and currently set the autosave name in the windowControllerDidLoadNib: function. This almost works fine, except that if I open and close the same document repeatedly without closing the App then each time its window's height is increased a small amount (26 pixels), almost like its doing the cascade thing. However if I close the App completely and reopen it then the document remembers its previous position exactly. Am I doing something wrong or is this a bug. Is there some cleanup I should be doing perhaps to ensure that the window is not being resized each time its re-opened.
// NSPersistentDocument subclass
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
LOG(#"windowControllerDidLoadNib called...");
[super windowControllerDidLoadNib:aController];
if ([self autoSaveName] != nil) {
[aController setWindowFrameAutosaveName:[self autoSaveName]];
}
[aController setShouldCascadeWindows:NO];
}
- (NSString*)autoSaveName
{
return [[self fileURL] lastPathComponent];
}
If I add the following code to add 22 pixels to its height
- (NSRect)windowPositionPreference {
LOG(#"printUserDefaults called");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *autosaveNameKey = [NSString stringWithFormat:#"NSWindow Frame %#", [self autoSaveName]];
NSString *frameString = [defaults objectForKey:autosaveNameKey];
NSArray *array = [frameString componentsSeparatedByString:#" "];
CGFloat x = [[array objectAtIndex:0] floatValue];
CGFloat y = [[array objectAtIndex:1] floatValue];
CGFloat width = [[array objectAtIndex:2] floatValue];
CGFloat height = [[array objectAtIndex:3] floatValue];
NSRect rect = CGRectMake(x, y, width, height+22);
FLOG(#" window frame = %fx, %fy, %fw, %fh", x, y, width, height);
return rect;
}
and then set the frame like so in - (void)windowControllerDidLoadNib:(NSWindowController *)aController
NSRect rect = [self windowPositionPreference];
[aController.window setFrame:rect display:YES];
The position seems to be retained exactly. Surely autosavename is just meant to work.
The above was still not working but after some messing about I gave up on using autosavename because of the automatic cascading that seems to be impossible to disable.
Now I just keep my own preferences and set the window frame using the code below. So far it seems to be working perfectly and I have not had to create my own subclasses of NSWindowController or NSWindow.
// Methods in subclassed NSPersistentDocument
- (void)windowControllerDidLoadNib:(NSWindowController *)aController {
[aController setShouldCascadeWindows:NO];
_mainWindow = aController.window;
[self restoreSavedWindowPosition];
}
- (void)close {
[self saveWindowPosition];
[super close];
}
- (void)saveWindowPosition {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *autosaveNameKey = [NSString stringWithFormat:#"OSWindow Frame %#", [self autoSaveName]];
[defaults setObject:[self stringFromFrame] forKey:autosaveNameKey];
}
- (void)restoreSavedWindowPosition {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *autosaveNameKey = [NSString stringWithFormat:#"OSWindow Frame %#", [self autoSaveName]];
NSString *frameString = [defaults objectForKey:autosaveNameKey];
// Do nothing if it has not been set
if (frameString) {
[_mainWindow setFrame:[self rectFromString:frameString] display:YES animate:YES];
}
else {
// Set the default for new docs
NSRect rect = CGRectMake(70, 350, 1000, 760);
[_mainWindow setFrame:rect display:YES animate:YES];
}
return;
}
// Use the filename as the preferences key
- (NSString*)autoSaveName {
return [[self fileURL] lastPathComponent];
}
- (NSString *)stringFromFrame {
NSRect rect = _mainWindow.frame;
rect.origin.y = rect.origin.y + 26;
rect.size.height = rect.size.height - 26;
return [self stringFromRect:rect];
}
- (NSString *)stringFromRect:(NSRect)rect {
return [NSString stringWithFormat:#"%f %f %f %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height];
}
- (NSRect)rectFromString:(NSString*)string {
NSRect rect;
NSArray *array = [string componentsSeparatedByString:#" "];
rect.origin.x = [[array objectAtIndex:0] floatValue];
rect.origin.y = [[array objectAtIndex:1] floatValue];
rect.size.width = [[array objectAtIndex:2] floatValue];
rect.size.height = [[array objectAtIndex:3] floatValue];
return rect;
}
Is that possible to save colorcodes in Plist file somehow?
Do I have to represent them in string? and then can I create colors from them?
I saw a similar thread here about this but it has no answer yet
What could be done?
I can give you a suggestion. Instead of storing the color names you can store the RGB values in a array and store that array in each row in the plist.
key - Red
Type - Array
value - 1.0,0.0,0.0
Retrieve the array for each key.
NSArray *colorsArray = [dictionaryFromPlist objectForKey:#"Red"];
UIColor *mycolor = [UIColor colorWithRed:[[colorsArray objectAtIndex:0] floatValue]
green:[[colorsArray objectAtIndex:1] floatValue]
blue:[[colorsArray objectAtIndex:2] floatValue]
alpha:1.0];
Just my thought..
UIColor (see here) conforms to the NSCoding protocol (see here) meaning you can write them out to a plist if you do it using NSCoding.
There is a great tutorial here about saving and restoring your app data using NSCoding
You can use NSKeyedArchiver which inherits from NSCoder
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[UIColor purpleColor]];
[[NSUserDefaults standardUserDefaults] registerDefaults:#{#"color": data}];
To get the color back out you would use NSKeyedUnarchiver:
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSLog(#"%#", [NSKeyedUnarchiver unarchiveObjectWithData: dict[#"color"]]);
To preserve human readability, I did a category for this:
#implementation UIColor (EPPZRepresenter)
NSString *NSStringFromUIColor(UIColor *color)
{
const CGFloat *components = CGColorGetComponents(color.CGColor);
return [NSString stringWithFormat:#"[%f, %f, %f, %f]",
components[0],
components[1],
components[2],
components[3]];
}
UIColor *UIColorFromNSString(NSString *string)
{
NSString *componentsString = [[string stringByReplacingOccurrencesOfString:#"[" withString:#""] stringByReplacingOccurrencesOfString:#"]" withString:#""];
NSArray *components = [componentsString componentsSeparatedByString:#", "];
return [UIColor colorWithRed:[(NSString*)components[0] floatValue]
green:[(NSString*)components[1] floatValue]
blue:[(NSString*)components[2] floatValue]
alpha:[(NSString*)components[3] floatValue]];
}
#end
The same formatting that is used by NSStringFromCGAffineTransform. This is actually a part of a bigger scale plist object representer in [eppz!kit at GitHub][1].
I have a long list of places with Latitudes and Longitudes in a plist. I want to show a tableView of the places only within X distance of the user's current location. Is there a way to create objects from the lats & longs in the plist file so I can use 'distanceFromLocation'? More importantly, how do I get the array to only display the names with a distance from current less than X? I'm assuming I would need to make a series of objects from lats & longs in the plist, then do an objects in array if objects distanceFrom is less than X, correct?
Please help.
Here's where I am now: I get an error on the double clubLatitude line
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *clubArray = [NSArray arrayWithObjects:[self danceClubLocation], nil];
self.tableData = clubArray;
}
-(CLLocation *)danceClubLocation
{
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
NSArray *array = [NSArray arrayWithContentsOfFile:plistPath];
NSEnumerator *e = [array objectEnumerator];
id object;
while ((object = [e nextObject])) {
double clubLatitude = [[array valueForKey:#"Latitude"] doubleValue];
double clubLongitude = [[array valueForKey:#"Longitude"] doubleValue];
CLLocation *clubLocation = [[CLLocation alloc] initWithLatitude:clubLatitude longitude:clubLongitude];
if ([clubLocation distanceFromLocation:myLocation]<=50) {
return clubLocation;
}
else return nil;
}
return nil;
}
-(CLLocation *)myLocation
{
CLLocation *location = [locationManager location];
CLLocationCoordinate2D coordinate = [location coordinate];
NSNumber *myLatitude = [NSNumber numberWithDouble:coordinate.latitude];
NSNumber *myLongitude = [NSNumber numberWithDouble:coordinate.longitude];
double myLatitudeD = [myLatitude doubleValue];
double myLongitudeD = [myLongitude doubleValue];
myLocation = [[CLLocation alloc]initWithLatitude:myLatitudeD longitude:myLongitudeD];
return myLocation;
}
As #DavidNeiss said, you have to iterate over the list (an NSArray with the plist as source) and it would be something like this:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"latlong" ofType:#"plist"];
NSArray *array = [NSArray arrayWithContentsOfFile:plistPath];
NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
// do something with object
}
Then you can do what you want (removing what's far from the user or whatever).
Read in your plist, iterate over it to pull out ones within X distance and populate any array with them that will be the data source for your table view?
I have a problem with MKPointAnnotation. I want to create my iPhone application like this:
Show a pin in Google Maps
Show a description on each pin, pulling that description from a NSMutableArray.
So, My question is, how do I show a description on each pin?
This is my code:
NSMutableArray *points = [[NSMutableArray alloc] init];
for(int i=0; i < [dataTable count]; i++) {
MKPointAnnotation *point =[[MKPointAnnotation alloc] init];
CLLocationCoordinate2D coordinate;
coordinate.latitude = [[[dataTable objectAtIndex:i] objectForKey:#"latitude"] floatValue];
coordinate.longitude = [[[dataTable objectAtIndex:i] objectForKey:#"longitude"] floatValue];
[point setCoordinate:coordinate];
[point setTitle:[[dataTable objectAtIndex:i] objectForKey:#"name"]]; //-- name of pin
[points addObject:point];
}
[map addAnnotations:points];
I would adopt the MKAnnotation protocol in my own class and simply override the
- (NSString) title
and implement
- (CLLocationCoordinate2D) coordinate {
CLLocationCoordinate2D theCoordinate;
theCoordinate.latitude = self.latitude;
theCoordinate.longitude = self.longitude;
return theCoordinate;
}
My class would also have an initialiser that takes all the data it needs (from the array you mentioned)
- (id) initWithTitle:(NSString *)title_ andLatitude:(CLLocationDegrees)latitude_ andLongitude:(CLLocationDegrees)longitude_;
When iterating I would create my own objects and then add them to the collection of annotations.
Cheers...
I was curious what is considered the better way to manage the reading and writing of a High Score plist file. My High Score class is:
#interface HighScore : NSObject <NSCoding> {
NSString *name;
int score;
int level;
int round;
NSDate *date;
}
Now, I could do method A, add NSCoding methods:
- (void) encodeWithCoder: (NSCoder *) coder {
[coder encodeObject: name
forKey: kHighScoreNameKey];
[coder encodeInt: score
forKey: kHighScoreScoreKey];
[coder encodeInt: level
forKey: kHighScoreLevelKey];
[coder encodeInt: round
forKey: kHighScoreRoundKey];
[coder encodeObject: date
forKey: kHighScoreDateKey];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey: kHighScoreNameKey];
self.score = [decoder decodeIntForKey: kHighScoreScoreKey];
self.level = [decoder decodeIntForKey: kHighScoreLevelKey];
self.round = [decoder decodeIntForKey: kHighScoreRoundKey];
self.date = [decoder decodeObjectForKey: kHighScoreDateKey];
}
return (self);
} // initWithCoder
And write it all out with:
if (![NSKeyedArchiver archiveRootObject:highScoresList toFile:path]) ...
Reading it back in would be pretty straight forward. However the plist file, IMHO, looks like crap.
Or I could employ method B:
NSMutableArray *array = [NSMutableArray arrayWithCapacity:20];;
for (HighScore *hs in highScoresList) {
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
hs.name, kHighScoreNameKey,
[NSNumber numberWithInteger:hs.score], kHighScoreScoreKey,
[NSNumber numberWithInteger:hs.level], kHighScoreLevelKey,
[NSNumber numberWithInteger:hs.round], kHighScoreRoundKey,
hs.date, kHighScoreDateKey,
nil];
[array addObject:dict];
[dict release];
}
and write it all out with:
if (![array writeToFile:path atomically:YES]) ...
Reading it back in is a tiny bit harder. But the plist file looks much cleaner (smaller and compact).
Any thoughts? Am I missing something that is much simpler? (I want to keep the High Scores separate from NSUserDefaults so I am not using that).
Both your ways look fine to me. There is also Core Data in the 3.0 OS, although it might be overkill if all you want to save is a single high score value.
I'm not sure that I understand your objections! Both should work just fine.
Personally I prefer method A. I think it makes sense that an object knows how to encode itself. It makes maintenance easier and any changes more localised. Plus it probably uses less memory.
check out this question, maybe it's useful for your app
I went with method B because: 1. The plist file is more readable and 2. I can save off some file and class version numbering into this method easily:
In my HighScore class:
- (id)initFromDictionary: (NSDictionary *)dict;
{
if (self = [super init]) {
self.name = [dict objectForKey:kHighScoreNameKey];
self.score = [[dict objectForKey:kHighScoreScoreKey] integerValue];
self.game = [[dict objectForKey:kHighScoreGameKey] integerValue];
self.level = [[dict objectForKey:kHighScoreLevelKey] integerValue];
self.date = [dict objectForKey:kHighScoreDateKey];
}
return (self);
}
- (NSDictionary *)putIntoDictionary;
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
name, kHighScoreNameKey,
[NSNumber numberWithInt:score], kHighScoreScoreKey,
[NSNumber numberWithInt:game], kHighScoreGameKey,
[NSNumber numberWithInt:level], kHighScoreLevelKey,
date, kHighScoreDateKey,
nil];
return dict;
}
And in my HighScoreTable class:
- (id) load
{
NSString *path = [self getFilePath];
// [self clear];
NSDictionary *rootLevelPlistDict = [NSDictionary dictionaryWithContentsOfFile:path];
int versNum = [[rootLevelPlistDict objectForKey:kHighScoreVersKey] integerValue];
if (versNum == kHighScoreVersNum) {
NSArray *insideArray = [rootLevelPlistDict objectForKey:kHighScoresKey];
NSDictionary *dict;
for (dict in insideArray) {
HighScore *hs = [[HighScore alloc] initFromDictionary:dict];
[highScoresList addObject:hs];
[hs release];
}
}
return sharedHighScoresSingleton;
}
- (void) save
{
if (!changed)
return;
NSString *path = [self getFilePath];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:kNumberOfHighScores];
for (HighScore *hs in highScoresList) {
NSDictionary *dict = [hs putIntoDictionary];
[array addObject:dict];
[dict release];
}
NSDictionary *rootLevelPlistDict = [[NSDictionary alloc] initWithObjectsAndKeys:
[[[NSBundle mainBundle] infoDictionary]
objectForKey:(NSString*)kCFBundleNameKey], kHighScoreAppNameKey,
[NSNumber numberWithInt:kHighScoreHeaderVersNum], kHighScoreHeaderVersKey,
[NSNumber numberWithInt:kHighScoreVersNum], kHighScoreVersKey,
[NSDate date], kHighScoreCreationKey,
array, kHighScoresKey,
nil];
if (![rootLevelPlistDict writeToFile:path atomically:YES])
NSLog(#"not successful in writing the high scores");
[rootLevelPlistDict release];
}