viewDidUnload - stopping a method when the view is changed - iphone

I'm looking to stop an audio file from playing when the view is changed.
I'm using a tabController and I would like the audio that's playing to stop when the user moves to a different view. I'm not sure where and how I would do this. in the viewDidUnload perhaps?
here's the method I'm using to play the audio file:
-(void)startPlaying {
[NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(startPlaying) userInfo:nil repeats:NO];
NSString *audioSoundPath = [[ NSBundle mainBundle] pathForResource:#"audio_file" ofType:#"caf"];
CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioSoundPath];
AudioServicesCreateSystemSoundID(audioURL, &audioID);
AudioServicesPlaySystemSound(audioID);
}
thanks for any help

Something like this in your view controller (untested):
- (void)viewDidLoad
{
[super viewDidLoad];
// Load sample
NSString *audioSoundPath = [[NSBundle mainBundle] pathForResource:#"audio_file"
ofType:#"caf"];
CFURLRef audioURL = (CFURLRef)[NSURL fileURLWithPath:audioSoundPath];
AudioServicesCreateSystemSoundID(audioURL, &audioID)
}
- (void)viewDidUnload
{
// Dispose sample when view is unloaded
AudioServicesDisposeSystemSoundID(audioID);
[super viewDidUnload];
}
// Lets play when view is visible (could also be changed to viewWillAppear:)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startPlaying];
}
// Stop audio when view is gone (could also be changed to viewDidDisappear:)
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if([self.audioTimer isValid]) {
[self.audioTimer invalidate];
}
self.timer = nil;
}
// Start playing sound and reschedule in 15 seconds.
-(void)startPlaying
{
self.audioTimer = [NSTimer scheduledTimerWithTimeInterval:15 target:self
selector:#selector(startPlaying)
userInfo:nil
repeats:NO];
AudioServicesPlaySystemSound(audioID);
}
Missing:
Error checking
Property or ivar for audioTimer.
Could also keep the same timer, with repeats YES.
Freeing resources in dealloc.
Testing

Related

When using storyboards, and changing a view, sound will not stop playing in new view

