Playing NSSound in NSObject (Mac App with Objective-C) - objective-c

I'm making a game that needs to play music. To make my code more manageable, I wanted to make an NSObject that takes care of the sounds (like fading, playing sounds in a playlist, etc). I have this code:
NSSound *music = [[NSSound alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:self.filename ofType:self.fileExtention] byReference:NO];
[music play];
This code works when I place it in the AppDelegate.m file but is does not work when I place it in the New NSObject Class.
Code in NSObject Class (named Music):
- (void)playMusic:(NSString *)fileName ofType:(NSString *)type
{
NSSound *music = [[NSSound alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:self.filename ofType:self.fileExtention] byReference:NO];
[music play];
NSLog(#"Works!");
}
I call the method with this code in the AppDelegate.m:
[[[Music alloc] init] playMusic:self.fileName ofType:self.extension];
When this is executed it does log "Works!" which means the code is executed.
So the exact same code works in the AppDelegate but not in a NSObject Class. Does anyone know if playing an NSSound in a NSObject Class is even possible (if not, why?), and if so how to edit the code so that it works? It would make my code look a lot less messy ;)

Try called methods on main thread,
dispatch_async(dispatch_get_main_queue(), ^{
// do work here
});

Related

Must AVAudioPlayer be instantiated from a ViewController class?

