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
Related
Unable to add NSmutableDictionary in NSmutableArray, activitiesFeedArray is a mutable array and initialized in header file.
NSMutableDictionary *dummyitem = [[NSMutableDictionary alloc]init];
NSMutableDictionary *dummyitem2 = [[NSMutableDictionary alloc]init];
NSMutableDictionary *dummyitem3 = [[NSMutableDictionary alloc]init];
[dummyitem setObject:#"No Data Found" forKey:#"text"];
[dummyitem2 setValue:dummyitem forKey:#"Title"];
[dummyitem3 setObject:dummyitem2 forKey:#"ItemInfo"];
NSLog(#"%#",dummyitem3);
//dummyitem3 logs correct value here
if ([activitiesFeedArray count] == 0)
{
NSLog(#"%#",dummyitem3);
//dummyitem3 logs correct value here
[activitiesFeedArray addObject:dummyitem3];
NSLog(#"%#",activitiesFeedArray);
//activitiesFeedArray logs null value here
}
Viewdid load
(void)viewDidLoad
{
[super viewDidLoad];
activitiesFeedArray = [[NSMutableArray alloc]init];
}
Header File
#interface SearchViewController : UIViewController <UISearchBarDelegate ,UITableViewDelegate , UITableViewDataSource>
{
NSMutableArray *activitiesFeedArray;
}
#end
You don't mention where that code is, but it's clearly running some time before viewDidLoad, so the array is stil nil.
If this is the only code you have, then it should work fine and NSLog of your NSMutableArray should not return nil. I copied and pasted your code on my XCode and it works perfectly. You must be doing something somewhere else in your code to alter NSMutableArray.
If you are curious what I did to prove that your code works, I created a simple UIViewController with one button in it. I then created a UIViewController class and literally copied your code in the header file and the implementation file. I declared the NSMutableArray in the header file and initialized it in viewdidload (I copied and pasted them as is). I also put the rest of your NSMutableDictionary code in the viewDidLoad and it produced sensible results. The NSMutableArray log shows that it contains the correct data.
Sometimes it helps to do a clean build but as far as I can tell you, your code is correct and should work.
By the way, I have IOS5 and XCode 4.2.
Replace [dummyitem2 setValue:dummyitem forKey:#"Title"]; with [dummyitem2 setObject:dummyitem forKey:#"Title"];
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];
}
I'm making a subclass of UIView completely in code (no IB), let's call it ContentView. In this view I've already set up several players for sounds and video, as well as several imageViews (nothing special).
Next, I was planning to subclass ContentView several times in order to load different media for each view. All of these views would have the same view controller since the interface would be the same for all of them, only the content (sounds, video and images) would change.
So my approach to this problem was to declare several NSString *const in ContentView.h and specify their keys/values in the implementation file of each subclass view of ContentView, in the form of static NSString *const, since I would be reusing them to load different media for each view and did not want them in the global name space.
Here is some mockup code that illustrates what I'm talking about:
In ContentView.h
#interface ContentView : UIView {
NSString *const kSoundFile1;
NSString *const kSoundFile2;
NSString *const kSoundFileType;
NSString *const kMovieFile1;
NSString *const kMovieFile2;
NSString *const kMovieFileType;
NSString *const kImage1;
NSString *const kImage2;
and in ContentView.m, something of the sort,
#implementation ContentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSString *filePath1 = [[NSBundle mainBundle] pathForResource: kSoundFile1
ofType: kSoundFileType;
NSURL *url1 = [[NSURL alloc] initFileURLWithPath:filePath1];
AVAudioPlayer *audioPlayer1 = [AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
[audioPlayer1 prepareToPlay];
[url1 release];
... and so on, for the rest of the sound files and movies (except for the images, for which i'm using imageNamed:).
Then on the implementation file of each subclass of ContentView I have just this:
#implementation ContentViewSubclass
static NSString *const kSoundFile1 = #"sound1";
static NSString *const kSoundFile2 = #"sound2";
static NSString *const kSoundFileType = #"wav";
static NSString *const kMovieFile1 = #"movie1";
static NSString *const kMovieFile2 = #"movie2";
static NSString *const kMovieFileType = #"mov";
static NSString *const kImage1 = #"image1.png";
static NSString *const kImage2 = #"image2.png";
#end
I can't make this work. There are no compiler errors or warnings, simply nothing plays or shows. Am I doing something wrong, or is this just not the right approach to the problem?
I would really appreciate some insights. Thanks in advance.
Although the answer below from #deanWombourne makes perfect sense, I had an issue with his solution. But I have found out what was wrong with it (at least this is my take on it and it's working now).
A UIView subclass already has its own designated initializer, which is -(id)initWithFrame, so calling -(id)init on any subsequent subclass of ContentView is not going to update any instance variables since [super init] directs to nowhere (or better, first the superclass runs initWithFrame and only then it runs init, which is the same as having done nothing).
So my solution is the following:
(after changing the ivars on ContentView.h to the form of NSString *pointer, for example, NSString *kSoundFile1),
#implementation ContentViewSubclass
- (id)initWithFrame:(CGRect)frame {
kSoundFile1 = #"sound1";
kSoundFile2 = #"sound2";
kSoundFileType = #"wav";
kMovieFile1 = #"movie1";
kMovieFile2 = #"movie2";
kMovieFileType = #"mov";
kImage1 = #"image1.png";
kImage2 = #"image2.png";
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
#end
First update the strings on the designated initializer -(id)initWithFrame, and only then call super.
It works just fine now.
My sincere thanks to #deanWombourne for the help given in solving this problem.
I think I might have worked it out, ignore my other answer :)
You've set the strings up as members of ContentView (in ContentView.h). They start out initialised to nil.
Then, in each subclass you create new strings with the same name but only in that .m file (the static keyword does that!). Just beacuse they have the same name in the C code doesn't mean they're pointing to the same object :)
Your ContentView.m file can't see the new strings, it just uses the ones defined in your .h file, which have never been set to anything so I bet they're still nil!
You need to define the strings in your subclasses like this :
#implementation ContentViewSubclass
- (id)init {
self = [super init];
if (self) {
kSoundFile1 = #"sound1";
kSoundFile2 = #"sound2";
kSoundFileType = #"wav";
kMovieFile1 = #"movie1";
kMovieFile2 = #"movie2";
kMovieFileType = #"mov";
kImage1 = #"image1.png";
kImage2 = #"image2.png";
}
return self;
}
#end
PS The k in front of these names doesn't make much sense anymore - I'd get rid of it)
EDIT: This answer is completely barking up the wrong tree. Try this one instead :)
I instantly look for this line :
AVAudioPlayer *audioPlayer1 = [AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
The SDK wants to tell you the error but you ask it not to. Then you complain that there is no error!
Try this:
NSError *error = nil;
AVAudioPlayer *audioPlayer1 = [AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (error) {
NSLog(#"error : %#", error);
}
and let us know what you see.
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'm sure I'm missing something obvious here in a simple iPhone program I'm trying to write, but the code seems so basic that I can't see what's wrong.
I'm trying to use an NSMutableDictionary to store a list of classes and their associated save file names. In the header file I declare the dictionary
#interface ClassList : UITableViewController {
NSString *homedirectory;
NSString *masterindexpath;
NSMutableDictionary *classFilenameGlossary;
NSMutableArray *listofclasses;
}
#property (retain, nonatomic) NSString *homedirectory;
#property (retain, nonatomic) NSString *masterindexpath;
#property (retain, nonatomic) NSMutableDictionary *classFilenameGlossary;
#property (retain, nonatomic) NSMutableArray *listofclasses;
And, of course, in the implementation file:
#implementation ClassList
#synthesize homedirectory;
#synthesize masterindexpath;
#synthesize classFilenameGlossary;
#synthesize listofclasses;
I initialize this dictionary at ViewDidLoad from an existing file that saves the classlist:
- (void)viewDidLoad {
[super viewDidLoad];
// Get home directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
homedirectory = [paths objectAtIndex:0];
masterindexpath = [homedirectory stringByAppendingPathComponent:#"MasterIndex"];
// Get master course list or create it if necessary
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:masterindexpath] == NO)
{
NSMutableDictionary *temporarydictionary = [NSMutableDictionary dictionary];
[temporarydictionary writeToFile:masterindexpath atomically:NO];
[temporarydictionary release];
}
[classFilenameGlossary initWithContentsOfFile:masterindexpath];
[homedirectory retain];
[masterindexpath retain];
}
Later, I add an item using setObject:forKey: but the dictionary never changes. Originally I was doing this in another view controller using a pointer back to this ClassList file, but once I discovered that didn't work, I simplified and tried to just set any sample object and key within the ClassList file:
[classFilenameGlossary setObject:#"sample filename" forKey:#"sample classname"];
I've looked at the debugger variable list and it shows classFilenameGlossary properly identified as an NSMutableDictionary with 0 key/value pairs. I'm not getting any sort of error; the dictionary simply never changes. At the moment, the dictionary loaded from the file masterindexpath is empty (since I haven't been able to add any items yet), but that shouldn't keep me from being able to add items to the dictionary.
I'm a total beginner at iPhone programming, so I'm sure there's something basic that I'm missing. I'd appreciate any help.
This line:
[classFilenameGlossary initWithContentsOfFile:masterindexpath];
should look like this:
classFilenameGlossary = [[NSMutableDictionary alloc] initWithContentsOfFile:masterindexpath];
You forgot to allocate memory for the NSMutableDictionary, so that's why it never initializes.