I'm trying to save a simple string on a simple .plist file when the application is closing.
This is my code:
- (void)viewDidLoad {
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
self.kmOld.text = [array objectAtIndex:0];
[array release];
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:NULL];
[super viewDidLoad];
}
Then follow the applicationWillResignActive method:
- (void)applicationWillResignActive:(NSNotification *)notification {
NSString *text = [[NSString alloc]initWithFormat:#"%#",kmNew.text];
NSLog(#"%#",text);
if ([text intValue]>0) {
NSArray *array = [[NSArray alloc] initWithObjects:text, nil];
BOOL success = [array writeToFile:[self dataFilePath] atomically:YES];
NSLog(#"%d",success);
[array release];
}
[text release];
}
This work fine on the simulator, but it seems to be ignored by the device...
Where is the problem? Thanks...
Take a look at http://www.cocoanetics.com/2010/07/understanding-ios-4-backgrounding-and-delegate-messaging/ for a good overview of the UIApplication lifecycle. Depending on the iOS version your app is running on and the action causing your app to terminate/background you may not be listening for the correct notification and may need to observe UIApplicationWillTerminateNotification as well.
Related
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];
}
How can I check if the NSData dataWithContentsOfURLparsing in my secondary thread are finished? When every image is finished I want to open my view controller. Not before. Now I can open my view controller directly, and sometimes if I'm to quick my table view has no images, because they're not finished yet. Any ideas?
The following code happens in didFinishLaunchingWithOptions in AppDelegate. Im using the SBJSON framework for parsing.
(Im using the storyboard in this project so there's no code for opening the first view controller)
Code:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"json_template" ofType:#"json"];
NSString *contents = [NSString stringWithContentsOfFile: filePath encoding: NSUTF8StringEncoding error: nil];
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *json = [jsonParser objectWithString: contents];
tabs = [[NSMutableArray alloc] init];
jsonParser = nil;
//parsing json into model objects
for (NSString *tab in json)
{
Tab *tabObj = [[Tab alloc] init];
tabObj.title = tab;
NSDictionary *categoryDict = [[json valueForKey: tabObj.title] objectAtIndex: 0];
for (NSString *key in categoryDict)
{
Category *catObj = [[Category alloc] init];
catObj.name = key;
NSArray *items = [categoryDict objectForKey:key];
for (NSDictionary *dict in items)
{
Item *item = [[Item alloc] init];
item.title = [dict objectForKey: #"title"];
item.desc = [dict objectForKey: #"description"];
item.url = [dict objectForKey: #"url"];
if([dict objectForKey: #"image"] != [NSNull null])
{
dispatch_async( dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^(void)
{
NSURL *imgUrl = [NSURL URLWithString: [dict objectForKey: #"image"]];
NSData *imageData = [NSData dataWithContentsOfURL: imgUrl];
dispatch_async( dispatch_get_main_queue(), ^(void)
{
item.image = [UIImage imageWithData: imageData];
});
});
}
else
{
UIImage *image = [UIImage imageNamed: #"standard3.png"];
item.image = image;
}
[catObj.items addObject: item];
}
[tabObj.categories addObject: catObj];
}
[tabs addObject: tabObj];
}
//sort array
[tabs sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
Tab *r1 = (Tab*) obj1;
Tab *r2 = (Tab*) obj2;
return [r1.title caseInsensitiveCompare: r2.title];
}];
/***** END PARSING JSON *****/
[[UINavigationBar appearance] setTitleTextAttributes: #{
UITextAttributeTextShadowOffset: [NSValue valueWithUIOffset:UIOffsetMake(0.0f, 0.0f)],
UITextAttributeFont: [UIFont fontWithName:#"GreatLakesNF" size:20.0f]
}];
UIImage *navBackgroundImage = [UIImage imageNamed:#"navbar.png"];
[[UINavigationBar appearance] setBackgroundImage:navBackgroundImage forBarMetrics:UIBarMetricsDefault];
UIImage *backButtonImage = [[UIImage imageNamed:#"backBtn.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
UIImage *backButtonSelectedImage = [[UIImage imageNamed:#"backBtn_selected.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonSelectedImage forState: UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
return YES;
Also, if this way of parsing is bad, please tell me!
First of all, you shouldn't use such way of downloading any content from remote host.
There are lots of libraries like AFNetworking, ASIHTTPRequest
which work around CFNetwork or NSURLConnection to handle such things as redirects, error handling etc.
So you should definitely move to one of those (or implement your own based on NSURLConnection).
As a direct answer to your question:
You should use some kind of identifier for counting downloaded images (i.e. for-loop iteration counter) and pass it via +[UINotificationCenter defaultCenter] as a parameter of some custom notification.
Example (assuming that you are blocking current thread by +[NSData dataWithContentsOfURL:]):
for (int i = 0; i < 10; i++) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"someCustomNotificationClassName" object:nil userInfo:#{ #"counter" : #(i) }];
}
More expanded example of NSNotification-based approach:
- (id)init {
self = [super init];
if (self) {
// subscribing for notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleDataDownload:) name:#"someCustomNotificationClassName" object:nil];
}
return self;
}
- (void)dealloc {
// unsubscribing from notification on -dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - downloading delegation
- (void)handleDataDownload:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
int counter = [userInfo[#"counter"] intValue];
if (counter == 10) {
// do some work afterwards
// assuming that last item was downloaded
}
}
Also you can use callback technique to manage handling of download state:
void (^callback)(id result, int identifier) = ^(id result, int identifier) {
if (identifier == 10) {
// do some work afterwards
}
};
for (int i = 0; i < 10; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
// some downloading stuff which blocks thread
id data = nil;
callback(data, i);
});
}
I have created a UIDocument to wrap a custom object. The UIDocument is successfully saving to iCloud, and I am successfully loading each UIDocument when I run the app each time. However, when I attempt to run the app on a new device (iPhone 5) it will not load any UIDocument from iCloud, but I can check iCloud under Settings and see the files that are saved under my app. Does anyone have some steps to check to see why one device (iPhone 4) can load and the other (iPhone 5) cannot? I also cannot create a new UIDocument from the iPhone 5 device.
EDIT
- (void)loadObjects
{
if (!self.objects)
{
self.objects = [[NSMutableArray alloc] init];
}
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL)
{
self.query = [[NSMetadataQuery alloc] init];
[self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like 'Object_*'", NSMetadataItemFSNameKey];
[self.query setPredicate:predicate];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(queryDidFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[nc addObserver:self selector:#selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:self.query];
[self.query startQuery];
}
}
- (void)queryDidFinish:(NSNotification *)notification
{
[self processQueryResults:notification.object];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:notification.object];
self.query = nil;
}
- (void)queryDidUpdate:(NSNotification *)notification
{
[self processQueryResults:notification.object];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:notification.object];
self.query = nil;
}
- (void)processQueryResults:(NSMetadataQuery *)query
{
[query disableUpdates];
[query stopQuery];
[self.objects removeAllObjects];
[query.results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
NSURL *documentURL = [(NSMetadataItem *)obj valueForAttribute:NSMetadataItemURLKey];
ObjectDocument *document = [[ObjectDocument alloc] initWithFileURL:documentURL];
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
[self.objects addObject:document];
[self sortObjects];
[self.tableView reloadData];
}
}];
}];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)save:(Object *)object
{
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL)
{
NSURL *documentsURL = [baseURL URLByAppendingPathComponent:#"Documents"];
NSURL *documentURL = [documentsURL URLByAppendingPathComponent:[NSString stringWithFormat:#"Object_%f", [object.date timeIntervalSince1970]]];
TVBFlightDocument *document = [[ObjectDocument alloc] initWithFileURL:documentURL];
document.object = object;
[document saveToURL:documentURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
NSLog(#"Save succeeded.");
[_objects addObject:document];
[self sortObjects];
} else {
NSLog(#"Save failed.");
}
}];
}
}
The app on the iPhone 5 will not load the objects stored in iCloud, but iPhone 4 will load the objects from the iCloud documents.
This only happens when the Entry has been created on this run of the app. If the Entry is previously created, it fetches the image fine.
This code works fine without using background threads, so it leads me to believe it to be part of the problem. Here's the code I have:
NSMutableDictionary *thumbnails = [[NSMutableDictionary alloc] init];
dispatch_queue_t thumbnailSetupQueue = dispatch_queue_create("com.App.SetupTimelineThumbnails", NULL);
dispatch_async(cellSetupQueue, ^{
NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = [NSManagedObjectContext contextForCurrentThread].persistentStoreCoordinator;
[newMoc setPersistentStoreCoordinator:coordinator];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:newMoc];
Media *media = [localEntry.media anyObject];
UIImage *image = [media getThumbnail];
NSLog(#"image: %#", image);
[[NSNotificationCenter defaultCenter] removeObserver:self];
});
dispatch_release(cellSetupQueue);
Then
-(UIImage *)getThumbnail {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:
[NSString stringWithFormat:#"%#-%#.jpg",
self.mediaID,
THUMBNAIL_FILENAME]];
UIImage *thumbnail = [UIImage imageWithContentsOfFile:fullPath];
NSLog(#"correct size thumbnail: %#", correctSizeThumbnail);
return correctSizeThumbnail;
}
The NSLog in getThumbnailWithSave returns as a UIImage, the other NSLog returns as nil.
I had this problem explained to be a long time ago and I think this is how I fixed it.
Calling getThumbnail needs to be called back on the main thread.
So adding something such as:
UIImage *image;
dispatch_async(dispatch_get_main_queue(), ^{
image = [media getThumbnail];
});
or
UIImage *image = [media performSelectorOnMainThread:#selector(getThumbnail) withObject: nil, waitUntilDone:NO];
Again this is off the top of my head but I'm pretty sure this is how I went about it.
I have seen iCloud Document sample code for iOS and I used it to sync a uidocument to and from iCloud and now I am trying to sync iCloud with an nsdocument on a Mac OSX app which doesn't have UIDocument.
I tried to change UIDocument to NSDocument but all the methods of syncing with icloud are different. I haven't found any sample code or tutorials except for the documentation from apple which is very confusing and not well written.
For example, the method below, from UIDocument on iOS does not exist in NSDocument on OS X:
//doc is an instance of subclassed UIDocument
[doc openWithCompletionHandler:nil];
The Apple Documentation provides this code for OS X:
- (void)checkIfCloudAvaliable {
NSURL *ubiquityContainerURL = [[[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil]
URLByAppendingPathComponent:#"Documents"];
if (ubiquityContainerURL == nil) {
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
NSLocalizedString(#"iCloud does not appear to be configured.", #""),
NSLocalizedFailureReasonErrorKey, nil];
NSError *error = [NSError errorWithDomain:#"Application" code:404
userInfo:dict];
[self presentError:error modalForWindow:[self windowForSheet] delegate:nil
didPresentSelector:NULL contextInfo:NULL];
return;
}
dest = [ubiquityContainerURL URLByAppendingPathComponent:
[[self fileURL] lastPathComponent]];
}
- (void)moveToOrFromCloud {
dispatch_queue_t globalQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^(void) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *error = nil;
// Move the file.
BOOL success = [fileManager setUbiquitous:YES itemAtURL:[self fileURL]
destinationURL:dest error:&error];
dispatch_async(dispatch_get_main_queue(), ^(void) {
if (! success) {
[self presentError:error modalForWindow:[self windowForSheet]
delegate:nil didPresentSelector:NULL contextInfo:NULL];
}
});
});
[self setFileURL:dest];
[self setFileModificationDate:nil];
}
How can I sync between iOS and OS X (because NSDocument does not exists on iOS, and UIDocument does not exist on OS X)? Does anyone know where I can find a sample for Mac OSX (NSDocument syncing)?
I managed to get it working! Here's my code from the subclassed nsdocument file on OS X:
Header file:
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#interface subclassedNSDocument : NSDocument
#property (strong) NSData *myData;
#end
Implementation file:
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError
{
BOOL readSuccess = NO;
if (data)
{
readSuccess = YES;
[self setMyData:data];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"dataModified"
object:self];
return readSuccess;
}
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError
{
if (!myData && outError) {
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSFileWriteUnknownError userInfo:nil];
}
return myData;
}
and in the AppDelegate.m file:
#define kFILENAME #"mydocument.dox"
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSURL *ubiq = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSLog(#"iCloud access at %#", ubiq);
// TODO: Load document...
[self loadDocument];
}
else
{
NSLog(#"No iCloud access");
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dataReloaded:)
name:#"dataModified" object:nil];
}
- (void)update_iCloud
{
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:kFILENAME];
self.doc.myData = [NSKeyedArchiver archivedDataWithRootObject:[#"Your Data Array or any data", nil]];
[self.doc saveToURL:ubiquitousPackage ofType:#"dox" forSaveOperation:NSSaveOperation error:nil];
}
- (void)loadData:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
NSMetadataItem *item = [query resultAtIndex:0];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
NSLog(#"url = %#",url);
subclassedNSDocument *doc = [[subclassedNSDocument alloc] initWithContentsOfURL:url ofType:#"dox" error:nil];
[doc setFileURL:url];
self.doc = doc;
}
else {
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:kFILENAME];
dataUrls *doc = [[dataUrls alloc] init];
[self.doc setFileURL:ubiquitousPackage];
self.doc = doc;
[self.doc saveToURL:ubiquitousPackage ofType:#"dox" forSaveOperation:NSSaveOperation error:nil];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates];
[query stopQuery];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
_query = nil;
[self loadData:query];
}
- (void)loadDocument {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat: #"%K == %#", NSMetadataItemFSNameKey, kFILENAME];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
}
- (void)dataReloaded:(NSNotification *)notification
{
self.doc = notification.object;
NSArray *arrFromCloud = [NSKeyedUnarchiver unarchiveObjectWithData:self.doc.myData];
//Update you UI with new data
}
The only thing that I haven't got working is that if I change the data of the document on the iPad, the Mac app doesn't call the readFromData method for to update from iCloud, does anyone know what I am missing?
On iOS, the equivalent method, loadFromContents, is called automatically on every change of the UIDocument in iCloud. On OS X the readFromData is called once on load but never called again.
Hope my code can help, for me it is working one way from Mac to iPad.
I think NSMetadataQueryDidUpdateNotification is what you are looking for to detect document update.
This can be used just like NSMetadataQueryDidFinishGatheringNotification.
If you are dealing with files in addition to core data. Here is a better tutorial
http://samvermette.com/312