AVAudioPlayer breaking video capture - objective-c

In one of the views of my app there's a button. When pressed it is supposed to begin taking a video, trigger a sound file to start, and hide itself from view while unhiding another button. The second button is supposed to stop the video recording and make it save. Here's the code I have for the video recording, which initially worked with no problems:
in viewDidLoad:
finishButton.hidden = TRUE;
session = [[AVCaptureSession alloc] init];
movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
NSError *error;
AVCaptureDeviceInput *videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:[self cameraWithPosition:AVCaptureDevicePositionFront] error:&error];
if (videoInput)
{
[session addInput:videoInput];
}
AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
NSError *audioError = nil;
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioCaptureDevice error:&audioError];
if (audioInput)
{
[session addInput:audioInput];
}
Float64 TotalSeconds = 35; //Total seconds
int32_t preferredTimeScale = 30; //Frames per second
CMTime maxDuration = CMTimeMakeWithSeconds(TotalSeconds, preferredTimeScale);
movieFileOutput.maxRecordedDuration = maxDuration;
movieFileOutput.minFreeDiskSpaceLimit = 1024 * 1024;
if ([session canAddOutput:movieFileOutput])
[session addOutput:movieFileOutput];
[session setSessionPreset:AVCaptureSessionPresetMedium];
if ([session canSetSessionPreset:AVCaptureSessionPreset640x480]) //Check size based configs are supported before setting them
[session setSessionPreset:AVCaptureSessionPreset640x480];
[self cameraSetOutputProperties];
[session startRunning];
and for the button:
-(IBAction)start:(id)sender
{
startButton.hidden = TRUE;
finishButton.hidden = FALSE;
//Create temporary URL to record to
NSString *outputPath = [[NSString alloc] initWithFormat:#"%#%#", NSTemporaryDirectory(), #"output.mov"];
self.outputURL = [[NSURL alloc] initFileURLWithPath:outputPath];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:outputPath])
{
NSError *error;
if ([fileManager removeItemAtPath:outputPath error:&error] == NO)
{
//Error - handle if required
}
}
//Start recording
[movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
finally, under the last button:
[movieFileOutput stopRecording];
and here's the code to save the video:
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error
{
NSLog(#"didFinishRecordingToOutputFileAtURL - enter");
BOOL RecordedSuccessfully = YES;
if ([error code] != noErr)
{
// A problem occurred: Find out if the recording was successful.
id value = [[error userInfo] objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value)
{
RecordedSuccessfully = [value boolValue];
}
}
if (RecordedSuccessfully)
{
//----- RECORDED SUCESSFULLY -----
NSLog(#"didFinishRecordingToOutputFileAtURL - success");
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL])
{
[library writeVideoAtPathToSavedPhotosAlbum:outputURL
completionBlock:^(NSURL *assetURL, NSError *error)
{
if (error)
{
}
}];
}
}
}
All of this was working just fine. Then I added a few lines so that a song file would play when the start button was pressed.
in viewDidLoad:
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Song.aiff", [[NSBundle mainBundle] resourcePath]]];
NSError *audioFileError;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&audioFileError];
player.numberOfLoops = 0;
[self.player prepareToPlay];
and under the start button:
if (player == nil)
NSLog(#"Audio file could not be played");
else
[player play];
Now when the start button is pressed the song plays with no problems, but the video capture is messed up. Before adding the AVAudioPlayer stuff I would get the "didFinishRecordingToOutputFileAtURL - enter" and "didFinishRecordingToOutputFileAtURL - success" logs when I pressed the finish button, and now I get the first log as soon as I press the start button, nothing happens when I press the finish button, and no video is recorded. If I comment out the lines that make the song play then the video capture works just fine again. Any ideas what's going on here?

- (void)setupAudioSession
{
static BOOL audioSessionSetup = NO;
if (audioSessionSetup)
{
return;
}
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: nil];
UInt32 doSetProperty = 1;
AudioSessionSetProperty (kAudioSessionProperty_OverrideCategoryMixWithOthers, sizeof(doSetProperty), &doSetProperty);
[[AVAudioSession sharedInstance] setActive: YES error: nil];
audioSessionSetup = YES;
}
- (void)playAudio
{
[self setupAudioSession];
NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:#"btnClick" ofType:#"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:soundFilePath];
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[fileURL release];
self.audioPlayer = newPlayer;
[newPlayer release];
[audioPlayer setDelegate:self];
[audioPlayer prepareToPlay];
audioPlayer.volume=1.0;
[audioPlayer play];
}
NOTE: Add the framework: AudioToolbox.framework.
#import <AudioToolbox/AudioServices.h>