I am trying to do something similar to what was requested in this question:
Playing audio with controls in iOS
I am using Storyboards with XCode 4.2. On my initial view page, I just have buttons that call other views, and no sounds are played there. These other views contain a UINavigationController with a BarButtonItem to allow you to go back. Other view pages will have buttons that allow one to play a sound. Each sound has its own play button, and there are resume, pause and stop buttons.
All sounds play fine, but if I hit the back button to the main page, the sound keeps playing. The desired behavior is for the sound to stop when navigating back to the main page via the back button.
Before storyboards, I could easily code a back button to stop the audio, but storyboards appear not that simple. It appears that I have to implement the method prepareForSegue. Okay, I can set the Segue identifier can be set in the Attributes Inspector, and referring back to the earlier post, I will use showPlayer.
But I was thinking I could code the prepareForSegue in one of my sound views, as seen in this cool example:
http://www.scott-sherwood.com/?p=219
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier] isEqualToString:#"showPlayer"]){
SoundPageController *theAudio = (SoundPageController *)[segue ViewController];
theAudio.delegate = self;
}
}
In the main page ViewController.m, I tried adding:
#property (strong, nonatomic) AVAudioPlayer *theAudio;
And I tried adding something like:
- (void)viewDidLoad {
[super viewDidLoad];
// this is the important part: if we already have something playing, stop it!
if (audioPlayer != nil)
{
[audioPlayer stop];
}
// everything else should happen as normal.
audioPlayer = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
I wasn't able to get this to work, so I reverted the changes back to original. I will put my simplified code below, and if you have suggestions how to do it right, please let me know!
ViewController.h:
#interface ViewController : UIViewController {
}
ViewController.m (pretty much default):
#import "ViewController.h"
#import "AppDelegate.h"
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
SoundPage1.h:
#import
#import <AVFoundation/AVAudioPlayer.h>
#interface occupy : UIViewController <AVAudioPlayerDelegate> {
AVAudioPlayer* theAudio;
}
//-(IBAction)PressBack:(id)sender; now the back button is linked up through storyboard
-(IBAction)pushButton1;
-(IBAction)pushButton2;
-(IBAction)play;
-(IBAction)stop;
-(IBAction)pause;
#end
SoundPage1.m
#import "SoundPage1.h"
#import "AppDelegate.h"
#import "ViewController.h"
#implementation SoundPage1
-(IBAction)pushButton {
NSString *path = [[NSBundle mainBundle] pathForResource:#"sound1" ofType:#"mp3"];
if(theAudio)[theAudio release];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
theAudio.volume = 1.0;
[theAudio play];
}
-(IBAction)pushButton2 {
NSString *path = [[NSBundle mainBundle] pathForResource:#"sound2" ofType:#"mp3"];
if(theAudio)[theAudio release];
theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
theAudio.delegate = self;
theAudio.volume = 1.0;
[theAudio play];
}
// Old code from XCode 3.x when creating a back button that could stop the audio was easy
//-(IBAction)PressBack:(id)sender{
// [theAudio stop];
// ViewController* myappname = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
// [self.navigationController pushViewController:myappname animated:NO];
// }
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
Add in the code for play, stop and pause for the buttons.
I tried to format this, sorry in advance if it is hard to read.
Thanks for any suggestions!
Rob
You should stop the AVAudioPlayer in viewWillDisappear.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if(theAudio && theAudio.isPlaying) {
[theAudio stop];
}
}
ViewDidUnload is only called by the program when the device is reaching the end of memory capacity, not when you change views. Only just found this out myself this week, while struggling with the same problem.
Hope that clears things up.

NSMutableArray elements not being released in dealloc

I have define a NSMutableArray in .h file
NSMutableArray *arrayBatLevel;
init it in .m file
- (void)viewDidLoad {
[super viewDidLoad];
[self DataTimer];
}
-(void)DataTimer
{
[recordDataTimer invalidate];
recordDataTimer = [NSTimer scheduledTimerWithTimeInterval:[timeInterval.text floatValue]
target:self
selector:#selector(recordData)
userInfo:nil
repeats:YES];
}
-(void)recordData
{
if ([aSwitch isOn] == YES) {
if (arrayBatLevel == nil) {
arrayBatLevel = [[NSMutableArray alloc] init];
NSLog(#"alloc arrayBatLevel");
}
[arrayBatLevel addObject:batLevel.text];
}
}
and release in dealloc
- (void)dealloc {
[arrayBatLevel release];
[super dealloc];
}
but, it seems it’s not releasing all the objects inside the NSMutableArray.
When I exit this app and run it gain, these objects still in NSMutableArray, why?
Your problem is not with the array, it's with the objects inside the array. You don't indicate where "batLevel" came from. I'm guessing that "batLevel" is retained somewhere else, and with it "batLevel.text".

UIScrollView with images slideshow and timers

i'm trying to implement a slideshow of images with previews horizontaly and vertically, and i want to be able to do some stuff when the user stop scrolling and after 5 seconds do this stuff. I use 2 timers to handle this but i need some synchronization or maybe i'm in the wrong way! Why i use 2 timers : because i want to handle scrolling in the main view (-(void)touchesBegan and -(void)touchesEnded)and scrolling on the scrollView( UIScrollView delegate). my code is below and i hope someone can help :). Thanks.
// This is the slideShow view controller
// It display all the images with preview (right, left, up and down)
#interface ImageSlideShowViewController : UIViewController<UIScrollViewDelegate>
{
UIScrollView *scrollViewForSlideShow;
// Timers
NSTimer *autoscrollTimer;
NSTimer *mainViewTimer;
}
#end
#implementation ImageSlideShowViewController
#pragma mark Touch handling
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{
....
// Invalidate all timers
[autoscrollTimer invalidate];
autoscrollTimer = nil;
[mainViewTimer invalidate];
mainViewTimer = nil;
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
....
assert(mainViewTimer == nil);
mainViewTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(allViewTimerFireMethod:)
userInfo:nil
repeats:NO];
// prev image horizontal
...
// next image horizontal
....
// prev image vertical
...
// next image vertical
....
}
#pragma mark -
#pragma mark UIScrollView delegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// Invalidate all timers
[autoscrollTimer invalidate];
autoscrollTimer = nil;
[mainViewTimer invalidate];
mainViewTimer = nil;
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
//Doing some management stuff here
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (!scrollView.dragging)
{
//Doing some management stuff here
assert(autoscrollTimer == nil);
autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(timerFireMethod:)
userInfo:nil
repeats:NO];
}
}
#pragma mark -
#pragma mark Timers methods
- (void)timerFireMethod:(NSTimer*)theTimer
{
autoscrollTimer = nil;
// Call a method that dispaly the image
}
- (void)allViewTimerFireMethod:(NSTimer *)theTimer
{
mainViewTimer = nil;
// Call a method that dispaly the image (same as the method called above)
}
#end
I think that you could get rid of the two timers and define an intermediate function, say enableTimerFor:, that you could call from -scrollViewDidEndDecelerating: and -touchesEnded:withEvent: instead of creating there the 2 timers.
enableTimerFor: would basically create the timer and schedule it:
-(void)enableTimerFor:(NSUInteger)callerId {
viewTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(timerFired:)
userInfo:nil
repeats:NO];
}
-(void)timerFired {
// do whatever you need to
}
And it would be called like this:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
....
[self enableTimerFor:kTouchesEnded];
....
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (!scrollView.dragging) {
//Doing some management stuff here
[self enableTimerFor:kScrollEnded];
}
....
}
Like that, you would have no problem in syncing. Notice also that I thought of enableTimerFor: as having a parameter that allows you to identify whether it was called from one function or the other, but you can decide easily to get rid of it in case you don't need to distinguish the two cases.
I am not sure that this suggestion could work well for you, since I don't know all the details of your solution, but still I think this is the most direct suggestion given the information I have.

