Loading different media files for each view subclass using NSString *const - iphone

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.

Related

Memory leak when using NSString inside for loop

I have 100 images in my resource bundle named like image1.jpg,image2.jpg.
Basically what i am trying to do is create path names to those images dynamically inside a for loop.
While testing in simulator,the images loaded fine and the app did not crash.But while testing the app with instruments i was shocked to see the heavy memory leak that was happening while i was creating the path1 object.
I am pasting the entire method here for reference
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
for(int i=1 ; i<100 ; i++){
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSBundle mainBundle] pathForResource:str ofType:#"jpg"];
[self.arrayImages addObject:path1];
}
}
return self;
}
As i have not made use of any alloc inside the loop i dont have any ownership and hence no right to release the object.What is the reason for this memory leak??
Kindly explain the problem and provide the necessary solution in order to fix it..
As always,any help is highly appreciated..
arrayImages is retaining path1, and so if you do not release arrayImages it will leak. How are you creating arrayImages, and are you releasing it anywhere?
Edited based on comments:
Make sure you release arrayImages in your -dealloc method like so: [arrayImages release]; (note the lack of self).
There is no leak in the code you've shown.
There are (at least) two possibilities:
You have a leak in code you didn't paste into your question
Everything is fine and Instruments gave you a false-positive
Your loop will create a lot of autoreleased variables. These won't be deallocated until after the loop has finished, but that's how it's supposed to work.
The reason for the leak would be this line right here:
NSString *str = [NSString stringWithFormat:#"Century%d",i];
By using convenience methods in Objective-C, what happens in the background is the following:
NSString *str = [[[NSString alloc] initWithFormat:#"Century%d", i] autorelease];
Not using alloc/init to create a weak reference is a misconception. You are always the owner of a created object, no matter how you create it. The convenience method simply does the alloc/init and autoreleases it for you.
Here's what I would suggest you do to avoid leaking memory:
- (id)init {
self = [super init];
if (self) {
self.arrayImages = [[[NSMutableArray alloc] init] autorelease];
NSAutoreleasePool *tmpPool = [[NSAutoreleasePool alloc] init];
for(int i = 1 ; i < 100 ; i++) {
NSString *str = [NSString stringWithFormat:#"Century%d",i];
NSString *path1 = [[NSString alloc] initWithString:[[NSBundle mainBundle] pathForResource:str ofType:#"jpg"]];
[self.arrayImages addObject:path1];
[path1 release];
}
[tmpPool drain];
}
return self;
}
Let me know if this works better for you.
-EDIT- Allocating the path1 object and releasing it after adding to arrayImages.

read from plist and load into image name/label text

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];
}

Issue loading plist into NSArray on ViewDidLoad

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

Referencing superview's methods

I'm making an application in Xcode, and running into some problems. I'm using the GameKit framework to allow for bluetooth communication between two iOS devices. The application is setup so that one of the devices is the "master" and the other is the "slave," changing it's screen content based on data received from the "master" device. The user can select whether to be the master or the slave, and when that choice is made, the other device automatically becomes the opposite role. This is all done in one view controller class. When a role is chosen, a subview is added to the baseViewController.
What my problem is, is that when the subview that is added, I would like to be able to send data using the methods in the baseViewController class. With the current setup, the device invoking the action becomeMaster:sender crashes.
What I've tried so far is,
BaseViewController:
-(IBAction)becomeMaster:(id)sender {
[self dataToSend:#"slave"]; //tells peer device to become slave, since this device is master
masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
[masterViewController setBaseViewController:self];
[self.view addSubview:masterViewController.view];
}
-(void)dataToSend:(NSString *)direction {
//—-convert an NSString object to NSData—-
NSData* data;
NSString *str = [NSString stringWithString:direction];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}
-(void)dataToSend:(NSString *)direction {
//—-convert an NSString object to NSData—-
NSData* data;
NSString *str = [NSString stringWithString:direction];
data = [str dataUsingEncoding: NSASCIIStringEncoding];
[self mySendDataToPeers:data];
}
//----------------------------------------------------------------------------//
- (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context {
//—-convert the NSData to NSString—-
NSString* str;
str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
[self useReceivedData:str];
[str release];
}
-(void)useReceivedData:(NSString *)str {
if ([str isEqualToString:#"forward"]) {
[slaveViewController.view setBackgroundColor:[UIColor blackColor]];
}
}
MasterViewController:
-(void)setBaseViewController:(BaseViewController *)bvc {
baseViewController = bvc;
}
-(IBAction)goForward:(id)sender {
actionLabel.text = #"goingForward";
[baseViewController dataToSend:#"forward"];
}
Most of that code is part of the standard Apple documentation/examples, but I included it for understanding the flow of logic.
I believe the problem originates to with the becomeMaster:sender and setBaseViewController:bvc methods. Could anyone help fix? Thanks so much!
What kind of crash are you getting? EXC_BAD_ACCESS? Try turning on NSZombieEnabled in your executable's arguments. It's difficult to say what could be causing the crash, but you might try changing your setBaseViewController: implementation to this:
-(void)setBaseViewController:(BaseViewController *)bvc {
[self willChangeValueForKey:#"baseViewController"];
[baseViewController autorelease]
baseViewController = [bvc retain];
[self didChangeValueForKey:#"baseViewController"];
}
And add [baseViewController release]; to MasterViewController's -dealloc method.
Keep in mind that it's not entirely necessary to have a custom setter for baseViewController. If you have the following property declaration in your header file:
#property (nonatomic, retain) BaseViewController *baseViewController;
And you use #synthesize baseViewController, the -setBaseViewController: method is already generated for you, with key-value observing support built in. If you aren't familiar with Objective-C 2.0 properties, I suggest reading Apple's documentation.

Help creating custom iPhone Classes

This should be a simple question, but I just can't seem to figure it out.
I'm trying to create my own class which will provide a simpler way of playing short sounds using the AudioToolbox framework as provided by Apple. When I import these files into my project and attempt to utilize them, they just don't seem to work. I was hoping someone would shed some light on what I may be doing wrong here.
simplesound.h
#import <Foundation/Foundation.h>
#interface simplesound : NSObject {
IBOutlet UILabel *statusLabel;
}
#property(nonatomic, retain) UILabel *statusLabel;
- (void)playSimple:(NSString *)url;
#end
simplesound.m
#import "simplesound.h"
#implementation simplesound
#synthesize statusLabel;
- (void)playSimple:(NSString *)url {
if (url = #"vibrate") {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
statusLabel.text = #"VIBRATED!";
} else {
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *audioF1ile = [paths stringByAppendingPathComponent:url];
NSURL *audioURL = [NSURL fileURLWithPath:audioFile isDirectory:NO];
SystemSoundID mySSID;
OSStatus error = AudioServicesCreateSystemSoundID ((CFURLRef)audioURL,&mySSID);
AudioServicesAddSystemSoundCompletion(mySSID,NULL,NULL,simpleSoundDone,NULL);
if (error) {
statusLabel.text = [NSString stringWithFormat:#"Error: %d",error];
} else {
AudioServicesPlaySystemSound(mySSID);
}
}
static void simpleSoundDone (SystemSoundID mySSID, void *args) {
AudioServicesDisposeSystemSoundID (mySSID);
}
}
- (void)dealloc {
[url release];
}
#end
Does anyone see what I'm trying to accomplish here? Does anyone know how to remedy this code that is supposedly wrong?
In C based languages, = is an assignment operator, and == is an equality operator.
So when you write this:
if (url = #"vibrate") {
That will always return true, since in C (and hence Obj-C), if statements are 'true' if what's in the brackets is not 0, and an = operation returns the assigned value, which in this case is a pointer to the NSString #"vibrate" (which is definitely not zero).
I don't know exactly why you're trying to compare a URL string to #"vibrate", but the correct way to compare NSString objects is to do something like:
if ([url isEqualToString:#"vibrate"])
if (url = #"vibrate") {
See also Tell The Program What To Do When No Save Data Is Found NSUserDefaults, iPhone.