Related

Pause/Resume download with AFNetworking 3.0 and NSURLSessionDownloadTask Objective-C, OSX

I have trouble with creating Pause/Resume download methods in my Objective-C osx app, I have tried some of the answers I found here but nothing seems to work.
Here is my code: `-(void)pauseDownload:(id)sender {
if(!downloadTask) return;
[downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
if (!resumeData) return;
[self setDownloadResumeData:resumeData];
[self setDownloadTask:nil];
}];
NSString *datastring = [[NSString alloc] initWithData:downloadResumeData encoding:NSUTF8StringEncoding];
isDownloading = NO;
NSLog(#"data %#", datastring);}`
Continue method:
-(void)continueDownload:(id)sender {
if (!self.downloadResumeData) {
return;
}
self.downloadTask = [manager.session downloadTaskWithResumeData:self.downloadResumeData];
[self.downloadTask resume];
[self setDownloadResumeData:nil];}
And download request with download task:
-(void)downloadRequest:(NSString *)url toDirectory:(NSString *)directoryName atIndex:(NSInteger) rowIndex {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:url];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSString *downloadDirectoryPath = [[NSUserDefaults standardUserDefaults] objectForKey:#"downloadPath"];
NSURL *downloadURL;
if (directoryName != nil || ![directoryName isEqualToString:#""]) {
downloadURL = [self createDownloadDirectoryIn:downloadDirectoryPath withName:directoryName];
} else {
downloadURL = [NSURL URLWithString:[[NSUserDefaults standardUserDefaults] objectForKey:#"downloadFolderPath"]];
}
downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(#"%#", downloadProgress);
// [self downloadStartedWithDownloadId:downloadID andDeviceId:userDeviceID];
isDownloading = YES;
NSNumber *downloadPercentage = [NSNumber numberWithInteger:downloadProgress.fractionCompleted *100];
progressArray[rowIndex] = downloadPercentage;
[self updateProgressBars];
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
return [downloadURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
[[files objectAtIndex:self.tableOfContents.selectedRow] setIsFinishedDownloading:YES];
// [self downloadFinishedWithDownloadID:downloadID andDeviceID:userDeviceID];
NSLog(#"File downloaded to: %#", filePath);
}] ;
[downloadTask resume];}
Appreciate any help.

Objective C - Recording Audio Stream

I'm looking for a solution to record an audio stream to a file. I can get audio to play but I'm struggling to figure out how to record/save to file what is playing.
A nudge in the right direction would be greatly appreciated.
Player code thus far:
#interface ViewControllerPlayer ()
#end
#implementation ViewControllerPlayer
#synthesize receivedStreamReferenceNumber;
- (void)convertData:(NSData *) data {
NSString *urlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSURL *url = [[NSURL alloc] initWithString:urlString];
[self loadPlayer:url];
}
- (void) loadPlayer:(NSURL *) url {
audioPlayer = [AVPlayer playerWithURL:url];
[audioPlayer play];
}
- (void) start {
NSLog(#"%#", receivedStreamReferenceNumber);
NSString *urlHalf = #"http://getstreamurl.php?KeyRef=";
NSMutableString *mutableUrlString = [NSMutableString stringWithFormat:#"%#%#", urlHalf, receivedStreamReferenceNumber];
NSURL *url = [NSURL URLWithString: mutableUrlString];
NSData *data = [NSData dataWithContentsOfURL:url];
[self convertData:data];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self start];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
EDIT:
I've incorporated the following code... but i'm struggling to tie it all together. The following should create a file that the recorded stream audio is saved to. I suppose i've got to tell the AVRecorder to listen to the AVPlayer some how? Again -- help will be greatly appreciated:
- (void)viewDidLoad
{
[super viewDidLoad];
[stopButton setEnabled:YES];
[playButton setEnabled:YES];
// Set the audio file
NSArray *pathComponents = [NSArray arrayWithObjects:
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject],
#"xxx.mp3",
nil];
NSURL *outputFileURL = [NSURL fileURLWithPathComponents:pathComponents];
// Setup audio session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
NSMutableDictionary *recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatMPEG4AAC] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey];
[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
recorder = [[AVAudioRecorder alloc] initWithURL:outputFileURL settings:recordSetting error:nil];
recorder.delegate = self;
recorder.meteringEnabled = YES;
[recorder prepareToRecord];
}

Download video and save in custom album in library?

i have created a album in my photo library using this code
NSString *albumName=#"iphonemaclover";
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library addAssetsGroupAlbumWithName:albumName
resultBlock:^(ALAssetsGroup *group) {
NSLog(#"added album:%#", albumName);
AND
I have download the video from server using this code
-(IBAction)btnDownload:(UIView *)sender {
[DSBezelActivityView newActivityViewForView:self.view withLabel:#"DOWNLOADING..."];
NSString *albumName=#"album name";
NSURL *url = [NSURL URLWithString:#"http://www.ebookfrenzy.com/ios_book/movie/movie.mov"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDownloadDestinationPath:#"/Users/seemtech/Desktop/dd/vvv.m4v"];//work fine
[ASIHTTPRequest setDefaultTimeOutSeconds:3000];
[request startAsynchronous];
}
failureBlock:^(NSError *error) {
NSLog(#"error adding album");
}];
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
[[[[UIAlertView alloc] initWithTitle:#"Message"
message:#"Success!!!"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] autorelease] show]; [DSBezelActivityView removeViewAnimated:YES];
}
-(void)requestFailed:(ASIHTTPRequest *)request {
NSLog(#"error==%#",request.error); [DSBezelActivityView removeViewAnimated:YES];
}
I need to save the video in the album i created.
How can i do that?
You can save your video to your saved photos album using "ALAssetsLibrary" like this:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
NSURL *capturedVideoURL = [NSURL URLWithString:#"/Users/seemtech/Desktop/dd/vvv.m4v"];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:capturedVideoURL]) {
// request to save video in photo roll.
[library writeVideoAtPathToSavedPhotosAlbum:capturedVideoURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(#"error while saving video");
} else{
[self addAssetURL:assetURL toAlbum:#"iphonemaclover"];
}
}];
}
Add that asset to your album
- (void)addAssetURL:(NSURL*)assetURL toAlbum:(NSString*)albumName
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupAlbum usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if ([albumName compare: [group valueForProperty:ALAssetsGroupPropertyName]]==NSOrderedSame) {
//If album found
[library assetForURL:assetURL resultBlock:^(ALAsset *asset) {
//add asset to album
[group addAsset:asset];
} failureBlock:nil];
}
else {
//if album not found create an album
[library addAssetsGroupAlbumWithName:albumName resultBlock:^(ALAssetsGroup *group) {
[self addAssetURL:assetURL toAlbum:albumName];
} failureBlock:nil];
}
} failureBlock: nil];
}
The only problem with this code is it keeps a copy of video in saved photos album. So find a way to delete an asset from albums. This Link might help.

How to send a PDF file using UIActivityViewController

I'm trying to send a PDF using a UIActivityViewController. So far everything works fine using a fairly basic approach but the one issue I have is that when I select the send by mail option, the PDF's file name is Attachment-1 rather than Calculation.PDF which is the name that I give the file.
I don't mind too much the change in title, but the lack of a .pdf extension does seem to cause a problem when sending the file to people with Windows PC's and I'd like to fix that.
I've had a look at:
Control file name of UIImage send with UIActivityViewController
But can't see an equivalent method to:
[mailComposer addAttachmentData: UIImagePNGRepresentation(viewImage) mimeType:#"" fileName:#"myImage.png"];
that will work with a PDF file. Is this something that is not fixable without customization or is there a simple solution to this problem?
try this
NSData *pdfData = [NSData dataWithContentsOfFile:pdfFilePath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[#"Test", pdfData] applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
and also
NSString *str = [[NSBundle mainBundle] pathForResource:#"AppDistributionGuide" ofType:#"pdf"];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:#[#"Test", [NSURL fileURLWithPath:str]] applicationActivities:nil];
Swift
let url = NSURL.fileURLWithPath(fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
Swift 4.0
Here I attached code.I just added thread handling in to present "activityViewController" because of this viewcontroller present before load actual data.
let url = NSURLfileURL(withPath:fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
DispatchQueue.main.async {
self.present(activityViewController, animated: true, completion: nil)
}
Above listing about Swift is deprecated in Swift 3
let url = NSURL.fileURL(withPath: fileName)
let activityViewController = UIActivityViewController(activityItems: [url] , applicationActivities: nil)
present(activityViewController,
animated: true,
completion: nil)
For Objective-C tested code to share PDF
- (void)downloadPDFfile:(NSString *)fileName withFileURL:(NSString *)shareURL {
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#",[self generateName:fileName withFiletype:#"pdf"]]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:shareURL]];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Download Error:%#",error.description);
} else if (data && error == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
[data writeToFile:filePath atomically:YES];
[self shareFile:fileName withFilepath:filePath];
});
}
}];
[task resume];
});
}
-(void)shareFile:(NSString*)withfileName withFilepath:(NSString*)filePath {
NSMutableArray *items = [NSMutableArray array];
if (filePath) {
[items addObject:[NSURL fileURLWithPath:filePath]];
}
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
[activityViewController setValue:withfileName forKey:#"subject"];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = activityViewController.popoverPresentationController;
popPC.sourceView = self.view;
CGRect sourceRext = CGRectZero;
sourceRext.origin = CGPointMake(self.view.frame.size.width-30, 0);
popPC.sourceRect = sourceRext;
popPC.permittedArrowDirections = UIPopoverArrowDirectionDown;
}
[activityViewController setCompletionWithItemsHandler:
^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
}];
[self presentViewController:activityViewController animated:YES completion:nil];
}
-(NSString*)generateName:(NSString*)title withFiletype:(NSString*)type {
NSString *subject = [title stringByReplacingOccurrencesOfString:#" " withString:#"_"];
subject = [NSString stringWithFormat:#"%#.%#",subject,type];
return subject;
}
call function like below
[self downloadPDFfile:#"yourFileName" withFileURL:shareURL];
For Swift 3
You have to have a URL array with the path of the PDF you want to send.
let urlArray = [pdfPath1, pdfPath2]
Then create an UIActivityViewController:
let activityController = UIActivityViewController(activityItems: urlArray, applicationActivities: nil)
If you are using a UIBarButtonItem to make that action, you can implement this to prevent an error on iPad:
if let popover = activityController.popoverPresentationController {
popover.barButtonItem = self.barButtonItem
}
Finally you have to present the activityController:
self.present(activityController, animated: true, completion: nil)
The reply by Muruganandham K is simple and quite elegant. However, it doesn't work in iOS 9. In order to make it work, if you remove the #[#"Test" and just pass the pdfData, an attachment is made.
NSData *pdfData = [NSData dataWithContentsOfFile:pdfFilePath];
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:pdfData applicationActivities:nil];
[self presentViewController:activityViewController animated:YES completion:nil];
May be try this..
#define IS_IPAD UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
// Validate PDF using NSData
- (BOOL)isValidePDF:(NSData *)pdfData {
BOOL isPDF = false;
if (pdfData.length >= 1024 ) {
int startMetaCount = 4, endMetaCount = 5;
// check pdf data is the NSData with embedded %PDF & %%EOF
NSData *startPDFData = [NSData dataWithBytes:"%PDF" length:startMetaCount];
NSData *endPDFData = [NSData dataWithBytes:"%%EOF" length:endMetaCount];
// startPDFData, endPDFData data are the NSData with embedded in pdfData
NSRange startRange = [pdfData rangeOfData:startPDFData options:0 range:NSMakeRange(0, 1024)];
NSRange endRange = [pdfData rangeOfData:endPDFData options:0 range:NSMakeRange(0, pdfData.length)];
if (startRange.location != NSNotFound && startRange.length == startMetaCount && endRange.location != NSNotFound && endRange.length == endMetaCount ) {
// This assumes the checkstartPDFData doesn't have a specific range in file pdf data
isPDF = true;
} else {
isPDF = false;
}
}
return isPDF;
}
// Download PDF file in asynchronous way and validate pdf file formate.
- (void)downloadPDFfile:(NSString *) fileName withFileURL:(NSString *) url {
NSString *filePath = #"";
dispatch_async(dispatch_get_main_queue(), ^ {
NSString *documentDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
filePath = [documentDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/Attachments/%#",[self generateName:fileName withFiletype:#"pdf"]]];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
NSLog(#"Download Error:%#",error.description);
} else if (data && error == nil) {
bool checkPdfFormat = [self isValidePDF:data];
if (checkPdfFormat) {
//saving is done on main thread
dispatch_async(dispatch_get_main_queue(), ^{
[data writeToFile:filePath atomically:YES];
NSURL *url = [NSURL fileURLWithPath:filePath];
[self triggerShare:fileName withFilepath:filePath];
});
}
}
}];
});
}
// Trigger default share and print functionality using UIActivityViewController
-(void) triggerShare:(NSString*)fileName withFilepath:(NSString*)filePath {
* Set this available field on the activity controller */
NSMutableArray *items = [NSMutableArray array];
if (filePath) {
[items addObject:[NSURL fileURLWithPath:filePath]];
}
UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
[activityViewController setValue:fileName forKey:#"subject"]; // Set the mail subject.
if (IS_IPAD) {
activityViewController.modalPresentationStyle = UIModalPresentationPopover;
UIPopoverPresentationController *popPC = activityViewController.popoverPresentationController;
popPC.sourceView = self.view;
CGRect sourceRext = CGRectZero;
sourceRext.origin = CGPointMake(self.view.frame.size.width-30, 0 ); // I have share button in navigation bar. ;)
popPC.sourceRect = sourceRext;
popPC.permittedArrowDirections = UIPopoverArrowDirectionUp;
}
[activityViewController setCompletionWithItemsHandler:
^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
}];
[self presentViewController:activityViewController animated:YES completion:nil];
}
-(NSString*) generateName:(NSString*)title withFiletype:(NSString*)type {
NSString *subject = [title stringByReplacingOccurrencesOfString:#" " withString:#"_"];
subject = [NSString stringWithFormat:#"%#.%#",subject,type];
return subject;
}

how to return result after OpenWithCompletionHandler: is complete

Want to query a photo in the Coredata database
this is my code
this is the NSObjectSubclass category
//Photo+creak.h
#import "Photo+creat.h"
#implementation Photo (creat)
+(Photo *)creatPhotoByString:(NSString *)photoName inManagedObjectContext:(NSManagedObjectContext *)context{
Photo *picture = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", photoName];
NSArray *matches = [context executeFetchRequest:request error:nil];
if (!matches || [matches count]>1) {
//error
} else if ([matches count] == 0) {
picture = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
picture.name = photoName;
} else {
picture = [matches lastObject];
}
return picture;
}
+ (BOOL)isPhoto:(NSString *)photoName here:(NSManagedObjectContext *)context{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"name = %#", photoName];
NSArray *matches = [context executeFetchRequest:request error:nil];
switch ([matches count]) {
case 1:
return YES;
break;
default:
return NO;
break;
}
}
#end
code inside of view controller
//View Controller
- (IBAction)insertData:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
[defaultDocument saveToURL:defaultDocument.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:NULL];
}
[defaultDocument openWithCompletionHandler:^(BOOL success) {
[Photo creatPhotoByString:#"test" inManagedObjectContext:defaultDocument.managedObjectContext];
[defaultDocument saveToURL:defaultDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
[sender setTitle:#"Okay" forState:UIControlStateNormal];
[sender setEnabled:NO];
}
- (IBAction)queryFromDatabase:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
BOOL isItWorking = [checkPhoto isPhoto:#"test" inManagedDocument:defaultDocument];
if (isItWorking) {
[sender setTitle:#"Okay" forState:UIControlStateNormal];
} else {
[sender setTitle:#"NO" forState:UIControlStateNormal];
}
}
The NSObject Class that hook them up.
// checkPhoto.m
#import "checkPhoto.h"
#implementation checkPhoto
+ (BOOL)isPhoto:(NSString *)photoToCheck inManagedDocument:(UIManagedDocument *)document{
__block BOOL isPhotoHere = NO;
if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
isPhotoHere = [Photo isPhoto:photoToCheck here:document.managedObjectContext];
}];
}
return isPhotoHere;
}
#end
The coredata only have on Entity named "Photo", and it got only one attribute "name".
The problem is that the return always get execute before the block is complete and always return NO.
Test code here
Or should I do something else than openWithCompletionHandler when querying?
You need to rework your method to work asynchronously, like -openWithCompletionHandler:. It needs to take a block which is invoked when the answer is known and which receives the answer, true or false, as a parameter.
Then, the caller should pass in a block that does whatever is supposed to happen after the answer is known.
Or, alternatively, you should delay the whole chunk of logic which cares about the photo being in the database. It should be done after the open has completed.
You'd have to show more code for a more specific suggestion.
So, you could rework the isPhoto... method to something like:
+ (BOOL)checkIfPhoto:(NSString *)photoToCheck isInManagedDocument:(UIManagedDocument *)document handler:(void (^)(BOOL isHere))handler {
if (document.documentState == UIDocumentStateClosed) {
[document openWithCompletionHandler:^(BOOL success) {
handler([Photo isPhoto:photoToCheck here:document.managedObjectContext]);
}];
}
else
handler(NO);
}
Then you can rework this:
- (IBAction)queryFromDatabase:(UIButton *)sender {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"test"];
UIManagedDocument *defaultDocument = [[UIManagedDocument alloc] initWithFileURL:url];
[checkPhoto checkIfPhoto:#"test" isInManagedDocument:defaultDocument handler:^(BOOL isHere){
if (isHere) {
[sender setTitle:#"Okay" forState:UIControlStateNormal];
} else {
[sender setTitle:#"NO" forState:UIControlStateNormal];
}
}];
}
Try that
+(BOOL)isPhoto:(Photo *)photo inDataBase:(UIManagedDocument *)defaultDocument{
__block BOOL isPhotoThere = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[defaultDocument openWithCompletionHandler:^(BOOL success) {
[defaultDocument.managedObjectContext performBlock:^{
isPhotoThere = [Photo checkPhoto:photo];
dispatch_semaphore_signal(sema);
}];
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
return isPhotoThere;
}