Making this run while other views are in 'use'

Hello
Okay right I have a view called 'RecordViewController' this has the voice recorder function so once the user presses 'record', it records their voice, I have also created a 'back' button. But once that back button is tapped the voice recording also stops. I want it so once the user goes back, its still recording their voice.
Here is the code I used:
.h
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudioTypes.h>
#interface RecordViewController : UIViewController <AVAudioRecorderDelegate> {
IBOutlet UIButton * btnStart;
IBOutlet UIButton * btnPlay;
IBOutlet UIActivityIndicatorView * actSpinner;
BOOL toggle;
NSURL * recordedTmpFile;
AVAudioRecorder * recorder;
NSError * error;
NSTimer *theTimer;
IBOutlet UILabel *seconds;
int mainInt;
NSString *timeRemainingString;
}
#property (nonatomic,retain)IBOutlet UIActivityIndicatorView * actSpinner;
#property (nonatomic,retain)IBOutlet UIButton * btnStart;
#property (nonatomic,retain)IBOutlet UIButton * btnPlay;
- (IBAction) start_button_pressed;
- (IBAction) play_button_pressed;
-(IBAction)goBack:(id)sender;
-(void)countUp;
#end
.m
#import "RecordViewController.h"
#implementation RecordViewController
#synthesize actSpinner, btnStart, btnPlay;
-(void)countUp {
mainInt += 1;
seconds.text = [NSString stringWithFormat:#"%02d", mainInt];
}
-(IBAction)goBack:(id)sender; {
[self dismissModalViewControllerAnimated:YES];
}
/*
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
// Custom initialization
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
//Start the toggle in true mode.
toggle = YES;
btnPlay.hidden = YES;
//Instanciate an instance of the AVAudioSession object.
AVAudioSession * audioSession = [AVAudioSession sharedInstance];
//Setup the audioSession for playback and record.
//We could just use record and then switch it to playback leter, but
//since we are going to do both lets set it up once.
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error: &error];
//Activate the session
[audioSession setActive:YES error: &error];
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (IBAction) start_button_pressed{
if(toggle)
{
toggle = NO;
[actSpinner startAnimating];
[btnStart setImage:[UIImage imageNamed:#"stoprecording.png"] forState:UIControlStateNormal];
mainInt = 0;
theTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countUp) userInfo:nil repeats:YES];
btnPlay.enabled = toggle;
btnPlay.hidden = !toggle;
//Begin the recording session.
//Error handling removed. Please add to your own code.
//Setup the dictionary object with all the recording settings that this
//Recording sessoin will use
//Its not clear to me which of these are required and which are the bare minimum.
//This is a good resource: http://www.totodotnet.net/tag/avaudiorecorder/
NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatAppleIMA4] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
//Now that we have our settings we are going to instanciate an instance of our recorder instance.
//Generate a temp file for use by the recording.
//This sample was one I found online and seems to be a good choice for making a tmp file that
//will not overwrite an existing one.
//I know this is a mess of collapsed things into 1 call. I can break it out if need be.
recordedTmpFile = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: #"%.0f.%#", [NSDate timeIntervalSinceReferenceDate] * 1000.0, #"caf"]]];
NSLog(#"Using File called: %#",recordedTmpFile);
//Setup the recorder to use this file and record to it.
recorder = [[ AVAudioRecorder alloc] initWithURL:recordedTmpFile settings:recordSetting error:&error];
//Use the recorder to start the recording.
//Im not sure why we set the delegate to self yet.
//Found this in antother example, but Im fuzzy on this still.
[recorder setDelegate:self];
//We call this to start the recording process and initialize
//the subsstems so that when we actually say "record" it starts right away.
[recorder prepareToRecord];
//Start the actual Recording
[recorder record];
//There is an optional method for doing the recording for a limited time see
//[recorder recordForDuration:(NSTimeInterval) 10]
}
else
{
toggle = YES;
[actSpinner stopAnimating];
[btnStart setImage:[UIImage imageNamed:#"recordrecord.png"] forState:UIControlStateNormal];
btnPlay.enabled = toggle;
btnPlay.hidden = !toggle;
[theTimer invalidate];
NSLog(#"Using File called: %#",recordedTmpFile);
//Stop the recorder.
[recorder stop];
}
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
-(IBAction) play_button_pressed{
//The play button was pressed...
//Setup the AVAudioPlayer to play the file that we just recorded.
AVAudioPlayer * avPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:recordedTmpFile error:&error];
[avPlayer prepareToPlay];
[avPlayer play];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
//Clean up the temp file.
NSFileManager * fm = [NSFileManager defaultManager];
[fm removeItemAtPath:[recordedTmpFile path] error:&error];
//Call the dealloc on the remaining objects.
[recorder dealloc];
recorder = nil;
recordedTmpFile = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
Thanks
You are in need of a shared object that your application can access during the recording process. Something along the lines of
#interface UserRecording : NSObject
+(UserRecording*)currentRecording;
#end
You will want to take into account all of the fun threading principles as well as delegation and/or notifications if you would like to properly implement this.
Since you are creating the AudioSession inside of the modal dialog, it will be dealloc'ed when you dismiss that modal dialog. Try instantiating the AudioSession in the main VC, pass that into the modal VC and just start it there.

Help changing NSTimer for textLabel. Code included

want to change my NSTimer code to to use applicationSignificantTimeChange
This is what I have right now.
Basically I want to change the timer, instead of changing every 5 seconds for example I want these textLabels to change at 12am every night anyone help me out?
//h file
NSTimer *timer;
IBOutlet UILabel *textLabel;
//m file
- (void)onTimer {
static int i = 0;
if ( i == 0 ) {
textLabel.text = #"iphone app";
}
else if ( i == 1 ) {
textLabel.text = #" Great App!";
}
else if ( i == 2 ) {
textLabel.text = #" WOW!";
}
else {
textLabel.text = #" great application again!!";
i = -1;
}
i++;
}
timer =[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
There are two things you can do to respond to a applicationSignificantTimeChange:
(1) Just implement applicationSignificantTimeChange: in your app delegate if you have a connection to the view controller that should be updated.
- (void)applicationSignificantTimeChange:(UIApplication *)application {
yourViewController.textLabel.text = #"random text";
}
(2) Subscribe to the UIApplicationSignificantTimeChangeNotification notification in the view controller that should get the update. You could perhaps put that code in viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onSignificantTimeChange:)
name:UIApplicationSignificantTimeChangeNotification
object:nil];
}
- (void)onSignificantTimeChange:(NSNotification *)notification {
self.textLabel.text = #"random text";
}
You also need to call
[[NSNotificationCenter defaultCenter] removeObserver:self];
somewhere when your view controller is not longer needed.
the selector for the timer should be:
-(void) onTimer:(NSTimer *) timer
{
// yourcode
}
and called with:
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
you need the "onTimer*:*"
[EDIT]
for calling a function that will go off at 12:am, for low-performance (i.e. not 12:00:00:0000) you could do the following for like an app that sits in the background:
NSTimeInterval minute = 60.0f
[NSTimer scheduledTimerWithTimeInterval:minute target:self selector:#selector(onTimer:) userInfo:nil repeats:YES]
-(void) onTimer:(NSTimer *) timer
{
NSDate *midnight = [NSDate dateWithNaturalLanguageString:#"today at 12:00 am"]; // unsure of this line
if ([midnight timeIntervalSinceDate:[NSDate date]] < 30.0f)
// set off alarm.
}
Seems pretty straightforward to me -- just create an NSDate for midnight, calculate the time interval of 24 hours in seconds, and create the NSTimer with
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats