I have a very simple asynchronous operation
#interface IDBWebpValidationOperation()
#property BOOL executing;
#property BOOL finished;
#end
#implementation IDBWebpValidationOperation
#synthesize executing;
#synthesize finished;
- (instancetype)init
{
self = [super init];
if (self) {
self.completionBlock = ^{
NSDLog(#"webp validation has finished");
};
}
return self;
}
- (void)main
{
IDBAssert0(self.bestCapture.webpCandidate);
self.finished = NO;
if(self.postProcessingValidator) {
self.executing = YES;
// this starts async operation, see callback below
self.postProcessingValidator(self.bestCapture.webpCandidate);
}else{
IDBAssert0(0);
// self.bestCapture.jpegNSData = self.bestCapture.webpCandidate;
IDBAssert0(self.bestCapture.jpegNSData);
self.executing = NO;
self.finished = YES;
}
}
- (void)scanningViewController: (UIViewController<PPScanningViewController>*)scanningViewController
didOutputResults:(NSArray*)results
{
if([results count]>0) {
self.bestCapture.jpegNSData = self.bestCapture.webpCandidate;
IDBAssert0(self.bestCapture.jpegNSData);
}else{
IDBAssert0(self.microblinkFailureHandler);
self.microblinkFailureHandler();
}
IDBAssert0(!self.finished);
self.executing = NO;
self.finished = YES;
}
-(BOOL)isAsynchronous
{
return YES; //Default is NO so overriding it to return YES;
}
Here I have synthesized two atomic properties executing & finished
to avoid lots of (in my opinion dumb & extraneous) code swift would force you to do for KVO and the synthesided atomic properties would get me out of the box
(I think).
The issue is completionBlock is called before scanningViewController:
callback is invoked. Why?????
This is on ios 9.3.x in case this matters
here is my code, i'm new to NSThread, why these code doesn't execute? plz help.
Basically i have a viewcontroller, and i call TESTC() when a button is pressed,
you should know TESTC() is a C function.
Thanks in advance.
#interface Worker : NSObject
#property (nonatomic, strong) NSThread *thread;
- (void)performTask1 : (void *)context;
#end
#implementation Worker
- (instancetype)init
{
if (self = [super init]) {
_thread = [[NSThread alloc] init];
}
return self;
}
- (void)performTask1 : (void *)context
{
[self performSelector:#selector(Task1) onThread:self.thread withObject:(__bridge id _Nullable)(context) waitUntilDone:YES];
}
- (void)Task1
{
NSLog(#"Task1 proceed");
}
#end
void TESTC()
{
Worker *kelvin = [[Worker alloc] init];
[kelvin performTask1:nil];
}
I am trying to integrate SkyDrive in my iOS application and the following is the code where I have written the login authentication code. The client ID provided here is the one assigned for the application at the developer site. But the authentication always fails. Any idea what is the error ?
static NSString * const CLIENT_ID = #"00000000XXXXXXXXX";
#implementation PSMainViewController
#synthesize appLogo;
#synthesize userInfoLabel;
#synthesize signInButton;
#synthesize viewPhotosButton;
#synthesize userImage;
#synthesize liveClient;
#synthesize currentModal;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
_scopes = [NSArray arrayWithObjects:
#"wl.signin",
#"wl.basic",
#"wl.skydrive",
#"wl.offline_access", nil];
liveClient = [[LiveConnectClient alloc] initWithClientId:CLIENT_ID
scopes:_scopes
delegate:self];
}
return self;
}
- (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.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.appLogo.image = [UIImage imageNamed:#"skydrive.jpeg"];
[self updateUI];
}
- (void)viewDidUnload
{
[self setAppLogo:nil];
[self setUserInfoLabel:nil];
[self setUserImage:nil];
[self setSignInButton:nil];
[self setViewPhotosButton:nil];
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (IBAction)signinButtonClicked:(id)sender {
if (self.liveClient.session == nil)
{
[self.liveClient login:self
scopes:_scopes
delegate:self];
}
else
{
[self.liveClient logoutWithDelegate:self
userState:#"logout"];
}
}
- (IBAction)viewPhotoButtonClicked:(id)sender {
// Create a Navigation controller
PSSkyPhotoViewer *aPhotoViewer = [[PSSkyPhotoViewer alloc] initWithNibName:#"PSSkyPhotoViewer" bundle:nil];
aPhotoViewer.parentVC = self;
self.currentModal = [[UINavigationController alloc] initWithRootViewController:aPhotoViewer];
[self presentModalViewController:self.currentModal animated:YES];
}
- (void) modalCompleted:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
self.currentModal = nil;
}
#pragma mark LiveAuthDelegate
- (void) updateUI {
LiveConnectSession *session = self.liveClient.session;
if (session == nil) {
[self.signInButton setTitle:#"Sign in" forState:UIControlStateNormal];
self.viewPhotosButton.hidden = YES;
self.userInfoLabel.text = #"Sign in with a Microsoft account before you can view your SkyDrive photos.";
self.userImage.image = nil;
}
else {
[self.signInButton setTitle:#"Sign out" forState:UIControlStateNormal];
self.viewPhotosButton.hidden = NO;
self.userInfoLabel.text = #"";
[self.liveClient getWithPath:#"me" delegate:self userState:#"me"];
[self.liveClient getWithPath:#"me/picture" delegate:self userState:#"me-picture"];
}
}
- (void) authCompleted: (LiveConnectSessionStatus) status
session: (LiveConnectSession *) session
userState: (id) userState {
[self updateUI];
}
- (void) authFailed: (NSError *) error
userState: (id)userState {
// Handle error here
}
#pragma mark LiveOperationDelegate
- (void) liveOperationSucceeded:(LiveOperation *)operation {
if ([operation.userState isEqual:#"me"]) {
NSDictionary *result = operation.result;
id name = [result objectForKey:#"name"];
self.userInfoLabel.text = (name != nil)? name : #"";
}
if ([operation.userState isEqual:#"me-picture"]) {
NSString *location = [operation.result objectForKey:#"location"];
if (location) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:location]];
self.userImage.image = [UIImage imageWithData:data];
}
}
}
- (void) liveOperationFailed:(NSError *)error operation:(LiveOperation *)operation
{
// Handle error here.
}
#end
May be you are not providing the redirect url in case of a desktop application.But if it's the mobile application the you have to check the "Mobile or Desktop client app: " as yes in the api settings
I am trying to make use of CLLocationManager in an NSOperation. As part of this I require the ability to startUpdatingLocation then wait until a CLLocation is received before completing the operation.
At present I have done the following, however the delegate method never seems to be called. Please can someone advise what the issue is?
- (void)main
{
#autoreleasepool {
if (self.isCancelled)
return;
// Record the fact we have not found the location yet
shouldKeepLooking = YES;
// Setup the location manager
NSLog(#"Setting up location manager.");
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
while (shouldKeepLooking) {
if (self.isCancelled)
return;
// Do some other logic...
}
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// None of this ever seems to be called (despite updating the location)
latestLocation = [locations lastObject];
[manager stopUpdatingLocation];
shouldKeepLooking = NO;
}
Going back to the runloop discussion, this is how I generally solve that in my base NSOperation implementation:
// create connection and keep the current runloop running until
// the operation has finished. this allows this instance of the operation
// to act as the connections delegate
_connection = [[NSURLConnection alloc] initWithRequest:[self request]
delegate:self];
while(!self.isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
I key off of isFinished, which I keep updated through setters for isCancelled and isFinished. Here's the isCancelled setter as an example:
- (void)setIsCancelled:(BOOL)isCancelled {
_isCancelled = isCancelled;
if (_isCancelled == YES) {
self.isFinished = YES;
}
}
That said, I second some of the questions about why this is necessary. If you don't need to kick something off until a location is found, why not just fire up your location manager on the main thread, wait for the appropriate delegate callback and then kick off the background operation?
Update: updated solution
While the original answer generally stands, I've fully implement a solution and it does require a slight change to how you manage the run loop. That said, all code is available on GitHub - https://github.com/nathanhjones/CLBackgroundOperation. Here is a detailed explanation of the approach.
Tl;dr
Change
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
to
[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes
beforeDate:[NSDate distantFuture]];
Details
Within your operations interface define the following three properties. We'll be indicating that these operations are concurrent thus we'll manage their state manually. In the solution on GitHub these are part of NJBaseOperation.
#property(nonatomic,assign,readonly) BOOL isExecuting;
#property(nonatomic,assign,readonly) BOOL isFinished;
#property(nonatomic,assign,readonly) BOOL isCancelled;
Within your operations implementation you'll want to make those readwrite like so:
#interface NJBaseOperation ()
#property(nonatomic,assign,readwrite) BOOL isExecuting;
#property(nonatomic,assign,readwrite) BOOL isFinished;
#property(nonatomic,assign,readwrite) BOOL isCancelled;
#end
Next, you'll want to synthesize the three properties you defined above so that you can override the setters and use them to manage your operations state. Here's what I generally use, but sometimes there are some additional statements added to the setIsFinished: method depending on my needs.
- (void)setIsExecuting:(BOOL)isExecuting {
_isExecuting = isExecuting;
if (_isExecuting == YES) {
self.isFinished = NO;
}
}
- (void)setIsFinished:(BOOL)isFinished {
_isFinished = isFinished;
if (_isFinished == YES) {
self.isExecuting = NO;
}
}
- (void)setIsCancelled:(BOOL)isCancelled {
_isCancelled = isCancelled;
if (_isCancelled == YES) {
self.isFinished = YES;
}
}
Lastly, just so that we don't have to manually send the KVO notifications we'll implement the following method. This works because our properties are named isExecuting, isFinished and isCancelled.
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
return YES;
}
Now that the the operations foundation is taken care of it's time to knockout the location stuff. You'll want to override main and within it fire up your location manager and instruct the current run loop to keep running until you tell it otherwise. This ensures that your thread is around to receive the location delegate callbacks. Here's my implementation:
- (void)main {
if (_locationManager == nil) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[_locationManager startUpdatingLocation];
}
while(!self.isFinished) {
[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes
beforeDate:[NSDate distantFuture]];
}
}
You should receive a delegate callback at which point you can do some work based on location and then finish the operation. Here's my implementation that counts to 10,000 and then cleans up.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"** Did Update Location: %#", [locations lastObject]);
[_locationManager stopUpdatingLocation];
// do something here that takes some length of time to complete
for (int i=0; i<10000; i++) {
if ((i % 10) == 0) {
NSLog(#"Loop %i", i);
}
}
self.isFinished = YES;
}
The source on GitHub includes a dealloc implementation, which simply logs that it's being called and also observes changes to the operationCount of my NSOperationQueue and logs the count - to indicating when it drops back to 0. Hope that helps. Let me know if you've got questions.
I think you have two options.
Create a separate thread, with its own run loop, for location services:
#import "LocationOperation.h"
#import <CoreLocation/CoreLocation.h>
#interface LocationOperation () <CLLocationManagerDelegate>
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation LocationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self performSelector:#selector(main) onThread:[[self class] locationManagerThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]];
}
- (void)main
{
[self startStandardUpdates];
}
- (void)dealloc
{
NSLog(#"%s", __FUNCTION__);
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (executing != _executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (finished != _finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self stopStandardUpdates];
[super cancel];
[self completeOperation];
}
#pragma mark - Location Manager Thread
+ (void)locationManagerThreadEntryPoint:(id __unused)object
{
#autoreleasepool {
[[NSThread currentThread] setName:#"location manager"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)locationManagerThread
{
static NSThread *_locationManagerThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_locationManagerThread = [[NSThread alloc] initWithTarget:self selector:#selector(locationManagerThreadEntryPoint:) object:nil];
[_locationManagerThread start];
});
return _locationManagerThread;
}
#pragma mark - Location Services
- (void)startStandardUpdates
{
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 500;
[self.locationManager startUpdatingLocation];
}
- (void)stopStandardUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
// do whatever you want with the location
// now, turn off location services
if (location.horizontalAccuracy < 50) {
[self stopStandardUpdates];
[self completeOperation];
}
}
#end
Alternatively, even though you're using an operation, you could just run location services on the main thread:
#import "LocationOperation.h"
#import <CoreLocation/CoreLocation.h>
#interface LocationOperation () <CLLocationManagerDelegate>
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation LocationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self startStandardUpdates];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (executing != _executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (finished != _finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self stopStandardUpdates];
[super cancel];
[self completeOperation];
}
#pragma mark - Location Services
- (void)startStandardUpdates
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 500;
[self.locationManager startUpdatingLocation];
}];
}
- (void)stopStandardUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
// do whatever you want with the location
// now, turn off location services
if (location.horizontalAccuracy < 50) {
[self stopStandardUpdates];
[self completeOperation];
}
}
#end
I think I'd be inclined to do the second approach (just making sure that I don't do anything too intensive in didUpdateLocations, or if I did, make sure to do it asynchronously), but both of these approaches appear to work.
Another approach is to keep the run loop alive until the operation is finished:
while (![self isFinished]) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
But this doesn't appear to work in conjunction with CLLocationManager, as runUntilDate doesn't immediately return (it's almost as if CLLocationManager is attaching its own source to the runloop, which prevents it from exiting). I guess you could change the runUntilDate to something a little closer than distantFuture (e.g. [NSDate dateWithTimeIntervalSinceNow:1.0]). Still, I think it's just as easy to run this operation start location services on the main queue, like the second solution above.
Having said that, I'm not sure why you would want to use location manager in an operation at all. It's already asynchronous, so I would just start the location manager from the main queue and call it a day.
UIWebView with UIWebViewDelegate method callbacks in an NSOperation
A server I wanted to grab a URL from a server that changes values based upon JavaScript execution from various browsers. So I slapped a dummy UIWebView into an NSOperation and use that to grab out the value I wanted in the UIWebViewDelegate method.
#interface WBSWebViewOperation () <UIWebViewDelegate>
#property (assign, nonatomic) BOOL stopRunLoop;
#property (assign, nonatomic, getter = isExecuting) BOOL executing;
#property (assign, nonatomic, getter = isFinished) BOOL finished;
#property (copy, nonatomic, readwrite) NSURL *videoURL;
#property (strong, nonatomic) UIWebView *webView;
#end
#implementation WBSWebViewOperation
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (id)initWithURL:(NSURL *)episodeURL
{
self = [self init];
if (self != nil) {
_episodeURL = episodeURL;
}
return self;
}
- (void)start
{
if (![self isCancelled]) {
self.executing = YES;
[self performSelector:#selector(main) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]];
} else {
self.finished = YES;
}
}
- (void)main
{
if (self.episodeURL != nil) {
NSURLRequest *request = [NSURLRequest requestWithURL:self.episodeURL];
UIWebView *webView = [[UIWebView alloc] init];
webView.delegate = self;
[webView loadRequest:request];
self.webView = webView;
}
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self.webView stopLoading];
[super cancel];
[self completeOperation];
}
#pragma mark - UIWebViewDelegate methods
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *episodeVideoURLString = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('playerelement').getAttribute('data-media')"];
NSURL *episodeVideoURL = [NSURL URLWithString:episodeVideoURLString];
self.videoURL = episodeVideoURL;
if ([self.delegate respondsToSelector:#selector(webViewOperationDidFinish:)]) {
[self.delegate webViewOperationDidFinish:self];
}
[self completeOperation];
}
#end
Its going to call the delegate method in the same operation queue as main is running in. And NSOperation queues are serial by default. Your while loop is just spinning forever (because the operation is never cancelled) and the call to your delegate method is sitting in the queue behind it never able to run.
Get rid of the while loop entirely and let the operation finish. Then when the delegate method is called, if it's cancelled discard the result by returning.
I'm struggling for couple of days now to sort this thing out and simply just can't find the way. I want to play audio in background when app exits or when i click on link to go to Safari, but i just wont go to background mode. Please help.
FirstViewController.h file :
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVAudioPlayer.h>
#import <AVFoundation/AVFoundation.h>
#interface RygestopFirstViewController : UIViewController <AVAudioPlayerDelegate> {
IBOutlet UIButton *playButton;
IBOutlet UISlider *volumeSlider;
IBOutlet UISlider *progressBar;
IBOutlet UILabel *currentTime;
IBOutlet UILabel *duration;
AVAudioPlayer *player;
UIImage *playBtnBG;
UIImage *pauseBtnBG;
NSTimer *updateTimer;
BOOL inBackground;
}
- (IBAction)playButtonPressed:(UIButton*)sender;
- (IBAction)volumeSliderMoved:(UISlider*)sender;
- (IBAction)progressSliderMoved:(UISlider*)sender;
#property (nonatomic, retain) UIButton *playButton;
#property (nonatomic, retain) UISlider *volumeSlider;
#property (nonatomic, retain) UISlider *progressBar;
#property (nonatomic, retain) UILabel *currentTime;
#property (nonatomic, retain) UILabel *duration;
#property (nonatomic, retain) NSTimer *updateTimer;
#property (nonatomic, assign) AVAudioPlayer *player;
#property (nonatomic, assign) BOOL inBackground;
#end
FirstViewController.m code:
// amount to skip on rewind or fast forward
#define SKIP_TIME 1.0
// amount to play between skips
#define SKIP_INTERVAL .2
#implementation RygestopFirstViewController
#synthesize playButton;
#synthesize volumeSlider;
#synthesize progressBar;
#synthesize currentTime;
#synthesize duration;
#synthesize updateTimer;
#synthesize player;
#synthesize inBackground;
-(void)updateCurrentTimeForPlayer:(AVAudioPlayer *)p
{
currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)p.currentTime / 60, (int)p.currentTime % 60, nil];
progressBar.value = p.currentTime;
}
- (void)updateCurrentTime
{
[self updateCurrentTimeForPlayer:self.player];
}
- (void)updateViewForPlayerState:(AVAudioPlayer *)p
{
[self updateCurrentTimeForPlayer:p];
if (updateTimer)
[updateTimer invalidate];
if (p.playing)
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(updateCurrentTime) userInfo:p repeats:YES];
}
else
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
updateTimer = nil;
}
}
- (void)updateViewForPlayerStateInBackground:(AVAudioPlayer *)p
{
[self updateCurrentTimeForPlayer:p];
if (p.playing)
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
}
else
{
[playButton setImage:((p.playing == YES) ? pauseBtnBG : playBtnBG) forState:UIControlStateNormal];
}
}
-(void)updateViewForPlayerInfo:(AVAudioPlayer*)p
{
duration.text = [NSString stringWithFormat:#"%d:%02d", (int)p.duration / 60, (int)p.duration % 60, nil];
progressBar.maximumValue = p.duration;
volumeSlider.value = p.volume;
}
-(void)pausePlaybackForPlayer:(AVAudioPlayer*)p
{
[p pause];
[self updateViewForPlayerState:p];
}
-(void)startPlaybackForPlayer:(AVAudioPlayer*)p
{
if ([p play])
{
[self updateViewForPlayerState:p];
}
else
NSLog(#"Could not play %#\n", p.url);
}
- (IBAction)playButtonPressed:(UIButton *)sender
{
if (player.playing == YES)
[self pausePlaybackForPlayer: player];
else
[self startPlaybackForPlayer: player];
}
- (IBAction)volumeSliderMoved:(UISlider *)sender
{
player.volume = [sender value];
}
- (IBAction)progressSliderMoved:(UISlider *)sender
{
player.currentTime = sender.value;
[self updateCurrentTimeForPlayer:player];
}
- (void)dealloc
{
[super dealloc];
[playButton release];
[volumeSlider release];
[progressBar release];
[currentTime release];
[duration release];
[updateTimer release];
[player release];
[playBtnBG release];
[pauseBtnBG release];
}
#pragma mark AVAudioPlayer delegate methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
[p setCurrentTime:0.];
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
[self updateViewForPlayerState:p];
}
}
- (void)playerDecodeErrorDidOccur:(AVAudioPlayer *)p error:(NSError *)error
{
NSLog(#"ERROR IN DECODE: %#\n", error);
}
// we will only get these notifications if playback was interrupted
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption begin. Updating UI for new state");
// the object has already been paused, we just need to update UI
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
[self updateViewForPlayerState:p];
}
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption ended. Resuming playback");
[self startPlaybackForPlayer:p];
}
#pragma mark background notifications
- (void)registerForBackgroundNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setInBackgroundFlag)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clearInBackgroundFlag)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)setInBackgroundFlag
{
inBackground = true;
}
- (void)clearInBackgroundFlag
{
inBackground = false;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"Play", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"Home"];
}
return self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
return YES;
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
playBtnBG = [[UIImage imageNamed:#"Player.png"] retain];
pauseBtnBG = [[UIImage imageNamed:#"Pause.png"] retain];
[playButton setImage:playBtnBG forState:UIControlStateNormal];
[self registerForBackgroundNotifications];
updateTimer = nil;
duration.adjustsFontSizeToFitWidth = YES;
currentTime.adjustsFontSizeToFitWidth = YES;
progressBar.minimumValue = 0.0;
// Load the the sample file, use mono or stero sample
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:#"Sound1" ofType:#"m4a"]];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
if (self.player)
{
[self updateViewForPlayerInfo:player];
[self updateViewForPlayerState:player];
player.numberOfLoops = 0;
player.delegate = self;
}
[fileURL release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
And, by the way, i want to run thin in tab bar application, so the background mode must be present always.
Here's what you're looking for: https://devforums.apple.com/message/264397 and set your background mode to 'App plays audio' in your app's .plist file.