How to record perfect loops in iOS and Xcode - audio
I've been struggling with this for about a year now trying to pin down my problem and represent it for others to see.
I've been writing an app that depends on 'GarageBand' like recording. That is, I want to record the user for exactly 8 beats, and then I want them to be able to loop this. I am playing a metronome for the user at the same time (User would be wearing head phones hearing the metronome, recording into the mic on their device)
I can manage to turn on recording for about 4.8 seconds (.6*8 beats), and the timer says it ran for 4.8 seconds, however my audio recording is always a bit shorter than 4.8. It's like 4.78, or 4.71 which causes a the loop to go out of whack.
I've experimented with AVAudioRecorder,AudioQueue, and AudioUnits thinking one the latter methods might result in solving my problem.
I am using NSTimer to fire off every .6 seconds playing a short blip for the metronome. After 4 beats, the metronome timer's function, turns on the recorder metronnome which waits for 4.6 seconds the stops recording.
I'm using time intervals to time how long the metro runs (looks pretty tight at 4.800xxx) and comparing this to the duration of the audio file which is always different.
I wish I could attach my project, but I guess I'll just have to settle with attaching my header and implementation. To test you'll have to make a project with the following IB characteristics:
Record, Play, Stop buttons
Song/Track duration label
Timer duration label
Debug label
If you launch the app, then hit record, you are 'counted in' with 4 beats, then the recorder starts. Tap your finger on the desk until the recorder stops. After 8 more beats (12 in total) the recorder stops.
You can see in the displays that the recorded track is a little shorter than 4.8 seconds, and in some cases, a lot shorter, causing the audio to not loop properly.
Does anyone know what I can do to tighten this up? Thanks for reading.
Here's my code:
//
// ViewController.h
// speakagain
//
// Created by NOTHING on 2014-03-18.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "CoreAudio/CoreAudioTypes.h"
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>
#import <AVFoundation/AVFoundation.h>
#interface ViewController : UIViewController
{
IBOutlet UIButton *btnRecord;
IBOutlet UIButton *btnPlay;
IBOutlet UIButton *btnStop;
IBOutlet UILabel *debugLabel;
IBOutlet UILabel *timerDuration;
IBOutlet UILabel *songDuration;
//UILabel *labelDebug;
struct AQRecorderState {
AudioStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
AudioFileID mAudioFile;
UInt32 bufferByteSize;
SInt64 mCurrentPacket;
bool mIsRunning; // 8
};
struct AQRecorderState aqData;
AVAudioPlayer *audioPlayer;
NSString *songName;
NSTimer *recordTimer;
NSTimer *metroTimer;
NSTimeInterval startTime, endTime, elapsedTime;
int inputBuffer;
int beatNumber;
}
#property (nonatomic, retain) IBOutlet UIButton *btnRecord;
#property (nonatomic, retain) IBOutlet UIButton *btnPlay;
#property (nonatomic, retain) IBOutlet UIButton *btnStop;
#property (nonatomic, retain) IBOutlet UILabel *debugLabel;
#property (nonatomic, retain) IBOutlet UILabel *timerDuration;
#property (nonatomic, retain) IBOutlet UILabel *songDuration;
- (IBAction) record;
- (IBAction) stop;
- (IBAction) play;
static void HandleInputBuffer (void *aqData,AudioQueueRef inAQ,AudioQueueBufferRef inBuffer,const AudioTimeStamp *inStartTime, UInt32 inNumPackets,const AudioStreamPacketDescription *inPacketDesc);
#end
Implementation:
//
// ViewController.m
// speakagain
//
// Created by NOTHING on 2014-03-18.
//
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize btnPlay, btnRecord,btnStop,songDuration, timerDuration, debugLabel;
- (void)viewDidLoad
{
debugLabel.text = #"";
songName =[[NSString alloc ]init];
//NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//NSString *documentsDirectory = [paths objectAtIndex:0];
songName = #"TestingQueue.caf";
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)prepareAudioQueue
{
//struct AQRecorderState *pAqData;
inputBuffer=0;
aqData.mDataFormat.mFormatID = kAudioFormatLinearPCM;
aqData.mDataFormat.mSampleRate = 44100.0;
aqData.mDataFormat.mChannelsPerFrame = 1;
aqData.mDataFormat.mBitsPerChannel = 16;
aqData.mDataFormat.mBytesPerPacket =
aqData.mDataFormat.mBytesPerFrame = aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket = 1;
// AudioFileTypeID fileType = kAudioFileAIFFType;
AudioFileTypeID fileType = kAudioFileCAFType;
aqData.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsBigEndian| kLinearPCMFormatFlagIsSignedInteger| kLinearPCMFormatFlagIsPacked;
AudioQueueNewInput (&aqData.mDataFormat,HandleInputBuffer, &aqData,NULL, kCFRunLoopCommonModes, 0,&aqData.mQueue);
UInt32 dataFormatSize = sizeof (aqData.mDataFormat);
// in Mac OS X, instead use
// kAudioConverterCurrentInputStreamDescription
AudioQueueGetProperty (aqData.mQueue,kAudioQueueProperty_StreamDescription,&aqData.mDataFormat,&dataFormatSize);
//Verify
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:songName];
NSLog(#"INITIALIZING FILE");
if ([fileManager fileExistsAtPath:txtPath] == YES) {
NSLog(#"PREVIOUS FILE REMOVED");
[fileManager removeItemAtPath:txtPath error:nil];
}
const char *filePath = [txtPath UTF8String];
CFURLRef audioFileURL = CFURLCreateFromFileSystemRepresentation ( NULL,(const UInt8 *) filePath,strlen (filePath),false );
AudioFileCreateWithURL (audioFileURL,fileType,&aqData.mDataFormat, kAudioFileFlags_EraseFile,&aqData.mAudioFile );
DeriveBufferSize (aqData.mQueue,aqData.mDataFormat,0.5,&aqData.bufferByteSize);
for (int i = 0; i < kNumberBuffers; ++i)
{
AudioQueueAllocateBuffer (aqData.mQueue,aqData.bufferByteSize,&aqData.mBuffers[i]);
AudioQueueEnqueueBuffer (aqData.mQueue,aqData.mBuffers[i], 0,NULL );
}
}
- (void) metronomeFire
{
if(beatNumber < 5)
{
//count in time.
// just play the metro beep but don't start recording
debugLabel.text = #"count in (1,2,3,4)";
[self playSound];
}
if(beatNumber == 5)
{
//start recording
aqData.mCurrentPacket = 0;
aqData.mIsRunning = true;
startTime = [NSDate timeIntervalSinceReferenceDate];
recordTimer = [NSTimer scheduledTimerWithTimeInterval:4.8 target:self selector:#selector(killTimer) userInfo:nil repeats:NO];
AudioQueueStart (aqData.mQueue,NULL);
debugLabel.text = #"Recording for 8 beats (1,2,3,4 1,2,3,4)";
[self playSound];
}
else if (beatNumber < 12)
{ //play metronome from beats 6-16
[self playSound];
}
if(beatNumber == 12)
{
[metroTimer invalidate]; metroTimer = nil;
[self playSound];
}
beatNumber++;
}
- (IBAction) play
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:songName];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#",txtPath]];
if (audioPlayer)
{
[audioPlayer stop];
audioPlayer = nil;
}
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (audioPlayer == nil)
{
NSLog(#"%#",[error description]);
}
else
{
[audioPlayer play];
[audioPlayer setNumberOfLoops:-1];
}
}
- (void) killTimer
{
//this is the timer function. Runs once after 4.8 seconds.
[self stop];
}
- (IBAction) stop
{
if (audioPlayer)
{
[audioPlayer stop];
audioPlayer = nil;
}
else
{
if(metroTimer)
{
[metroTimer invalidate];metroTimer = nil;
}
//Stop the audio queue
AudioQueueStop (aqData.mQueue,true);
aqData.mIsRunning = false;
AudioQueueDispose (aqData.mQueue,true);
AudioFileClose (aqData.mAudioFile);
//Get elapsed time of timer
endTime = [NSDate timeIntervalSinceReferenceDate];
elapsedTime = endTime - startTime;
//Get elapsed time of audio file
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
songName,
nil];
NSURL *audioFileURL = [NSURL fileURLWithPathComponents:pathComponents];
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:audioFileURL options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
//Log values
NSLog(#"Track Duration: %f",audioDurationSeconds);
NSLog(#"Timer Duration: %.6f", elapsedTime);
//Show values on GUI too
songDuration.text = [NSString stringWithFormat: #"Track Duration: %f",audioDurationSeconds];
timerDuration.text = [NSString stringWithFormat:#"Timer Duration: %#",[NSString stringWithFormat: #"%.6f", elapsedTime]];
debugLabel.text = #"Why is the duration of the track less than the duration the timer ran?";
}
}
-(void) playSound
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"blip2" ofType:#"aif"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)[NSURL fileURLWithPath:path], &soundID);
AudioServicesPlaySystemSound (soundID);
}
- (IBAction) record
{
[self prepareAudioQueue];
songDuration.text = #"";
timerDuration.text = #"";
//debugLabel.text = #"Please wait 12 beats (The first four are count in)";
//init beat number
beatNumber = 1;
//safe guard
if(aqData.mIsRunning)
{
AudioQueueStop (aqData.mQueue,true);
aqData.mIsRunning = false;
AudioQueueDispose (aqData.mQueue,true);
AudioFileClose (aqData.mAudioFile);
}
//start count in (metro will start recording)
//aqData.mCurrentPacket = 0;
//aqData.mIsRunning = true;
startTime = [NSDate timeIntervalSinceReferenceDate];
metroTimer = [NSTimer scheduledTimerWithTimeInterval:.6 target:self selector:#selector(metronomeFire) userInfo:nil repeats:YES];
//recordTimer = [NSTimer scheduledTimerWithTimeInterval:4.8 target:self selector:#selector(killTimer) userInfo:nil repeats:NO];
//AudioQueueStart (aqData.mQueue,NULL);
}
static void HandleInputBuffer (void *aqData,AudioQueueRef inAQ,AudioQueueBufferRef inBuffer,const AudioTimeStamp *inStartTime,UInt32 inNumPackets,const AudioStreamPacketDescription *inPacketDesc)
{
//boiler plate
NSLog(#"HandleInputBuffer");
struct AQRecorderState *pAqData = (struct AQRecorderState *) aqData;
if (inNumPackets == 0 && pAqData->mDataFormat.mBytesPerPacket != 0)
inNumPackets = inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
if (AudioFileWritePackets (pAqData->mAudioFile,false,inBuffer->mAudioDataByteSize,inPacketDesc,pAqData->mCurrentPacket,&inNumPackets,inBuffer->mAudioData) == noErr)
{
pAqData->mCurrentPacket += inNumPackets;
}
if (pAqData->mIsRunning == 0)
return;
AudioQueueEnqueueBuffer (pAqData->mQueue,inBuffer,0,NULL);
}
void DeriveBufferSize(AudioQueueRef audioQueue,AudioStreamBasicDescription ASBDescription,Float64 seconds,UInt32 *outBufferSize)
{
//boiler plate
static const int maxBufferSize = 0x50000;
int maxPacketSize = ASBDescription.mBytesPerPacket;
if(maxPacketSize == 0)
{
UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
AudioQueueGetProperty(audioQueue, kAudioQueueProperty_MaximumOutputPacketSize, &maxPacketSize, &maxVBRPacketSize);
NSLog(#"max buffer = %d",maxPacketSize);
}
Float64 numBytesForTime = ASBDescription.mSampleRate * maxPacketSize * seconds;
*outBufferSize = (UInt32)(numBytesForTime < maxBufferSize ? numBytesForTime : maxBufferSize);
}
OSStatus SetMagicCookieForFile (AudioQueueRef inQueue, AudioFileID inFile)
{
//boiler plate
OSStatus result = noErr;
UInt32 cookieSize;
if (AudioQueueGetPropertySize (inQueue,kAudioQueueProperty_MagicCookie,&cookieSize) == noErr)
{
char* magicCookie =(char *) malloc (cookieSize);
if (AudioQueueGetProperty (inQueue,kAudioQueueProperty_MagicCookie,magicCookie,&cookieSize) == noErr)
{
result = AudioFileSetProperty (inFile,kAudioFilePropertyMagicCookieData,cookieSize,magicCookie);
}
free (magicCookie);
}
return result;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This is a big topic so I doubt you'll get an answer big enough to re-architect the code that you've provided. However, I can give you links that will supply the vast majority of what you require.
First thing is NSTimer would never work due to synchronisation issues. Also, forget AudioQueue and AVAudioRecorder. Only AudioUnit is low level enough for your needs.
Have a look at my answer here:
iOS Stream Audio from one iOS Device to Another
But the true goldmine - and knowledge that you will need to be intimately familiar with - is Tasty Pixel's blog. Tasty Pixel being the vendor of Loopy HD, but also someone that is kind enough to share some pretty in depth knowledge.
See:
A simple, fast circular buffer implementation for audio processing
Developing Loopy, Part 2: Implementation
and
Using RemoteIO audio unit
Finally, make sure you are familiar with packets, frames, samples, etc. Everything needs to sync perfectly.
Related
Storing CLLocation (latitude, longtitude data) in to Core Data
i want to ask a question about core location and core data. i looked some questions but couldnt do that.. i have a application which stores some textfields, photos, date and time datas in UITableView With core data, i stored everything (photos, texts, date etc.) Now trying to store Location data i couldnt do. this is some of my code here. #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; locationManager = [[CLLocationManager alloc] init]; locationManager.delegate = self; locationManager.desiredAccuracy = kCLLocationAccuracyBest; [locationManager startUpdatingLocation]; NSDateFormatter *myFormatter = [[NSDateFormatter alloc] init]; [myFormatter setDateFormat:#"MM-dd-yyyy HH:mm"]; [myFormatter setTimeZone:[NSTimeZone systemTimeZone]]; todaysDate = [myFormatter stringFromDate:[NSDate date]]; myDateLabel.text = todaysDate; UIView *patternBg = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; patternBg.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"background01.png"]]; self.tableView.backgroundView = patternBg; // If we are editing an existing picture, then put the details from Core Data into the text fields for displaying if (currentPicture) { [companyNameField setText:[currentPicture companyName]]; [myDateLabel setText:[currentPicture currentDate]]; if ([currentPicture photo]) [imageField setImage:[UIImage imageWithData:[currentPicture photo]]]; } } in the saveButton - (IBAction)editSaveButtonPressed:(id)sender { // For both new and existing pictures, fill in the details from the form [self.currentPicture setCompanyName:[companyNameField text]]; [self.currentPicture setCurrentDate:[myDateLabel text]]; [self.currentPicture setCurrentTime:[myTimeLabel text]]; [self.currentPicture setLatitudeData:[_latitudeLabel text]]; [self.currentPicture setLongtidueData:[_longtitudeLabel text]]; } and last one, my locationManager's method.. - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSLog(#"didUpdateToLocation: %#", newLocation); CLLocation *currentLocation = newLocation; if (currentLocation != nil) { _longtitudeLabel.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.longitude]; _latitudeLabel.text = [NSString stringWithFormat:#"%.8f", currentLocation.coordinate.latitude]; [self->locationManager stopUpdatingLocation]; } } i tried "[locationmanager stopUpdatingLocation];" many times, but when i entered the app, code starts to calculating latitude and longtitude data, i just want to take that data 1 time, and store.. Thanks!
If calling stopUpdatingLocation doesn't stop location updates, then most likely self->locationManager is nil. That would mean you're not really making the call. It's hard to be sure exactly why this would happen, except that your code seem to make a point of not using any semantics implied by a #property declaration. Just assigning to location in viewDidLoad avoids any declaration, and looking up the manager using self->locationManager does as well. Assuming that location is a property, you should assign it to self.locationManager, and use that when looking it up as well.
A couple things: - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow]; if (locationAge > 5) return; // ignore cached location, we want current loc if (newLocation.horizontalAccuracy <= 0) return; // ignore invalid // wait for GPS accuracy (will be < 400) if (newLocation.horizontalAccuracy < 400) { _longtitudeLabel.text = [NSString stringWithFormat:#"%.8f", newLocation.coordinate.longitude]; _latitudeLabel.text = [NSString stringWithFormat:#"%.8f", newLocation.coordinate.latitude]; [manager stopUpdatingLocation]; } }
In your didUpdateToLocation do this code (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSTimeInterval locationAge = -[newLocation.timestamp timeIntervalSinceNow]; if (locationAge > 5) return; // ignore cached location, we want current loc if (newLocation.horizontalAccuracy <= 0) return; // ignore invalid // wait for GPS accuracy (will be < 400) if (newLocation.horizontalAccuracy < 400) { _longtitudeLabel.text = [NSString stringWithFormat:#"%.8f", newLocation.coordinate.longitude]; _latitudeLabel.text = [NSString stringWithFormat:#"%.8f", newLocation.coordinate.latitude]; [manager stopUpdatingLocation]; //assign nil to locationManager object and delegate locationManager.delegate = nil; locationManager = nil; } } Thanks.
Custom MKAnnotaionView, Display title/subtitle Position on Map
1) My annotation shows the title and subtitle of not. I have already tried a lot, but I can not. 2) If an annotation has been selected, the annotation will be centered in the middle of the window. So the map is moved. Any idea? 3) My Callout button does not work anymore, I've got the # selector (openAnything) but I want to use this function is triggered - (void) mapView: (MKMapView *) mapView annotationView: (MKAnnotationView *) view calloutAccessoryControlTapped: (UIControl *) control { Enough to ask, here's a video and some source code http://www.youtube.com/watch?v=Ur1aqeYEFHw&feature=youtube_gdata_player Thanks TAPPMapViewControler.m - (MKAnnotationView *)mapView:(MKMapView *)MapView viewForAnnotation:(id<MKAnnotation>)annotation { if ([annotation isKindOfClass:[TAPPMapAnnotation class]]) { static NSString *shopAnnotationIdentifier = #"Filiale"; //pinView = (MKPinAnnotationView *) TAPPMapAnnotationCustom* pinView = (TAPPMapAnnotationCustom*)[self.myMapView dequeueReusableAnnotationViewWithIdentifier:shopAnnotationIdentifier]; [self.myMapView dequeueReusableAnnotationViewWithIdentifier:shopAnnotationIdentifier]; if (pinView == nil) { // if an existing pin view was not available, create one TAPPMapAnnotationCustom *customPinView = [[TAPPMapAnnotationCustom alloc] initWithAnnotation:annotation reuseIdentifier:shopAnnotationIdentifier]; customPinView.image = [UIImage imageNamed:#"map_pin.png"]; return customPinView; } else { pinView.annotation = annotation; } return pinView; } return nil; } -(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control { TAPPMapAnnotation *theAnnotation = view.annotation; TAPPMapAnnotationDetail *theController = [self.storyboard instantiateViewControllerWithIdentifier:#"MapAnnotationDetail"]; theController.theAnnotationId = theAnnotation.objectId; [self.navigationController pushViewController:theController animated:YES]; } TAPPMapAnnotationCustom.h #import <MapKit/MapKit.h> #interface TAPPMapAnnotationCustom : MKAnnotationView #property (strong, nonatomic) UIImageView *calloutView; #property (strong, nonatomic) UILabel *title; #property (strong, nonatomic) UILabel *subTitle; - (void)setSelected:(BOOL)selected animated:(BOOL)animated; - (void)animateCalloutAppearance:(BOOL)inAdd; #end TAPPMapAnnotationCustom.m #import "TAPPMapAnnotationCustom.h" #import "TAPPMapAnnotation.h" #implementation TAPPMapAnnotationCustom - (void)setSelected:(BOOL)selected animated:(BOOL)animated{ [super setSelected:selected animated:animated]; if(selected) { // Remove Image, because we set a second large one. self.image = Nil; UIImage *imageBack = [[UIImage imageNamed:#"map_pin.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 35, 0, 6)]; self.calloutView = [[UIImageView alloc]initWithImage:imageBack]; [self.calloutView setFrame:CGRectMake(0,0,0,0)]; [self.calloutView sizeToFit]; [self addSubview:self.calloutView ]; // Callout Button UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; [rightButton addTarget:self action:#selector(openAnything) forControlEvents:UIControlEventTouchUpInside]; rightButton.frame = CGRectMake(0 , ((self.calloutView.frame.size.height-6) / 2)-(rightButton.frame.size.height / 2) , rightButton.frame.size.width , rightButton.frame.size.height); self.rightCalloutAccessoryView = rightButton; self.rightCalloutAccessoryView.hidden = YES; self.rightCalloutAccessoryView.alpha = 0; [self addSubview:self.rightCalloutAccessoryView]; // Start Annimation [self animateCalloutAppearance:YES]; } else { //Start Annimation and Remove from view [self animateCalloutAppearance:NO]; } } - (void)didAddSubview:(UIView *)subview{ if ([[[subview class] description] isEqualToString:#"UICalloutView"]) { for (UIView *subsubView in subview.subviews) { if ([subsubView class] == [UIImageView class]) { UIImageView *imageView = ((UIImageView *)subsubView); [imageView removeFromSuperview]; }else if ([subsubView class] == [UILabel class]) { UILabel *labelView = ((UILabel *)subsubView); [labelView removeFromSuperview]; } } } } - (void)animateCalloutAppearance:(BOOL)inAdd { if (inAdd == YES) { self.rightCalloutAccessoryView.hidden = NO; [UIView animateWithDuration:0.4 delay:0 options: UIViewAnimationOptionTransitionNone animations:^{ self.calloutView.frame = CGRectMake(self.calloutView.frame.origin.x , self.calloutView.frame.origin.y , self.calloutView.frame.size.width+150 , self.calloutView.frame.size.height); self.rightCalloutAccessoryView.alpha = 1; self.rightCalloutAccessoryView.frame = CGRectMake(self.calloutView.frame.size.width - (self.rightCalloutAccessoryView.frame.size.width) , self.rightCalloutAccessoryView.frame.origin.y , self.rightCalloutAccessoryView.frame.size.width , self.rightCalloutAccessoryView.frame.size.height); } completion:^(BOOL finished){ }]; } else { [UIView animateWithDuration:0.4 delay:0 options: UIViewAnimationOptionTransitionNone animations:^{ self.rightCalloutAccessoryView.alpha = 0; self.rightCalloutAccessoryView.frame = CGRectMake(0 , self.rightCalloutAccessoryView.frame.origin.y , self.rightCalloutAccessoryView.frame.size.width , self.rightCalloutAccessoryView.frame.size.height); self.calloutView.frame = CGRectMake(self.calloutView.frame.origin.x , self.calloutView.frame.origin.y , self.calloutView.frame.size.width-150 , self.calloutView.frame.size.height); } completion:^(BOOL finished){ self.image = [UIImage imageNamed:#"map_pin.png"]; [self.calloutView removeFromSuperview]; [self.rightCalloutAccessoryView removeFromSuperview]; }]; } } #end
Your annotation class needs to have a subtitle attribute, yours has subTitle. If calloutAccessoryControlTapped is not being called it is usually a sign your MKMapview's delegate has not been set. However you're only setting the accessory button when it gets selected. Do you have a good reason for that? Since the callout window won't be visible until it is selected, I would advise you to set the accessory button in viewForAnnotation and let it appear when the callout window does.
I fix the first one: TAPPMapAnnotation *theAnnotation = (TAPPMapAnnotation *)self.annotation; self.title = [[UILabel alloc]init]; self.title.font = [UIFont systemFontOfSize:12.0]; self.title.textColor = [UIColor whiteColor]; self.title.backgroundColor = [UIColor clearColor]; self.title.text = theAnnotation.title; [...] [self.theView addSubview:self.title];
Singleton class for displaying iAds for iPhone
I have written a Singleton Class for managing iAds.The iAds pop up after 5 seconds of the user inactivity. The idleTimerExceeded call generate a notification to show the iAd. This code works fine for my requirements but since I am new to iOS development, my application sometimes hangs unexpectedly after integrating this code. This code results in lots of warnings etc. I would like to optimize my code in terms of memory and performance. I would be very thankful for your kind suggestions and reviews. Below is my code: iAdSingleton.h #import <Foundation/Foundation.h> #import "AppDelegate.h" #import "iAd/iAd.h" #interface iAdSingleton : UIViewController<ADBannerViewDelegate> { ADBannerView *adView; UIViewController *displayVC; NSTimer *idleTimer; BOOL isItFirstTime; } #property (nonatomic, retain) ADBannerView *adView; #property (nonatomic, retain) UIViewController *displayVC; #property (nonatomic) BOOL isItFirstTime; + (id) shareAdSingleton; - (void) resetIdleTimer; - (void) idleTimerExceeded; #end iAdSingleton.m #import "iAdSingleton.h" #implementation iAdSingleton static iAdSingleton* _sharedAdSingleton = nil; BOOL bannerVisible = NO; BOOL controlAccessBannerVisibility = NO; #synthesize adView, displayVC; #synthesize isItFirstTime; #define kMaxIdleTimeSeconds 5.0 +(id)sharedAdSingleton { #synchronized(self) { if(!_sharedAdSingleton) _sharedAdSingleton = [[self alloc] init]; return _sharedAdSingleton; } return nil; } +(id)alloc { #synchronized([iAdSingleton class]) { NSAssert(_sharedAdSingleton == nil, #"Attempted to allocate a second instance of a singleton."); _sharedAdSingleton = [super alloc]; return _sharedAdSingleton; } return nil; } -(id)init { self = [super init]; if (self != nil) { /* Initialize The Parameters Over Here */ //adView = [[ADBannerView alloc] initWithFrame:CGRectMake(0, 480, 0, 0)]; adView = [[ADBannerView alloc] init]; adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait; self.adView.delegate=self; [self resetIdleTimer]; } return self; } -(void)dealloc { displayVC = nil; if (adView) { [adView removeFromSuperview]; //Remove ad view from superview [adView setDelegate:nil]; adView = nil; } [super dealloc]; } -(UIViewController *)viewControllerForPresentingModalView { return displayVC; } - (void)bannerViewDidLoadAd:(ADBannerView *)banner { banner.hidden = NO; if(!bannerVisible){ NSLog(#"Banner Changes 1 - Purpose: Visibility"); // [UIView beginAnimations:#"bannerAppear" context:NULL]; // banner.frame = CGRectOffset(banner.frame, 0, -100); // [UIView commitAnimations]; bannerVisible = YES; controlAccessBannerVisibility = YES; } } - (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error { //NSLog(#"Unable to receive Ad."); NSLog(#"Banner Changes 2 - Purpose: Unable to Receive Ad."); banner.hidden = YES; if(bannerVisible){ [UIView beginAnimations:#"bannerDisappear" context:NULL]; banner.frame = CGRectOffset(banner.frame, 0, 100); [UIView commitAnimations]; bannerVisible = NO; } } - (BOOL) bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave { NSLog(#"Pause anything necessary"); return YES; } - (void) bannerViewActionDidFinish:(ADBannerView *)banner { NSLog(#"We now resume to normal operations"); } - (void)resetIdleTimer { if (!idleTimer) { idleTimer = [[NSTimer scheduledTimerWithTimeInterval:kMaxIdleTimeSeconds target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO] retain]; } else { if (fabs([idleTimer.fireDate timeIntervalSinceNow]) < kMaxIdleTimeSeconds-1.0) { [idleTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:kMaxIdleTimeSeconds]]; /* Notification: HideAd */ NSLog(#"Notification Generated For HideAd"); [[NSNotificationCenter defaultCenter] postNotificationName:#"HideAdBanner" object:nil userInfo:nil]; } } } - (void)idleTimerExceeded { AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate]; if (appDel.adVisible == NO) { NSLog(#"Notification Generated For ShowAd"); /* Notification: ShowAd */ if (controlAccessBannerVisibility == YES) { [[NSNotificationCenter defaultCenter] postNotificationName:#"ShowAdBanner" object:nil userInfo:nil]; } } } #end
This is what you need. This code is thread safe as well as will not have any memory issue and warnings. + (iAdSingleton *) sharedInstance { static dispatch_once_t onceToken; static iAdSingleton * __sharedInstance = nil; dispatch_once(&onceToken, ^{ __sharedInstance = [[self alloc] init]; }); return __sharedInstance; }
Find assets in library - add to a AVMutableComposition - export = crash
I've been struggling with adding assets from the iPhone Photo Library to a AVMutableComposition and then export them. Here is what I got: Finding the assets: (here I grab the AVURLAsset) -(void) findAssets { ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init]; // Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos. [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) { // Within the group enumeration block, filter to enumerate just videos. [group setAssetsFilter:[ALAssetsFilter allVideos]]; [group enumerateAssetsUsingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop){ // The end of the enumeration is signaled by asset == nil. if (alAsset) { ALAssetRepresentation *representation = [alAsset defaultRepresentation]; NSURL *url = [representation url]; AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil]; // Do something interesting with the AV asset. [thumbs addObject:alAsset]; [assets addObject:avAsset]; }else if(alAsset == nil){ [self createScroll]; } }]; } failureBlock: ^(NSError *error) { // Typically you should handle an error more gracefully than this. NSLog(#"No groups"); }]; [library release]; } Here I add a asset to my composition (I use the first object in the array for testing only. -(void) addToCompositionWithAsset:(AVURLAsset*)_asset{ NSError *editError = nil; composition = [AVMutableComposition composition]; AVURLAsset* sourceAsset = [assets objectAtIndex:0]; Float64 inSeconds = 1.0; Float64 outSeconds = 2.0; // calculate time CMTime inTime = CMTimeMakeWithSeconds(inSeconds, 600); CMTime outTime = CMTimeMakeWithSeconds(outSeconds, 600); CMTime duration = CMTimeSubtract(outTime, inTime); CMTimeRange editRange = CMTimeRangeMake(inTime, duration); [composition insertTimeRange:editRange ofAsset:sourceAsset atTime:composition.duration error:&editError]; if (!editError) { CMTimeGetSeconds (composition.duration); } } And finally I export the comp and here it crashes -(void)exportComposition { AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; NSLog (#"can export: %#", exportSession.supportedFileTypes); NSArray *dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectoryPath = [dirs objectAtIndex:0]; NSString *exportPath = [documentsDirectoryPath stringByAppendingPathComponent:EXPORT_NAME]; [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil]; NSURL *exportURL = [NSURL fileURLWithPath:exportPath]; exportSession.outputURL = exportURL; exportSession.outputFileType = AVFileTypeQuickTimeMovie;//#"com.apple.quicktime-movie"; [exportSession exportAsynchronouslyWithCompletionHandler:^{ NSLog (#"i is in your block, exportin. status is %d", exportSession.status); switch (exportSession.status) { case AVAssetExportSessionStatusFailed: case AVAssetExportSessionStatusCompleted: { [self performSelectorOnMainThread:#selector (exportDone:) withObject:nil waitUntilDone:NO]; break; } }; }]; } Does anyone have an idea of what it might be? It crashes on AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough]; And I tried different presets and outputFileTypes. Thanks * SOLVED *
I have to answer my own question now when I have solved. It's amazing that I've been struggling with this for a whole day and then I fix it right after posting a question :) I changed and moved: composition = [AVMutableComposition composition]; to: composition = [[AVMutableComposition alloc] init]; I think I was too tired when I was working on this yesterday. Thanks guys!
My custom class object gets released even after retaining it
I have been trying to debug this error like for days and still can't seem to grasp what really is going on with my object. The code goes like this. - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType { // extract data from clicked link SongObject *Data = [[SongObject alloc] init]; [Data retain]; selectedSong = Data; } selectedSong is public variable in the main class, now the crazy thing is that using the above method works everywhere else but shoulStartLoadWithRequest. Trying to deubg it gives me "Out Of Scope", could it be by some crazy reason that "shouldStartLoadWithRequest" is autoreleasing my object even when I say don't? Any idea's? Edit: This is the H and M file which selectedSong is suppose to hold. #import <Foundation/Foundation.h> #import "SongObject.h" #interface SongObject : NSObject<NSCopying> { NSString *artist; NSString *titel; NSString *album; NSString *genre; NSString *songUrl; NSString *rating; NSString *image; BOOL local; } #property (readonly) NSString *getArtist; #property (readonly) NSString *getTitle; #property (readonly) NSString *getAlbum; #property (readonly) NSString *getGenre; #property (readonly) NSString *getSongURL; #property (readonly) NSString *getRating; #property (readonly) NSString *getImage; #property (readonly) BOOL isLocal; -(void) setInfo:(NSString *) a: (NSString*) t: (NSString*) ab: (NSString*)g: (NSString*)sU: (NSString*)r: (NSString*) i: (BOOL) loc; #end #import "SongObject.h" #implementation SongObject -(id)init { if(self =[super init]) { NSLog(#"Init songobject"); } return self; } -(id) copyWithZone: (NSZone *) zone { SongObject *newSong = [[SongObject allocWithZone:zone] init]; NSLog(#"_copy: %#", [newSong self]); [newSong setInfo:[self getArtist] :[self getTitle] :[self getAlbum] :[self getGenre] :[self getSongURL] :[self getRating] :[self getImage] :[self isLocal]]; return(newSong); } -(void)dealloc { NSLog(#"Songobject deallocted from memory..."); [super dealloc]; } -(void) setInfo:(NSString *) a: (NSString*) t: (NSString*) ab: (NSString*)g: (NSString*)sU: (NSString*)r: (NSString*) i: (BOOL) loc{ artist = a; titel = t; album = ab; genre = g; songUrl = sU; rating = r; image = i; local = loc; } -(NSString*)getArtist { return artist; } -(NSString*)getTitle { return titel; } -(NSString*)getAlbum { return album; } -(NSString*)getGenre { return genre; } -(NSString*)getSongURL { return songUrl; } -(NSString*)getRating { return rating; } -(NSString*)getImage { return image; } -(BOOL)isLocal{ return local; } #end
That doesn't seem possible. Be careful, cause you're increasing the reference count twice. Once in the alloc, and again with the retain. What does selectedSong looks like? is it a property with automated retain? Also, sometimes I cannot read the object in the debugger and I use NSLog. What do you see then? What about overriding -dealloc. Did you try setting a breakpoint there?
I finally found the problem the SongObject class with the NSString variables was never retained. So the selectedSong was actually never released but the variables within the class it self. Fixed it by using NSString alloc initWithString and then overriding the dealloc method and releasing them in there.