New to iOS and Objective-C, have been struggling to figure this out. I have a class that holds a strong reference to an AVAudioPlayer object and defines a method to play an mp3 depending on the parameter 'tag', which belongs to a UIButton. In my view controller I have a method that uses this class to play a sound when a button is pressed. But when I run the simulator and press the button, the mp3 is not being played. When I don't use the other class and make the AVAudioPlayer belong to my ViewController, initialize it in viewDidLoad, and call play right in the IBAction method, it works fine. I checked that the files are available to my project and that they are being referenced correctly in the code.
I looked around and found this and this, neither solved my problem. Here's my code
GuitarTuner.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#interface GuitarTuner : NSObject
- (void) play: (NSUInteger)tag;
#end
GuitarTuner.m
#import "GuitarTuner.h"
#import <AVFoundation/AVFoundation.h>
#interface GuitarTuner()
#property (strong, nonatomic) AVAudioPlayer *audioPlayer;
#end
#implementation GuitarTuner
- (void) play:(NSUInteger)tag
{
NSString *note;
switch (tag) {
case 0:
note = #"Low-E";
break;
case 1:
note = #"A";
break;
case 2:
note = #"D";
break;
case 3:
note = #"G";
break;
case 4:
note = #"B";
break;
case 5:
note = #"Hi-E";
break;
}
NSString *path = [[NSBundle mainBundle] pathForResource:note ofType:#"mp3"];
NSURL *soundURL = [NSURL fileURLWithPath:path];
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:nil];
[self.audioPlayer play];
}
#end
ViewController.m
#import "ViewController.h"
#import "GuitarTuner.h"
#interface ViewController ()
#property (strong, nonatomic) GuitarTuner *tuner;
#end
#implementation ViewController
- (GuitarTuner *) tuner
{
if (!_tuner) return [[GuitarTuner alloc] init];
return _tuner;
}
- (IBAction)noteButton:(id)sender
{
UIButton *button = (UIButton*)sender;
[self.tuner play:button.tag];
}
#end
Thanks in advance
EDIT:
Silly mistake! Just didn't properly initialize GuitarTuner property in the getter in ViewController -- should be _tuner = [[GuitarTuner alloc] init] Answer below works too.
Try to initialise AVAudioPlayer like this
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:[[NSBundle mainBundle] URLForResource:note withExtension:#"mp3"] error:&error];
UPDATE:
You give yourself your answer:
When I don't use the other class and make the AVAudioPlayer belong to
my ViewController, initialize it in viewDidLoad, and call play right
in the IBAction method, it works fine.
Try to alloc tuner in viewDidLoad or create from your class GuitarTuner a singleton and from there everything will be much easier.
Also comment this:
- (GuitarTuner *) tuner
{
if (!_tuner) return [[GuitarTuner alloc] init];
return _tuner;
}
Related
STILL NO SOLUTION - REDUCED TEST CASE PROJECT HERE:
http://www.friendlycode.co.uk/assets/Bugfix.zip
I'm new to Xcode/Objective C and have done a lot of research but cannot find an answer. There are loads of similar questions here but none of them have helped me solve this.
Files:
app.h
app.m
Settings.h
Settings.m
I have some background music playing which starts when the app is launched via ViewDidLoad in the ViewController.m file.
I am trying to stop this from the Settings.m file if the Music switch is touched and set to off.
Please see code below (have removed unnecessary outlets/methods etc)
The NSLog outputs 'attempting to stop audio' but audio is not stopped. I think I have referenced the ViewController class correctly so unsure why it won't stop?
app.h
#import <UIKit/UIKit.h>
#import <Social/Social.h>
#import "AVFoundation/AVAudioPlayer.h"
#interface ViewController : GAITrackedViewController <AVAudioPlayerDelegate, UIActionSheetDelegate>
{
// removed
}
#property (nonatomic, retain) AVAudioPlayer *BackgroundMusicPlayer;
#end
app.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Play Background music
[self PlayBackgroundMusic];
}
-(void)PlayBackgroundMusic
{
NSString* resourcePath = [[NSBundle mainBundle]
pathForResource:#"music-file"
ofType:#"aiff"];
NSLog(#"Path to play: %#", resourcePath);
NSError* err;
//Initialize our player pointing to the path to our resource
_BackgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath:resourcePath] error:&err];
if( err ){
//bail!
NSLog(#"Failed with reason: %#", [err localizedDescription]);
}
else{
//set our delegate and begin playback
_BackgroundMusicPlayer.delegate = self;
[_BackgroundMusicPlayer play];
_BackgroundMusicPlayer.numberOfLoops = -1;
_BackgroundMusicPlayer.currentTime = 0;
_BackgroundMusicPlayer.volume = 0.5;
}
}
Settings.h
#import <UIKit/UIKit.h>
#import "app.h"
#interface Settings : GAITrackedViewController <AVAudioPlayerDelegate, UIActionSheetDelegate>
{
IBOutlet UIButton *BackButton;
IBOutlet UISwitch *MusicSwitch;
IBOutlet UISwitch *SoundFXSwitch;
// Get instance of ViewController object
ViewController *home;
}
-(IBAction)BackButton:(id)sender;
-(IBAction)ToggleMusic:(id)sender;
-(IBAction)ToggleSoundFX:(id)sender;
#end
Settings.m
#import "Settings.h"
#interface Settings ()
#end
#implementation Settings
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)ToggleMusic:(id)sender {
// Get instance of ViewController object
//home = [[ViewController alloc] init];
if (MusicSwitch.on)
{
[home.BackgroundMusicPlayer play];
}
else {
[home.BackgroundMusicPlayer stop];
NSLog(#"Attempting to stop audio");
}
}
-(IBAction)ToggleSoundFX:(id)sender {
if (SoundFXSwitch.on)
{
}
else{
}
}
-(IBAction)BackButton:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}
I think the problem is with the ViewController *home.
Your AvAudioPlayer object is in the app.h in the interface ViewController.
But in your code , you are not initialising the ViewController object "home"
in settings.m. So effectively , you are trying to access and stop a player that
is not created.
To access the AVAudioPlayer object add the following code in your viewDidload of settings.h.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//initialise the audioPlayer object.
home=[[ViewController alloc] init];
}
In your Settings.h , declare the ViewController property as assign
#interface Settings : GAITrackedViewController <UIActionSheetDelegate>
{
#property (nonatomic, assign) ViewController *home;
}
-(IBAction)ToggleMusic:(id)sender {
if (MusicSwitch.on)
{
[self.home.BackgroundMusicPlayer play];
}
else {
[self.home.BackgroundMusicPlayer stop];
}
}
From your app.m , assign the home property as self _BackgroundMusicPlayer.home = self;
-(void)PlayBackgroundMusic
{
NSString* resourcePath = [[NSBundle mainBundle]
pathForResource:#"music-file"
ofType:#"aiff"];
NSLog(#"Path to play: %#", resourcePath);
NSError* err;
//Initialize our player pointing to the path to our resource
_BackgroundMusicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath:resourcePath] error:&err];
if( err ){
//bail!
NSLog(#"Failed with reason: %#", [err localizedDescription]);
}
else{
//set our delegate and begin playback
_BackgroundMusicPlayer.delegate = self;
settingsViewObj.home = self; //recommended after the 'Settings' view allocation code.
[_BackgroundMusicPlayer play];
_BackgroundMusicPlayer.numberOfLoops = -1;
_BackgroundMusicPlayer.currentTime = 0;
_BackgroundMusicPlayer.volume = 0.5;
}
}
Notes:
Read more about object communication
Read more about Objective C coding standards
Read more about class hierarchy
If I am understanding your code correctly, it seems you are creating a instance of your initial view controller and trying to stop the music player property of that instance. If that is the case, the music player you are trying to stop is already stopped, because it is a a separate instance of AVAudioPlayer that was created when you created an instance of your ViewController. In order to stop the music player from the first view controller, you could try this:
In the Settings.h file, add an AVAudioPlayer property just like in app.h
#property (strong, nonatomic) AVAudioPlayer *backgroundMusic;
Then when segueing to the settings view controller, pass the audio player to the new controller using prepareForSegue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"YourSegueName"]) {
if([segue.destinationViewController isKindOfClass:[YourSettingsClass class]]) {
YourSettingsClass *destination = segue.destinationViewController;
destination.backgroundMusic = self.BackgroundMusicPlayer;
}
}
}
You should now be able to simply call [self.backgroundMusic stop] and stop your tunes.
Ensure that you #import your Settings controller class in your app class to access it in the prepareForSegue method.
You can't access the instance of an object of another class created by it, by importing
it.Here You have to access the same object instance , in order to stop the AVAudioPlayer.
So you have to place the object somewhere unique, like AppDelegate.
Try declaring the AVAudioPlayer in the appdelegate.h.
In Appdeleagte.h
#property (nonatomic, strong) AVAudioPlayer *BackgroundMusicPlayer;
and in your app.h you can access the player as follows.
AppDelegate *appDelegate;
//in your viewDidLoad
appDelegate=[[UIApplication sharedApplication]delegate];
//in your PlayBackGroundMusic
appdelegate.avAudioPlayer=[[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath:resourcePath] error:&err];
[appDelegate.avAudioplayer play];
in your settings.h
AppDeleagte *appDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//initialise the audioPlayer object.
appDelegate=[[UIApplication sharedApplication]delegate];
}
you can stop the player by
[appDelegate.avAudioPlayer stop];
You can download the fixed project here
I am new to iOS and I'm trying to make an webview-based app with sidebar drawer and simple navigation (I'm using MFSideMenu library). I'm able to load URL with my webview:
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIWebViewDelegate>{
UIWebView *webView;
}
#property (retain, nonatomic) IBOutlet UIWebView *webView;
- (IBAction)showLeftMenuPressed:(id)sender;
- (void) loadURL;
#property (nonatomic, strong) NSURL *url;
#end
ViewController.m
#import "ViewController.h"
#import "MFSideMenu.h"
#interface ViewController ()
- (void)webViewDidFinishLoad:(UIWebView *)webView;
#end
#implementation ViewController
#synthesize webView;
#synthesize url;
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate=self;
if(self.url == Nil) {
NSString *myURL = #"http://google.com/";
self.url = [NSURL URLWithString:myURL];
}
[self loadURL];
}
-(void)loadURL{
NSLog(#"loadURL: %#", self.url);
[self.webView loadRequest:[NSURLRequest requestWithURL:self.url]];
}
When I'm trying to load new URL using following code, I'm receiving right url param value. Another controller is a sidebar (drawer) tableview where I'm selecting an item to change the URL showing in the main view (ViewController which contains webview).
Another controller:
ViewController *webViewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
NSString *urlString = #"http://kirk.kh.ua/";
NSString *encodedString=[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:encodedString];
webViewController.url = url;
//[self.navigationController pushViewController:webViewController animated:YES];
[webViewController loadURL];
[webViewController release];
You may notice that there is a commented line - I have tried this solution, but result is the same: when I'm calling code in another controller nothing happens with the ViewController's webview which is strange because in log output I see that loadURL method was called and it gives me a right URL (kirk.kh.ua).
UPD: I have noticed that on the first load webview as an object, and then it appears to be null (see log):
UPD2: I have noticed that webivew object and other synthesized objects are null when I'm calling them from another class, but this objects exist and accessible when I'm logging them from self (ViewController) controller.
Can anyone suggest how can I access ViewController class objects from another class?
Instead of trying to open the URL from the Another Controller - make a NSString property in the ViewController, then when you tap a cell just pass the URL as a string:
ViewController *webViewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
webViewController.urlString = #"http://kirk.kh.ua/";
And then add in the ViewController add something like this in the viewWillAppear:
if (![self.urlString isEqualToString:#""]) {
NSString *encodedString=[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:encodedString];
webViewController.url = url;
[webViewController loadURL];
}
I'm having trouble making a shopping cart sort-of concept in my app. I have my AppDelegate (named ST2AppDelegate) that contains an NSMutableArray called myCart. I want RecipeViewController.m to pass an NSString object to myCart, but every time I pass it the NSString and use NSLog to reveal the contents of the array, it is always empty.
Can anyone please tell me what I am doing wrong? I have worked on this code for days, and there is a line of code in which I don't understand at all what's going on (in the RecipeViewController.m, labeled as such).
Any help would be so appreciated... I'm just a beginner. Here are the relevant classes:
ST2AppDelegate.h:
#import <UIKit/UIKit.h>
#interface ST2AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) NSMutableArray* myCart;
- (void)addToCart:(NSString*)item;
- (void)readCartContents;
#end
ST2AppDelegate.m:
#import "ST2AppDelegate.h"
#implementation ST2AppDelegate
#synthesize myCart;
// all the 'applicationDid...' methods...
- (void)addToCart:(NSString *)item
{
[self.myCart addObject:item];
}
- (void)readCartContents
{
NSLog(#"Contents of cart: ");
int count = [myCart count];
for (int i = 0; i < count; i++)
{
NSLog(#"%#", myCart[count]);
}
}
#end
RecipeDetailViewController.h:
#import <UIKit/UIKit.h>
#import "ST2AppDelegate.h"
#interface RecipeDetailViewController : UIViewController
#property (nonatomic, strong) IBOutlet UILabel* recipeLabel;
#property (nonatomic, strong) NSString* recipeName;
#property (nonatomic, strong) IBOutlet UIButton* orderNowButton;
- (IBAction)orderNowButtonPress:(id)sender;
#end
RecipeDetailViewController.m:
#import "RecipeDetailViewController.h"
#implementation RecipeDetailViewController
#synthesize recipeName;
#synthesize orderNowButton;
// irrelevant methods...
- (IBAction)orderNowButtonPress:(id)sender
{
// alter selected state
[orderNowButton setSelected:YES];
NSString* addedToCartString = [NSString stringWithFormat:#"%# added to cart!",recipeName];
[orderNowButton setTitle:addedToCartString forState:UIControlStateSelected];
// show an alert
NSString* addedToCartAlertMessage = [NSString stringWithFormat:#"%# has been added to your cart.", recipeName];
UIAlertView* addedToCartAlert = [[UIAlertView alloc] initWithTitle:#"Cart Updated" message:addedToCartAlertMessage delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[addedToCartAlert show];
// add to cart (I don't understand this, but it works)
[((ST2AppDelegate*)[UIApplication sharedApplication].delegate) addToCart:recipeName];
// read cart contents
[((ST2AppDelegate*)[UIApplication sharedApplication].delegate) readCartContents];
}
#end
You need to initialize myCart when your application launches:
self.myCart = [[NSMutableArray alloc] init];
otherwise you are just attempting to add objects to a nil object which while it won't throw an exception because of the way objective-c handles nil objects it will not function as expected until you initialize it.
Do you ever initalize the shopping cart variable?
Try doing lazy instantiation.
-(NSMutableArray *) myCart{
if (!_myCart){
_myCart = [[NSMutableArray alloc] init];
}
return _myCart;
}
This way you will know it will always get allocated. Basically, this method makes it so that whenever someone calls your classes version of the object it checks to see if that object has been allocated and then allocates it if it has not. It's a common paradigm that you should employ with most of your objects.
This method should go in the app delegate (where the object was declared).
It´s only possible to give the AVAudioPlayer an URL of the file to play via it´s init method.
And as I understand it if one want´s to play another file, the old instance needs to be stopped from playing, and a new instance of AVAudioPlayer initialized with the URL to the new audiofile to play.
But this is making it hard cause I have a navigation controller, and when the user leaves the player screen the sound should keep playing, and it does. But when the user selects a new audio file to play from the tableview a new instance of the viewController and AVAudioPlayer is initalized and I have no way of stopping the old from playing. How do I get this working?
You can do something like this
In your v1AppDelegate.h file add,
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#include <AudioToolbox/AudioToolbox.h>
#interface v1AppDelegate : UIResponder <UIApplicationDelegate>
{
AVAudioPlayer *myAudioPlayer;
}
#property (nonatomic, retain) AVAudioPlayer *myAudioPlayer;
#property (strong, nonatomic) UIWindow *window;
#end
Now in your v1AppDelegate.m file add this
#import "v1AppDelegate.h"
#import <AVFoundation/AVFoundation.h>
#include <AudioToolbox/AudioToolbox.h>
#implementation v1AppDelegate
#synthesize window = _window;
#synthesize myAudioPlayer;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//start a background sound
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"Startsound" ofType: #"m4a"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath ];
myAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
myAudioPlayer.numberOfLoops = -1; //infinite loop
[myAudioPlayer play];
// Override point for customization after application launch.
return YES;
}
If you wish to stop or start this music from anywhere else in your code then simply add this
#import "v1AppDelegate.h"
- (IBAction)stopMusic
{
v1AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.myAudioPlayer stop];
}
- (IBAction)startMusic
{
v1AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.myAudioPlayer play];
}
I'm working on an ios app with uitabviewcontroller that plays some music. I don't want each tab viewcontroller to create it's own audio player. I want to have one single audio player and have all the viewcontrollers share it.
so I have created a class called player, which would initiate avaudioplayer with the song url and plays the song,
#import <AVFoundation/AVFoundation.h>
#interface player : NSObject {
AVAudioPlayer *theMainAudio;
}
-(void)playSong:(NSString *)songName;
#end
I want to create only one instance of this class and all my viewcontrollers share it. I've tried creating it in my delegate,
#interface AppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
player *theMainPlayer;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#property (nonatomic, retain) player *theMainPlayer;
#end
in .m file,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//some other stuff here....
theMainPlayer = [[player alloc]init];
return YES;
}
and then I called it in my viewcontrollers,
player myPlayer = ((AppDelegate *)[UIApplication sharedApplication].delegate).theMainPlayer;
but this didn't work.
can anyone tell me what's wrong with what I've done or if there is any other way to do what I want to do, which is to create a player object and share it among all of my viewcontrollers.
Thanks
create a singleton, in your player.m
#import "player.h"
#implementation player
static player *sharedInstance = nil;
+ (player *)sharedInstance {
if (sharedInstance == nil) {
sharedInstance = [[super allocWithZone:NULL] init];
}
return sharedInstance;
}
- (id)init
{
self = [super init];
if (self) {
// Work your initialising here as you normally would
}
return self;
}
-(void)playSong:(NSString *)songName
{
// do your stuff here
}
to use this class, just import the player.h
[[player sharedInstance] playSong:#"something"];
How did it go wrong? To me it seems the last line would cause compile error. You should add asterisk for class declaration :
player * myPlayer = ((AppDelegate *)[UIApplication sharedApplication].delegate).theMainPlayer;
Other than that, I didn't catch anything.
But I prefer using Singleton pattern for cases like this.
Reference :
http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/Singleton.html
http://www.galloway.me.uk/tutorials/singleton-classes/