I have no idea where this code is wrong. Please help, it is supposed to read a value from a dictionary and I use the value to call an image. I've tried to read the value as label.text but I got no result.
The only one I can call is from the nslog.
for (id key1 in dictionary)
{
NSMutableString *textnamed = [dictionary objectForKey:key1];
NSMutableString *imageDisplay =[NSMutableString stringWithFormat:#"%#.png",[dictionary objectForKey:key1]];
eyeImageSaved.image = [UIImage imageNamed:imageDisplay];
labelSaved.text = textnamed;
NSLog(#"%#",textnamed);
}
There are a few issues with your code. First of all, you should consider using NSString instead of NSMutableString because it should be faster. Second, why are you putting the dictionary object in a string then calling the dictionary object again in the next line? That first line is unnecessary. It's entirely possible that the dictionary entry is not a string, and that is why you are having issues. You should write it like this NSString *textnamed = (NSString *)[dictionary objectForKey:key1]. Also, imageNamed can only be used for files in the file bundle. Are you sure those pictures are stored there? There are a few other issues you could be having. What exactly is going wrong here?
I solved my problem. There is nothing wrong with my code above, the mistake I made is that the object that is supposed to hold my variable and store in plist as the name of the image became null. So I added the object to appdelegate.h as seen below:
AppDelegate
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSString *imageNameHolder;
}
#property (retain, nonatomic) NSString *StorageDecider;
View Controller
- (IBAction)lips2:(id)sender {
imageNameHolder = #"lips_blue";
}
-(void)writeNewPlist
{
AppDelegate* ref = (AppDelegate*) [[UIApplication sharedApplication] delegate];
[dictionary setObject:imageNameHolder forKey:#"image1"];
[dictionary writeToFile:finalPath atomically:YES];
}
nameDataFormArray_ & imageDataFormArray_ are NSMutableArray. Both have string and find image as per string (name of image).
- (void)readInfoFromThePlist
{
NSString* PListPath=[[NSBundle mainBundle] pathForResource:#"dataList" ofType:#"plist"];
NSMutableArray* tempDataList=[[NSMutableArray alloc ] initWithContentsOfFile:PListPath];
for(NSDictionary *dataDict in tempDataList)
{
[nameDataFormArray_ addObject:[dataDict objectForKey:#"name"]];
[imageDataFormArray_ addObject:[dataDict objectForKey:#"imagepath"]];
}
[tempDataList release];
}
Related
I'm trying to load a simple plist file in XCode 4 for an iPad application. The goal being that it will be loaded into the table portion of a split-view.
My project structure and plist look as follows:
I then declare an array in the interface of the RootViewController:
#import <UIKit/UIKit.h>
#class DetailViewController;
#interface RootViewController : UITableViewController {
NSArray *sites;
}
#property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
#end
I then, on viewDidLoad, attempt to load in the plist:
- (void)viewDidLoad
{
[super viewDidLoad];
self.clearsSelectionOnViewWillAppear = NO;
self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
NSBundle *bundle = [NSBundle mainBundle];
//NSLog(bundle);
NSString *path = [bundle pathForResource:#"StoredSites" ofType:#"plist"];
NSLog(path);
sites = [[NSArray alloc] initWithContentsOfFile:path];
NSLog(#"%d", [sites count]);
}
The NSLog statements first return what appears to be the valid path, but upon try to load the NSArray with the contents of the file, it comes back with a count of 0:
I checked in the build phases to see if the file reference appears, and it seems to be ok:
I know this must be a simple problem, but for some reason I'm not seeing what's missing!
I appreciate the help -
You may want to using an NSDictionary, as that is what the sample code given here for the method pathForResource:ofType: uses.
I'm not entirely sure why it isn't working with an NSArray, but maybe the plist isn't formatted entirely correctly? The documentation for NSArray says that initWithContentsOfFile: takes in as a parameter quote:
The path to a file containing a string representation of an array
produced by the writeToFile:atomically: method.
Again, I'm not really sure. Hope that Helps!
The problem was the plist XML was wrapped in a dictionary rather than just being a simple array of arrays -
DShah covers it here: plist in xcode creation problem
After a ASIFormDataRequest , i create a temporary NSMutableArray *resultArray from the JSON then add it to a defined NSMutablearray *myData
-(void)viewDidLoad{
myData = [[NSMutableArray alloc] init];
//request that calls gotInfo method
}
-(void)gotInfo:(ASIFormDataRequest *)request{
NSString *responseString = [request responseString];
NSMutableArray *resultArray = [responseString yajl_JSON];
[myData addObject:resultArray];
}
-(IBAction)doSomethingWithData:(id)sender{
//something with myData
}
but when i try to call myData from outside of the gotInfo: method, i get bad access errors and when i inspect myData outside of the method, it shows a kern_protection_failure. So i'm guessing that outside of the method, the resultArray is obviously released, but it's also released from myData since the object inside myData is sharing the same memory location?
I also tried
-(void)gotInfo:(ASIFormDataRequest *)request{
NSString *responseString = [request responseString];
[myData addObject:[responseString yajl_JSON]];
}
How do I preserve myData??
in my header file:
#import <UIKit/UIKit.h>
#class ASIFormDataRequest;
#interface EventsTableController : UITableViewController <UITableViewDataSource>{
NSMutableArray *myData;
}
-(void)gotInfo:(ASIFormDataRequest *)request;
UPDATE:
so in the gbd, the myData is allocated as 0x5e96560 so i did
po 0x5e96560
and then i get the EXC_BAD_ACCESS with the reason being KERN_PROTECTION_FAILURE at address: 0x00000009
but if i do
po [[0x5e96560 objectAtIndex:0] objectForKey:#"key"]
then i get the value! whyyyyyy?
#property(nonatomic,retain) NSMutableArray *myData
and create the object
self.myData = [[NSMutableArray alloc] init];
and
// and i assume your resultArray is a mature NSMutableArray object
[self.myData addObject:resultArray];
The best way of using copy I can think of, is to always set NSString properties to "copy" instead of retain. That way you get more accurate readings from the Leaks instrument if you mess up and forget to release a string an object is holding onto. Other uses of copy need to be more carefully thought out.
NOTE : You are responsible to release myData after no use of that variable.
You dont really have any way to correctly access myData as you declare it as a member inside of EventsTableController, but you dont set the #property for it, and do not synthesize it either. By synthesizing it in your EventsTableController.m file you are telling xcode to generate the getter/setters you need to correctly touch myData, which is where your program seems to be failing. If you do this, this should solve your problem.
-Karoly
Except for the different name of your ivar (mienVar vs. myVar), I don't see a problem. Some other code must be releasing your ivar, or you are accessing it before viewDidLoad has the opportunity to actually create the array (I bet it is the latter).
I think you should put the code in viewDidLoad in your initialization method instead. Don't forget to release the array in dealloc.
You could, of course, also write your own myData getter method, doing lazy initialization, instead of creating it in the init method:
- (NSMutableArray *) myData
{
if (!myData)
myData = [[NSMutableArray alloc] init];
return myData;
}
Note that now, you should access self.myData if you want to use it.
I think the NSString yajl_JSON category can return an array or a dictionary - you might need to inspect the type of the result array on the line below as it may be an NSDictionary:
NSMutableArray *resultArray = [responseString yajl_JSON];
IF you are treating it as an array when its a dictionary that might be causing your problems.
(relevant code from the NSObject+YAJL category below)
YAJLDocument *document = [[YAJLDocument alloc] initWithData:data parserOptions:options error:error];
id root = [document.root retain];
[document release];
return [root autorelease];
(and in YAJLDocument object)
#interface YAJLDocument : NSObject <YAJLParserDelegate> {
(id root_; // NSArray or NSDictionary
I've used this tutorial to create an app with a table view that is populated using an NSMutableArray. Now I'd like to add the functionality to add additional items to the array and save/load them. I've customized the Fruit class to look like this:
#import <UIKit/UIKit.h>
#interface Fruit : NSObject {
NSString *name;
NSString *instructions;
NSString *explination;
NSString *imagePath;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSString *instructions;
#property(nonatomic,copy) NSString *explination;
#property(nonatomic,copy) NSString *imagePath;
- (id)initWithName:(NSString*)n instructions:(NSString *)inst explination:(NSString *)why imagePath:(NSString *)img;
#end
and the Fruit.m file:
#import "Fruit.h"
#implementation Fruit
#synthesize name,instructions,explination,imagePath;
- (id)initWithName: (NSString*)n instructions:(NSString*)inst explination:(NSString *)why imagePath:(NSString *)img {
self.name = n;
self.instructions = inst;
self.explination = why;
self.imagePath = img;
return self;
}
#end
and this works great, I can load two textviews and an imageView, instead of just one textview. But how would I go about saving any new items the user creates, and loading them (if they exist) when the app gets launched again?
to save your array to disk you need a couple of things.
first you need to add some methods to your fruit class so it conforms to the NSCoding protocol.
The first method is - (id)initWithCoder:(NSCoder *)aDecoder. This method will be called when you create a Fruit object from a saved archive.
Second method is - (void)encodeWithCoder:(NSCoder *)aCoder. This method is used to save your fruit in an archive.
Sounds complicated? Actually it isn't. Just a couple lines of easy to understand code.
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.instructions = [aDecoder decodeObjectForKey:#"instructions"];
self.explanation = [aDecoder decodeObjectForKey:#"explanation"];
self.imagePath = [aDecoder decodeObjectForKey:#"imagePath"];
}
return self;
}
Look at first two lines of this init method. You have to call [super init] and do a check if self is not nil in your initWithName:instructions:explination:imagePath: method too. It won't change anything in this special case, but this will definitely change in the next few classes you write. So use it all the time.
I changed this for you. And I changed the spelling error.
- (id)initWithName: (NSString*)n instructions:(NSString*)inst explination:(NSString *)why imagePath:(NSString *)img {
self = [super init];
if (self) {
self.name = n;
self.instructions = inst;
self.explanation = why;
self.imagePath = img;
}
return self;
}
and the method for encoding:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:name forKey:#"name"];
[aCoder encodeObject:instructions forKey:#"instructions"];
[aCoder encodeObject:explanation forKey:#"explanation"];
[aCoder encodeObject:imagePath forKey:#"imagePath"];
}
It's not necessary that the key name matches the variable name. You don't need to do this. But in my opinion it adds some clarity. As long as you decode with the same name you've used for encoding you can use whatever you want.
First part is done. Next you need to load and save your NSMutableArray to a file. But to do this you need the path to the documents directory. So I created a little helper method that goes into your controller.
- (NSString *)applicationDocumentsPath {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
Then we need to load the array from disk.
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:#"some.fruits"];
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!array) {
// if it couldn't be loaded from disk create a new one
array = [NSMutableArray array];
}
then you add as much fruits as you like, and finally, to save your array to disk you need this line.
BOOL result = [NSKeyedArchiver archiveRootObject:array toFile:path];
you can check result if the archive was done without error.
I guess this should get you started. Happy coding.
You would need to persist your array on the disk and load it when the app launches.
See the answer to this question.
Than you should read about archiving : NSArchiver
You would need to implement 2 method for your Fruit class :
EncodeWithEncoder and InitWithEncoder.
than you could archive you fruits array.
Good Luck.
I have this tableview in my app which has a particular item name such as "Apple". When the user taps on that selected item they are directed to a detailView that views an image and description of that selected item. I was thinking to use plist´s to do this, but I don't know how to. Please help me.
I looked at some sources such as iCodeBlog but they don´t really explain retrieving and saving so well. Maybe you people can also give reference to some links that describe this better.
Heres a plist that I have tried. Each of the items (Orange, Apple) have the data that I want to display in my detailView. I basically want to display the data shown.
Here is an example view controller which will do what you want. I named the class DetailViewController and loaded the plist from Details.plist in the app's resources. Images are also located in the app's resources.
DetailViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController {
UILabel *description;
UIImageView *imageView;
NSString *selectedItem;
}
#property (nonatomic, retain) IBOutlet UILabel *description;
#property (nonatomic, retain) IBOutlet UIImageView *imageView;
#property (nonatomic, copy) NSString *selectedItem;
#end
DetailViewController.m
#import "DetailViewController.h"
#implementation DetailViewController
#synthesize description, imageView, selectedItem;
- (void)viewDidLoad {
NSDictionary *details = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Details" ofType:#"plist"]];
details = [details objectForKey:self.selectedItem];
self.description.text = [details objectForKey:#"description"];
self.imageView.image = [UIImage imageNamed:[details objectForKey:#"image"]];
}
#end
Then, all you have to do is update the selectedItem property before loading the view and create a NIB file with the description label and image view attached to their outlets.
Plists are really useful for many cases:
+ (NSMutableDictionary*) containerDictionary {
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"ContainerGraphics.plist"];
NSMutableDictionary *plistData = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
return plistData;
}
This loads a plist file (Property List) called "ContainerGraphics.plist" from my apps bundle.
The plist file is actually just an XML file. You can build one in Xcode by selecting "new file" -> "resource" -> "Property list".
You don't really say what it is you don't understand, so if there is something specific that you find hard to comprehend, please elaborate:)
EDIT:
With the plist you post, use my above approach to "load in" the plist file.
Then it is an NSDictionary of NSDictionaries..
So to get to the "Apple" dictionary you go:
NSDictionary *appleDictionary = [plistData objectForKey:#"Apple"];
Which means something along - go into the list and retrieve the "Apple" dictionary.
Now to get the image and description values you would go:
NSString *imagePath = [appleDictionary objectForKey:#"image"];
NSString *descriptionString = [appleDictionary objectForKey:#"description"];
That is pretty much what there is to it.
If you have a a tableView that need to be populated with this data there is a catch!
The data inside an NSDictionary has no order. So you can't just reference [dictionary objectAtIndex:indexPath.row] as you would do with an array of data for a tableView.
What you do then is to first get the plistData, as in my first example.
(do this in viewDidLoad).
Then you extract all the keys from the plistData dictionary - notice that the "top dictionary is specific, "Apple, "Orange" etc. not values you want to hardcode into your app. But, the dictionaries inside these dictionaries are general, image, description…
So:
NSArray *allKeys = [plistData allKeys];
the allKey array now contains keys (Apple, Orange, etc.)to get to the NSDictionaries in the plistData.
In the cellForRowAtIndexPath method you can now go:
NSDictionary *dictionaryForCurrentCell = [plistData objectForKey:[allKeys objectAtIndex:indexPath.row]];
[cell.textLabel setText:[dictionaryForCurrentCell objectForKey:#"image"]];
[cell.detailLabel setText:[dictionaryForCurrentCell objectForKey:#"description"]];
Hope it helps:)
Pass an NSString to the detail view and load the item based on the NSString, This could be a particular dictionary name, or item in an array.
newDetailViewController.currentItem = #"Array1";
and in my detail view, define:
.h
NSString *currentItem;
#property(nonatomic,retain) NSString *currentItem;
.m
#synthesize currentItem;
Now currentItem in your detailView controller will be "Array1", since that is what we passed to it.
It all depends on really how your plist is setup, are you using an NSArray, an NSDictionary?
There are plenty of ways you can put this to use:
NSString *current = [NSString stringWithFormat:#"%#/%#",appDocsPath,currentItem];
NSMutableDictionary* dict = [[[NSMutableDictionary alloc] initWithContentsOfFile:current] autorelease];
EDIT FROM QUESTION:
Using the example above:
aLabel.text = [dict objectForKey#"description"];
Create an array in a plist with a number of items which is equal to the number of rows. Create a string called description and a string called image in each item. Get the number of the selected row in didSelectRowAtIndex: method. Store the dictionary in an NSDictionary
[myDictionary initWithDictionary:[myArray objectAtIndex:indexPath.row]]
Then get the imagestring and the description string by calling
[myDictionary objectForKey:#"image"]
and pass these strings to the detail view.
I want to send a string from one class to the other:
1) In class1, I add a property to hold the string:
#property (nonatomic, retain) NSString *str;
2) and a method to send back a string:
-(NSString *)sendBackStr:(NSString *)strURL
{
NSString *str = [[NSString alloc] stringWithString:strURL];
return str;
}
3) In class2, I add a property to hold the received string:
#property (nonatomic, retain) NSString *returnStr;
4) and the following code:
Class1 *c1 = [[Class1 alloc] init];
returnStr = [c1 sendBackStr:#"URL"];
But the program stops at returnStr = [c1 sendBackStr:#"URL"]; Any ideas about what's wrong with it?
stringWithString is a class method returning an autoreleased string. You should be calling it like this:
myProperty = [NSString stringWithString:strURL];
Here I assume your property does a copy, to increment the retain count on the autoreleased string that's returned from the stringWithString method. (Objects returned from alloc calls have a retain count of one and are not autoreleased.) It's more usual to give strings the copy property rather than the retain one - you usually just want your own copy of a string, not a shared reference to a string owned by someone else.
What I also can't understand is why you've written the code like this, unless it's just an example. In class 2, all you need to do is write
returnStr = [NSString stringWithString:#"URL"];
stringWithString: is a message that needs to be sent to the NSString class, not an instance of your class (returned via alloc).
The correct code should be:
-(NSString *)sendBackStr:(NSString *)strURL
{
return [NSString stringWithString:strURL];
}
You might want to familarize yourself more about the idioms around allocation, retention, and autoreleasing of pointers. If you wanted to alloc this string for some reason and return it from the sendBackStr: message, then you would probably want this code:
-(NSString *)sendBackStr:(NSString *)strURL
{
return [[[NSString alloc] initWithString:strURL] autorelease];
}