I have been debating between a .plist and a sqlite3 database to hold some data that I need to access, but not manipulate within the .plist/database. Here is my question. Lets say I want to store the height and color of trees, flowers, bushes and I want each piece of information to be accessible. Below is similar to what I would like:Trees Palm 6 feet Green Willow 8 feet Brown Bushes Evergreen 5 feet Green Cinquefoil 2 feet Yellow Flowers Rose 1 foot Red Tulips 2 feet Yellow
So if I want to individually access the 2 feet height under Tulips and display it in a text box in my app..what is the best form of data store/resource to use. .plist or sqlite? How would I lay this out as a .plist?
As always, I appreciate your time and efforts!!
Thanks!
Since you dont have much data just use a .plist it would be easier to manage
make each parent Trees,Flowers,Bushes and array make each child item a dictinary , so when you check if a child is satisfies your requirement like 2 feet height under Tulips use it.
create some plist like this:
Code Sample:
Note:I didnt test this you need to improve this
you can use some kind of logic here like this to check the color,kind or height.
I am giving an example from my project for you to see how you would filter the plist, so change the name of the function as you wish.
I won't change function names cause "nobody aint got time for that" :)
create a nsobject class called ParseSchedulePlist
in ParseSchedulePlist .h
#import <Foundation/Foundation.h>
#interface ParseSchedulePlist : NSObject
#property (nonatomic,strong) NSMutableDictionary * agendaDic;
- (void)passStringToAgendaDic:(NSString *)string;
//trees
-(NSArray*)getTrees;
-(NSDictionary*)getItems:(NSInteger ) section ;
-(NSString*)getItemKind :(NSInteger ) section;
-(NSString*)getItemColor :(NSInteger ) section;
-(NSNumber*)getItemheight :(NSInteger ) section;
//flowers
//do the same of above for flowers an bushes took
#end
in ParseSchedulePlist .m
#import "ParseSchedulePlist.h"
#implementation ParseSchedulePlist
#synthesize agendaDic = _agendaDic;
- (void)passStringToAgendaDic:(NSString *)string {
//get plist from bundle
NSString * path = [[NSBundle mainBundle] pathForResource:string ofType:#".plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path];
NSLog(fileExists ? #"Yes" : #"No");
NSLog(#"Path is %#",path);
NSLog(#"agendaDic is %u",[self.agendaDic count]);
self.agendaDic = [NSMutableDictionary dictionaryWithContentsOfFile:path];
}
-(NSArray*)getTrees
{
NSArray * value = [[self agendaDic] objectForKey:#"trees"];
return value;
}
-(NSDictionary*)getItems:(NSInteger ) section
{
NSDictionary * value =[[self getTrees] objectAtIndex:section];
return value;
}
-(NSString*)getItemKind :(NSInteger ) section;
{
NSString * value =[[[self getItems] objectAtIndex:section] objectForKey:#"kind"];
return value;
}
-(NSString*)getItemColor :(NSInteger ) section
{
NSString * value =[[[self getItems] objectAtIndex:section] objectForKey:#"color"];
return value;
}
-(NSNumber *)getItemheight :(NSInteger ) section;
{
NSNumber * value =[[[self getItems] objectAtIndex:section] objectForKey:#"height"];
return value;
}
//write the same functions for flowers and bushes
#end
in your regular view controller .h:
#import "ParseSchedulePlist .h"
#property (nonatomic, strong) ParseSchedulePlist *agenda;
in your regular view controller .m:
#import "ParseSchedulePlist .h"
#synthesize agenda;
//here calls for the special class
self.agenda=[[ParseSchedulePlist alloc] init];
[self.agenda passStringToAgendaDic:#"name of the plist"];
NSMutableArray *newArray=[ NSMutableArray alloc] init];
//here for example get all the palm trees under 6 feet
for(int i=0; i<[self.agenda getTrees] count] i++)
{
if([self.agenda getItemKind :i] isquealtostring #"palm"){
if([self.agenda getItemheight :i] <= 6)
[newArray add object:[self.agenda getItems:i];
}
}
Nslog(#"print your array %#",newArray);
You can achieve the same with core data. Core data as such would be a little difficult to use. I have always used it with Magical Record, a wonderful library.
It's very easy to map objects with relation. In your case there is one to many and one to one relation between parent and child.
With MagicalRecord if you wanted to find an item with height equal to 2. You can do this in two simple steps
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"height =%#",#2];
NSArray *items = [Item findAllWithPredicate:predicate];
Good thing is that since there is a one to one relationship from an item to group, each item will have info about group. So everything nicely fits and it's easier to search what ever way you like.
You can find the source code for the details.
Related
I have built a specialized card application. What it does is allow a user to 'draw' a card, then view the card, and place it back in a random place in the deck. The only problem that I am having is that, more often than not, the card is being placed at the top of the deck.
Here is the contents of the .h file:
#class _Card;
#interface _Deck : NSObject
#property (readonly) NSString *deckData;
#property (readonly) NSUInteger count;
#property (readonly) NSUInteger deckCount;
#property (readonly) BOOL needsReset;
#property (readonly) NSArray *cards;
- (id)initWithArray:(NSArray *)array;
- (id)initWithContentsOfFile:(NSString *)filePath;
- (void)shuffle;
- (NSArray *)draw;
- (void)reset;
- (void)changeEdition;
#end
Now, here is my draw method, which will draw a card (more than one if the cards so specify it) and then place that card back into the deck, if it is allowed:
- (NSArray *)draw {
// first, we create an array that can be returned, populated with the
// cards that we drew
NSMutableArray *drawArray = [[[NSMutableArray alloc] init] autorelease];
// next, we get the top card, which is actually located in the
// indexArray (I use this for shuffling, pulling cards, etc.)
NSNumber *index = [[[indexArray objectAtIndex:0] retain] autorelease];
// now we get the card that the index corresponds to
// from the cards array
_Card *card = [cards objectAtIndex:[index integerValue]];
// now I remove the index that we
// got from the indexArray...don't worry,
// it might be put back in
[indexArray removeObject:index];
// if the card is supposed to discard after
// draw, we leave it out
if(!card.discard) {
int insertIndex = arc4random_uniform(indexArray.count);
// then I insert the card into the deck using the random, random
// number
[indexArray insertObject:index atIndex:insertIndex];
}
_Card *cardCopy = [card copy];
// we add the card to the array
// that we will return
[drawArray addObject:cardCopy];
// next, if the card is not the final card...
if(!card.final) {
// ...and the card has an
// additional draw specified...
if(card.force) {
// we keep drawing until we need to stop
[drawArray addObjectsFromArray:[self draw]];
}
}
return drawArray;
}
Is there anything that I may be doing wrong? If you need any more information, please let me know. Thanks in advance for any help that you can provide.
If I am understanding this correctly, the problem is that it is inserting the card at index 0 in indexArray?
Okay, have you tried something like this:
(don't use this line yet [indexArray removeObject:index];)
if(!card.discard)
{
int insertIndex = arc4random_uniform(indexArray.count);
id obj = [indexArray objectAtIndex:index];
[indexArray removeObjectAtIndex:index];
[indexArray insertObject:obj atIndex:insertIndex];
NSLog(#"insertIndex is %i and obj is %#", insertIndex, obj);
}
else
{
[indexArray removeObjectAtIndex:index];
}
Your code seems to be okay, I'm guessing it just doesn't like removing the object before... I added the log just so you could see if it really was inserting it at the top each time. Give me an update- if this doesn't work, I could take a look at your project file.
What does "more often than not" mean?
What you show here looks perfect....
Remember that this is random and it is quite possible (although rare) to get a particular number 10 times in a row. Over the long run you should get an even distribution though.
Run this routine 10,000,000 times or so, and check the number of times that you get each number (make sure that you have the same number of cards in the deck each time) before deciding that something is wrong.
Also, are you sure that your indexArray contains the correct values and you aren't duplicating 0 in there?
Firstly, sorry for my English... I want to write a program which is going to calculate distances between two cities. For example, in UITextField we write Paris, and in the second UITextField we write the second , "London. And We have a base of longitude s and latitude s of all Cities. Our program has distance formula which uses these four numbers. We know this formula.
When the user insert a name in UITextField i want to TAKE THIS .text AND COMPARE IT WITH OUR BASE. How to do that?? I can do a program like this but it`s ....stupid:
#interface City : UIViewController{
IBOutlet UITextField *city1;
IBOutlet UITextField *city2;
}
#property (nonatomic, retain) IBOutlet UITextField *city1;
#property (nonatomic, retain) IBOutlet UITextField *city2;
-(void) calculate;
#end
#import "City.h"
#implementation City
#synthesize city1, city2;
-(void) viewDidLoad
{
// to check changes in textfields i`m using NSTimer, I know it`s stupid but I haven`t learned how to make it in different way
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(calculate) userInfo:nil repeats:YES];
}
-(void) obliczenia
{
double distance;
if([city1 is EqualToString:#"Paris"] && [city2 is EqualToString:#"London"])
{
double Paris_lat = x1;
double Paris_lon = y1;
double London_lat = x2;
double London_lon = y2;
distance =...; // we know the formula for distance but this is not important for now.
// distance shows in some label but this is not a point of my problem.
}
}
It runs but when we have few cities. But when we have thousand cities, writing code would be a nonsens.
I am starting with iPhone programming.
Thank you for patience and please, help me. It s very important but I can't find a solution.
Take a look at this guide:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html
Basically, put all of your data into a property list, like so:
<dict>
<key>Paris</key>
<dict>
<key>x</key>
<real>20.2</real>
<key>y</key>
<real>30.4</real>
</dict>
...
</dict>
And then load it like this:
NSDictionary * data = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"NAMEOFPLIST" ofType:#"plist"]];
NSDictionary * paris = [data objectForKey:#"Paris"];
float xparis = [[paris objectForKey:#"x"] floatValue];
float yparis = [[paris objectForKey:#"y"] floatValue];
In your case you'd do something like this:
NSDictionary * data = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"NAMEOFPLIST" ofType:#"plist"]];
NSDictionary * city1Data = [data objectForKey:city1.text];
NSDictionary * city2Data = [data objectForKey:city2.text];
if (city1Data != nil && city2Data != nil) {
// Do what you need...
}
And then do what you need with the data.
You are probably better off using a picker view to display the available cities, or use a live search in your textview to autocomplete, or provide selections for your cities.
If you have a constrained set of inputs (in this case, the names of the cities for which you have long, lat values) it's a better idea to constrain the user's input to these values.
You can create a Array with all Country Names and compare each Country Name in the Array with your input.
NSArray *countries = [NSArray arrayWithObjects:#"Berlin", #"Rom", nil];
for (NSString *c in countries) {
if ([city1 isEqualToString:c]) {
//do something
}
}
Hopefully I can make this clear, but I am new to Objective-C and to be honest not great with Arrays in the first place.
So, I have a Singleton class (called SingletonState) that I am using to pass variables around my app (please can we leave the whether I should use Singleton classes out of this - I will fix that later). In this class, I have an NSMutableArray (called arrayMyEvents). I have also created a class that I am going to store a list of events (called EventClass). When the user logs in, I call a web service and get back 3 strings. The 3rd string is a comma separated list of value. I parse the data and populate the custom class EventClass. I then add this class to the SingletonState.arrayMyEvents.
I have all of this working. I can go to another ViewController and access the "count" of items in arrayMyEvents.
PROBLEM: I now want to edit one of the ScheduledEventsClass"es" in my array. How do I get access to it and edit some of the properties and update the one in my SingletonState class?
Here is some of the code, that I've tried:
NSString *sWebServiceEvents = [[NSString alloc] initWithFormat:#"%#", [result objectAtIndex:2]];
if ( [ sWebServiceEvents isEqualToString:#"NULL" ] != true ) {
NSArray *arrayEvents = [sWebServiceEvents componentsSeparatedByString:#","];
// If the array has not been initialized they initialize it.
if (sharedState.arrayMyEvents == nil) {
sharedState.arrayMyEvents = [[NSMutableArray alloc ] init ];
}
for (NSString * sEvent in arrayEvents) {
// Set equal to the value of the array (the Event Number) at the same
// position as the row that we are being asked to return a cell/row for.
EventClass *eventClass = [[EventClass alloc] retain];
eventClass.sEvent = sEvent;
[ sharedState.arrayEvents addObject:eventClass ];
}
NSLog(#"LoginView - sharedState.arrayMyEvents Count: %d", [sharedState.arrayMyEvents count]);
}
Here is me trying to access it in another ViewController:
EventClass *eventClass =
[sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"eventClass.sEventNumber: ", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
You're going to have some memory leaks from the sEvent loop. [[EventClass alloc]retain] leaves you an uninitialized EventClass object with a reference count of 2. You'll need to change that to [[[EventClass alloc] init] autorelease] to keep it from leaking. The arrayEvents NSMutableArray will retain it during the addObject: call. (Shouldn't that be [sharedState.arrayMyEvents addObject: eventClass] in the loop?)
After that, all you have to do to edit the EventClass object in the second block of code is edit it. The eventClass variable is a pointer to an object in the array. Anything done to that object doesn't affect the pointer referencing it, it affects data referenced by it. The code you have in the second block should change the sLocation of the selected object as you intend.
You have a few more memory leaks in there, too. Use Cmd-Shift-A to build with the static analyzer and it'll tell you where.
Maybe the problem is that you put them in sharedState.arrayEvents but try to take them out of sharedState.arrayMyEvents. Different variables.
Also, lots of memory leaks.
Thanks John and St3fan, your answers and time are appreciated!!!
I think that I figured out my issue:
Basically, the class that I created (EventClass) had the properties setup like this:
#property (nonatomic, assign) NSString *sStudyNumber;
#property (nonatomic, assign) NSString *sTheater;
but, they should be (or at least I got it to work like this):
#property (nonatomic, retain) NSString *sStudyNumber;
#property (nonatomic, retain) NSString *sTheater;
Then, in my second view I was able to do this:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
I then checked it in another method of the view using this and it was still there:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
NSLog(#"MyEvents: %#", eventClass.sLocation);
I also, checked it in yet another view and the value was maintained in the SharedState.arrayMyEvents without issue. :)
In the end, I believe that I boiled down to the difference between "assign" and "retain".
Now, on to the memory leaks :(
Please, let me know if you see any other issues with this.
Thanks,
Jason
I want to have a NSDictionary that maps from UIViews to something else.
However, since UIViews do not implement the NSCopying protocol, I can't use them directly as dictionary keys.
You can use an NSValue holding the pointer to the UIView and use this as key. NSValues
are copyable. but, if the view is destroyed, the NSValue will hold a
junk pointer.
Here is the actual code (based on the answer by luvieere and further suggestion by Yar):
// create dictionary
NSMutableDictionary* dict = [NSMutableDictionary new];
// set value
UIView* view = [UILabel new];
dict[[NSValue valueWithNonretainedObject:view]] = #"foo";
// get value
NSString* foo = dict[[NSValue valueWithNonretainedObject:view]];
Although this isn't really what they're intended for, you could whip up a functional dictionary-like interface using Associative References:
static char associate_key;
void setValueForUIView(UIView * view, id val){
objc_setAssociatedObject(view, &associate_key, val, OBJC_ASSOCIATION_RETAIN);
}
id valueForUIView(UIView * view){
return objc_getAssociatedObject(view, &associate_key);
}
You could even wrap this up in a class ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable*; in that case you might want to retain the views that you use as keys.
Something like this (untested):
#import "ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable.h"
#import <objc/runtime.h>
static char associate_key;
#implementation ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable
- (void)setObject: (id)obj forKey: (id)key
{
// Remove association and release key if obj is nil but something was
// previously set
if( !obj ){
if( [self objectForKey:key] ){
objc_setAssociatedObject(key, &associate_key, nil, OBJC_ASSOCIATION_RETAIN);
[key release];
}
return;
}
[key retain];
// retain/release for obj is handled by associated objects functions
objc_setAssociatedObject(key, &associate_key, obj, OBJC_ASSOCIATION_RETAIN);
}
- (id)objectForKey: (id)key
{
return objc_getAssociatedObject(key, &associate_key);
}
#end
*The name may need some work.
Provided you don't need to support before iOS 6, NSMapTable (suggested by neilsbot) works well because it can provide an enumerator over the keys in the collection. That's handy for code common to all of the text fields, like setting the delegate or bi-directionally syncing the text values with an NSUserDefaults instance.
in viewDidLoad
self.userDefFromTextField = [NSMapTable weakToStrongObjectsMapTable];
[self.userDefFromTextField setObject:#"fooUserDefKey" forKey:self.textFieldFoo];
[self.userDefFromTextField setObject:#"barUserDefKey" forKey:self.textFieldBar];
// skipped for clarity: more text fields
NSEnumerator *textFieldEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [textFieldEnumerator nextObject]) {
textField.delegate = self;
}
in viewWillAppear:
NSEnumerator *keyEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [keyEnumerator nextObject]) {
textField.text = [self.userDefaults stringForKey:[self.textFields objectForKey:textField]];
}
in textField:shouldChangeCharactersInRange:replacementString:
NSString *resultingText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if(resultingText.length == 0) return YES;
NSString *preferenceKey = [self.textFields objectForKey:textField];
if(preferenceKey) [self.userDefaults setString:resultingText forKey:preferenceKey];
return YES;
And now I will go cry, because I implemented all of this before realizing that my iOS 5.1-targeted app can't use it. NSMapTable was introduced in iOS 6.
Rather than store a pointer to the view and risk the garbage issue, just give the UIView a tag and store the tag's value in the dictionary. Much safer.
I'm using a simple solution under ARC provided by Objective-C++.
MyClass.mm:
#import <map>
#implementation MyClass
{
std::map<UIView* __weak, UIColor* __strong> viewMap;
}
- (void) someMethod
{
viewMap[self.someView] = [UIColor redColor];
}
In this example I am getting stronger type checking by making all the values have to be a UIColor* which is all I needed this for. But you could also use id as the value type if you want to allow any object as the value, ex: std::map<UIView* __weak, id __strong> viewMap; Likewise for keys: id __weak, id __strong> viewMap;
You can also vary the __strong and __weak attributes as needed. In my case, the views are already retained by the view controller that I use this in, so I saw no need to take a strong pointer to them.
a simple solution when you just want UIView as key occasionally,I use it to store UILabel and UIColor
NSArray<UIView *> *views = #[viewA,viewB,viewC,viewD];
NSArray *values = #[valueA,valueB,valueC,valueD];
for(int i = 0;i < 4;i++) {
UIView *key = views[i];
id value = values[i]
//do something
}
id value = values[[views indexOfObject:key]]
Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.