I'm hoping someone can help as I'm new to iOS / objective C and very puzzled. I'm trying to play a simple sound using AVAudioPlayer as follows:
NSString *path = [[NSBundle mainBundle] pathForResource:#"soundFile" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
self.player=[[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:NULL];
[self.player play];
I am using ARC so I also have in my .h file, the following reference to my player so that ARC does not deallocate my player prematurely:
#property (nonatomic, strong) AVAudioPlayer *player;
This code works just fine and plays my sound PROVIDED that I run this code from a ViewController or my application's AppDelegate.
However if I cut and paste this very same code, plus all the necessary #includes and the #property and add them into another class in the same application but which is not a ViewController, and call the code there then no error is raised but no sound is played.
It is exactly the same code just called on a different class??
Why would it not work?
I have looked and looked for a similar post but nowhere have I seem exactly this scenario addressed. Many thanks if you can help me- would be much appreciated.
To clarify the issue-- here is how I call this code on another class say a class I have named Audio Tester, I would write in AppDelate say
#import "AppDelegate.h"
#import "AudioTester.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
AudioTester * tester = [[AudioTester alloc]init];
[tester playAudio];
}
where AudioTester playAudio is defined as
#import "AudioTester.h"
#implementation AudioTester
-(void) playAudio {
NSString *path = [[NSBundle mainBundle] pathForResource:#"soundFile" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: path];
self.player=[[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:NULL];
[self.player play];
}
#end
with AudioTester.h as follows
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface AudioTester : NSObject
#property (nonatomic, strong) AVAudioPlayer *player;
-(void) playAudio;
#end
Stepping through this code, it gets called just fine but it does not play sound?
If you can help that would be much appreciated. I'm totally stumped.
a little conceptual explanation about your code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
AudioTester * tester = [[AudioTester alloc]init];
[tester playAudio];
}
1.
if you use ARC then the instance won't be not kept alive after the scope runs out, therefore the tester object will be immediately released, so in your case the object is deallocated before it could do anything – that is the reason why you can't hear any noise or sound.
if you want to keep your tester instance alive independently from your current scope where you inited in, you need to create like e.g. a property which is outside of the scope; you could put that into a class extension for instance:
#interface AppDelegate ()
// ...
#property (nonatomic, strong, nullable) AVAudioPlayer * tester;
// ...
#end
2.
we don't put anything like this into the AppDelegate.m file, the app delegate class basically handles the app-related events globally like launch, terminate, etc... briefly, the global and major events of the app's life cycle in runtime.
you can read more about its purpose in the official docs.
3.
you may use the –applicationDidFinishLaunching: method deliberately for initing your app, but I feel necessary to mention you may want to put everything inside the method –application:didFinishLaunchingWithOptions: instead.
you can read more about the initial procedure as well in the same documentation.
TL;DR
the answer to your original concern: NO, a class can be inited and instantiated in any other instance of any type of classes in general, but you need to worry about keeping the object alive as long as you want to use it.

Call AppDelegate Method From Subclass?

I'm probably not explaining this logically, as I'm new to Objective-C, but here I go...
I am writing an application in Objective-C that interacts with a WebView. Part of the app involves sharing an image via NSSharingService that is currently displayed in the WebView. Consequently, I have a method like this defined in my AppDelegate.m file:
#import "CCAppDelegate.h"
#import <WebKit/WebKit.h>
#import <AppKit/AppKit.h>
#implementation CCAppDelegate
-(void)shareFromMenu:(id)sender shareType:(NSString *)type{
NSString *string = [NSString stringWithFormat: #"window.function('%#')", type];
[self.webView stringByEvaluatingJavaScriptFromString: string];
}
#end
I then have a subclass of NSMenu, defined in CCShareMenu.m, which creates a menu of available sharing options:
#import "CCShareMenu.h"
#implementation CCShareMenu
- (void)awakeFromNib{
[self setDelegate:self];
}
- (IBAction)shareFromService:(id)sender {
NSLog(#"%#", [sender title]);
// [CCAppDelegate shareFromMenu];
}
- (void)menuWillOpen:(NSMenu *)menu{
[self removeAllItems];
NSArray *shareServicesForItems = #[
[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeMessage],
[NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail],
[NSSharingService sharingServiceNamed:NSSharingServiceNamePostOnFacebook],
[NSSharingService sharingServiceNamed:NSSharingServiceNamePostOnTwitter]
];
for (NSSharingService *service in shareServicesForItems) {
NSMenuItem *item = [[NSMenuItem alloc] init];
[item setRepresentedObject:service];
[item setImage:[service image]];
[item setTitle:[service title]];
[item setTarget:self];
[item setAction:#selector(shareFromService:)];
[self addItem:item];
}
}
#end
These methods both work fine on their own, except I need to call the shareFromMenu method from within the shareFromService IBAction.
I attempted moving the IBAction method to AppDelegate.m, then realized that made zero sense as the menuWillOpen-created selectors would never find the correct methods. Similarly, I tried following the instructions posted here, but:
[CCAppDelegate shareFromMenu];
Also responded with an error saying that the method was not found.
I realize I'm doing something fundamentally wrong here, so guidance would be appreciated.
-[CCAppDelegate shareFromMenu]
is different from
-[CCAppDelegate shareFromMenu:shareType:]
I would try adding the following to CCAppDelegate.h between #interface and #end:
-(void)shareFromMenu:(id)sender shareType:(NSString *)type
Then change your shareFromService: method to something like:
- (IBAction)shareFromService:(id)sender
{
NSString *shareType = #"Set your share type string here.";
CCAppDelegate *appDelegate = (CCAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate shareFromMenu:sender shareType:shareType];
}
-(void)shareFromMenu is a member method, but
[CCAppDelegate shareFromMenu]
is calling a class function which is not the correct way to call a member function.
You may try to get the CCAppDelegate instance and then call the function like this
CCAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate shareFromMenu];

short call for singleton

Update with working code. Problem was like #HotLinks state, that I did init instead of initWithBaseURL:url
I am using a Singleton in my App, based on this guide.
Now every time I use the singleton I do like this:
SingletonClass* sharedSingleton = [SingletonClass sharedInstance];
[sharedSingleton callAMethod];
// or
[[SingletonClass sharedInstance] callAMethod];
Is there a way to use a short syntax, especially if I have to use the Singleton several times? Something like:
[sc callAMethod];
I tried already this kind, but it did not work, as the init method was not called...
WebApi.h
#import "AFHTTPRequestOperationManager.h"
#import "SingletonClass.h"
#interface WebApi : AFHTTPRequestOperationManager
#property (nonatomic, strong) SingletonClass *sc;
+(WebApi*)sharedInstance;
-(void)sandbox;
#end
WebApi.m (updated with working code)
#import "WebApi.h"
#implementation WebApi
//-(WebApi*)init {
-(WebApi*)initWithBaseURL:url {
self = [super init];
if (self != nil) {
self.sc = [SingletonClass sharedInstance]; // is never called.
}
return self;
}
#pragma mark - Singleton methods
/**
* Singleton methods
*/
+(WebApi*)sharedInstance
{
static WebApi *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:kApiHost]];
});
return sharedInstance;
}
-(void)sandbox {
DLog(#"test short singleton call: %#", [sc callAMethod];
}
#end
Debug Message
[WebApi sandbox] [Line 42] test short singleton call: (null)
I don't see how you can do this in any language. In Java, you would generally see
<Class>.getInstance().<blah>.
There's nothing stopping you from getting that instance inside a method where it will be used a lot, e.g.
WebApi *api = [WebApi sharedInstance];
then a whole lot of:
[api <method1>];
...
Does that get you there?
(Amusingly, a developer and I were discussing this issue yesterday because the example code Apple has for use of the accelerometer puts the motion manager in the app delegate and the syntax to get a hold of the manager is completely insane:
CMMotionManager *mManager = [(APLAppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager];
As you can see, they are making a local var and then diddling that from there on in the controller class.
You could declare a global variable and set it in your +sharedInstance method, then make sure you call +sharedInstance once.
But, really, don't bother. Using [SomeClass sharedInstance] makes it easy to quantify all uses of shared instances in your code base, as well as all uses of SomeClass's class level API. Both are quite useful for anyone else that ends up maintaining your code.
Secondly, it doesn't really save that much typing. Not enough to justify requiring everyone to learn about a new global.
(What Rob said):
Finally, if you are calling instance methods on the shared instance repeatedly in a scope, just use a local variable:
ThingManager *thingManager = [ThingManager sharedInstance];
[thingManager foo];
[thingManager bar];
[thingManager baz];
You can do it this way:
In .h file
#interface WebApi : AFHTTPRequestOperationManager
#property (nonatomic, strong) SingletonClass *sc;
...
+(id) methodName;
...
#end
In .m file
+(id) methodName
{
return [[WebApi shareInstance] instanceMethod];
}
- (id) instanceMethod
{
return #"SMTH";
}

How do I get NSSound to play a sound in a user-defined class?

This question is from an Objective-C newbie, so please bear with me. I'm trying to make sounds in my classes and have been successful using NSBeep() but not NSSound. Here is an example. Notice that NSBeep(); and [[NSSound soundNamed:#"Frog"] play]; work fine in the "main.m" program, but only NSBeep(); works in the SoundMaker class. Any help in learning how to get NSSound to work is much appreciated.
main.m:
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import "SoundMaker.h"
int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"This is the main program.");
NSBeep(); // Works fine.
sleep(1);
[[NSSound soundNamed:#"Frog"] play]; // Works fine.
sleep(1);
[SoundMaker makeSound]; // Only NSBeep() works.
[pool drain];
return 0;
}
SoundMaker.h:
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#interface SoundMaker : NSObject
+(void) makeSound;
#end
SoundMaker.m:
#import "SoundMaker.h"
#implementation SoundMaker
+(void) makeSound
{
NSLog(#"This is SoundMaker.");
NSBeep(); // Works fine.
sleep(1);
[[NSSound soundNamed:#"Frog"] play]; // Doesn't work.
}
#end
So as noted, the solution is to add a sleep(...); statement following NSSound. Here is the change to SoundMaker.m that works:
#import "SoundMaker.h"
#implementation SoundMaker
+(void) makeSound
{
NSLog(#"This is SoundMaker.");
NSBeep(); // Works fine.
sleep(1);
[[NSSound soundNamed:#"Frog"] play]; // Works fine.
sleep(1); // This added statement allows the preceding "play" message to work.
}
#end
In Xcode, if you set a breakpoint, does it get hit?
I have started only recently myself, so the +(void) looks weird to me; the book I am using only teaches
- (return_variable)methodname
[Edit]
I just tested it, added
- (void)playFrog;
To my interface.
I then implemented the function
- (void)playFrog
{
[[NSSound soundName:#"Frog"] play];
}
I called it from somewhere else in my application like
[self playFrog];
That worked.
[Edit 2]
After reading your code again, I noticed you didn't alloc/init your SoundMaker...
Try
SoundMaker *mySoundMaker = [[SoundMaker alloc] init];
[mySoundMaker makeSound];
[mySoundMaker release];
Most of the other answers, while okay, make it look more complicated than it needs to be. All you need is this statement (in Objective-C) wherever you want the sound to be produced:
[[NSSound soundNamed:#"Hero"] play];
You can find and test the various sound names in the Sound window of System Preferences.

How to set a delegate in a different class

I'm working with NSXMLParser that parses a xml document. You have to set the delegate which we would be called every time the parser finds an element. The examples I've looked at all set the delegate to be the same class that's createing:
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:filename];
[parser setDelegate: self];
Other examples set the delegate to be the parent. What if I want another class (not related to the same class) to handle the delegate. What is the syntax to do so?
I've done this but it doesn't work.
#interface Util : NSObject <NSXMLParserDelegate> {
//Some code here
}
//functions for the delegate and the implementation on the Util.m
//.
//.
//.
Thx for your answers.
I forgot to say that when calling the delegate I assumed that it would be something like this:
[parser setDelegate:Util];
I assumed this knowing that to set the delegate for the same class the message is:
[parser setDelegate:self];
You have to create the Util object first.
The delegate has to be an actual instance of a class :)
Util* util = [[Util alloc] init];
[parser setDelegate:util];
[util release];