I am tying and succeeded to save a photo using CloudKit, at least I think I did, because I don't know a user friendly way to check if I am right. I am asking these in the scenario that the users makes a few photos and the photos are saved using cloudKit and then he deletes the app but theoretically he should still be able to access the data via some sort of interface provided by apple, am I right?
And here is the core that I am using:
- (void) saveToiCloudImageNamed: (NSString *)imageName andTimeCreated:(NSString *)timeCreated{
CKContainer *container = [CKContainer defaultContainer];
CKDatabase *privateDatabase = [container publicCloudDatabase];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths firstObject];
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:imageName];
NSURL *fileUrl = [[NSURL alloc] initFileURLWithPath:fullPath];
CKAsset *photoAsset = [[CKAsset alloc] initWithFileURL:fileUrl];
CKRecord *record = [[CKRecord alloc] initWithRecordType:#"Photo"];
record[#"asset"] = photoAsset;
record[#"name"] = timeCreated;
[privateDatabase saveRecord:record
completionHandler:^(CKRecord *record, NSError *error) {
if (error==nil) {
NSLog(#"The save was successful");
//Do something
}else{
NSLog(#"Error saving with localizedDescription: %#", error.localizedDescription);
NSLog(#"CKErrorCode = %lu", (long)[error code]);
if ([error code]==CKErrorNetworkFailure) {
double retryAfterValue = [[error.userInfo valueForKey:CKErrorRetryAfterKey] doubleValue];
NSLog(#"Error code network unavailable retrying after %f", retryAfterValue);
// NSTimer *timer = [NSTimer timerWithTimeInterval:retryAfterValue
// target:self
// selector:#selector(testOutCloudKit)
// userInfo:nil
// repeats:NO];
// [timer fire];
}
}
}];
}
At the moment there is no interface for accessing CloudKit data other than what you create yourself. Maybe you want to use iCloud documents instead.
When you save to the public database (as you are doing in the sample above) then you could access the data using the CloudKit dashboard. But that is only accessible by member in your Apple developer account.
Related
My app has turned on Data Protection and I created a file with NSFileProtectionComplete
+ (void)createLogFile {
NSString *deviceModel = [Utils getDeviceModel];
NSString *appVersion = [Utils getAppVersion];
NSData *initData = [[NSString stringWithFormat:#"%#-%#\n================================\n\n\n", deviceModel, appVersion] dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:[self logFilePath]
contents:initData
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
}
and when I lock my device applicationProtectedDataWillBecomeUnavailable: will be called.
- (void)applicationProtectedDataWillBecomeUnavailable:(UIApplication *)application {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSData *key = [MyKeychain getKey];
NSString *log = [NSString stringWithFormat:#"The key is:\n %#", key];
[MyFileLogger logInfo:log];
});
}
Then I can find the result in the file, which means I was able to write that file when my device is locked.
Shouldn't Data Protection prevents from accessing files when device is locked? What's wrong?
--updated-- (add method logInfo:)
+ (void)logInfo:(NSString *)str {
NSString *info = [self wrapWithTimestamp: str];
NSString *logFilePath = [Utils logFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) {
[Utils createLogFile];
}
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:[info dataUsingEncoding:NSUTF8StringEncoding]];
[handle closeFile];
}
According to the answer to this question, after the applicationProtectedDataWillBecomeUnavailable method is called there is a 10 second "grace period" before data protection activates.
If you increase your time delay from 5 to 11 seconds you should see that your data is not written to your log file.
I was able to observe this with sample code and an 11 second delay.
I am sending database file successfully between iOS devices with the following code:
-(void) doSendDatabase {
UIView *viewTemp = [[UIView alloc] init];
viewTemp.frame = CGRectMake(0.0f, 0.0f, 300, 300);
NSString *currentDatabaseName;
// This is the full path and file name with ext
currentDatabaseName = [self.databases objectAtIndex:[[mainTableView indexPathForSelectedRow] row]];
NSURL *url = [[NSURL alloc] initFileURLWithPath:currentDatabaseName];
UIActivityViewController * airDrop = [[UIActivityViewController alloc]
initWithActivityItems:#[url]
applicationActivities:nil];
airDrop.popoverPresentationController.sourceView = self.view;
[self presentViewController:airDrop
animated:YES
completion:nil];
[url release];
[airDrop release];
[viewTemp release];}
This code works and the database successfully gets sent from the sending iOS device to the receiving device. However, the databases are stored in the Documents/Inbox folder (by design I suppose). I simply want to move the received database files from the Inbox folder up one level into the Documents folder. From what I'm reading I need to handle this in openURL in the App Delegate - but am not sure how to go about this. Any help would be greatly appreciated.
Thank you.
Ok - here's what I did to resolve the problem.
(1) I created a handleInboxItems method in the App Delegate.
-(bool) handleInboxItems {
bool success = YES;
// Get the DBAccess object
DBAccess *dbAccess = [[DBAccess alloc] init];
// Get the Func object
Func *funcObject = [[Func alloc] init];
NSMutableArray *docDatabases;
// get a list of all database files in the Documents/Inbox folder ans store them in the inboxDatabases array
NSMutableArray *inboxDatabases = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *inboxDirectory = [documentsDirectory stringByAppendingPathComponent:#"Inbox"];
NSDirectoryEnumerator *directoryEnumerator = [[NSFileManager defaultManager] enumeratorAtPath:inboxDirectory];
for (NSString *inboxFileAndPath in directoryEnumerator)
{
//check to see if any of the files in the inbox folder end in the database extension - if so then save it in the inboxDatabases array
if ([[inboxFileAndPath pathExtension] isEqualToString:#“dbext”])
{
[inboxDatabases addObject:[inboxDirectory stringByAppendingPathComponent:inboxFileAndPath]];
}
}
// now go through the inboxDatabases array and copy them from the Documents/Inbox folder to the Documents folder
// loop through all inbox database and see if any of the database names already exist in Documents - if so then we need to tack on a sequential number
for (NSString *inboxDatabaseFileAndPath in inboxDatabases)
{
NSString *inboxDatabaseName = [[inboxDatabaseFileAndPath lastPathComponent] stringByDeletingPathExtension];
// Get the databases array from the DBAccess class (from the Documents folder) - need to get each time since we are moving files in there
docDatabases = [dbAccess getAllDatabases];
// does the inbox database already exist in the documents folder?
NSUInteger arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)]];
int i = 0;
while (arrayIndex != NSNotFound)
{
++i;
NSString *tempDatabaseName = [NSString stringWithFormat:[inboxDatabaseName stringByAppendingString:#" %d"],i];
// see if the database (with sequential number) already exists
arrayIndex = [docDatabases indexOfObject:[funcObject databaseNameToFullPathName:allTrim(tempDatabaseName)]];
if (arrayIndex == NSNotFound)
{
// it does not exist, we can use this name
inboxDatabaseName = tempDatabaseName;
}
}
// give it full path and extension
NSString *docDatabaseFileAndPathToWrite = [funcObject databaseNameToFullPathName:allTrim(inboxDatabaseName)];
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
success = [fileManager copyItemAtPath:inboxDatabaseFileAndPath toPath:docDatabaseFileAndPathToWrite error:&error];
if (success)
{
// delete the inbox database file
success = [fileManager removeItemAtPath:inboxDatabaseFileAndPath error:&error];
if (!success)
{
NSAssert1(0,#"Failed to delete inbox database:'%#'.",[error localizedDescription]);
}
}
else
{
NSAssert1(0,#"Failed to copy inbox database to documents folder:'%#'.",[error localizedDescription]);
}
}
[dbAccess release];
[funcObject release];
[inboxDatabases release];
return success;}
(2) Added a call to this new method in the didFinishLaunchingWithOptions in the App Delegate just in case there is anything stuck in the inbox upon startup.
(3) I added the openURL method to the App Delegate in order to call handleInboxItems. After done, I send a notification so that I can refresh my database list.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
bool success = [self handleInboxItems];
if (success)
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIF_DATABASE_AIRDROPPED object:self];
return success;}
That's it - works as I need it to.
I'm quite the beginner to iOS Programming, and I googled how to get the UI Activity View implemented, but I'm getting errors that I do not quite understand. Anyone that can help me figure out what the errors mean in more specific detail and how to fix them, it would be greatly appreciated.
#pragma mark - SHARING OPTIONS (using a DocumentInteractionController) =============
/* =================
NOTE: The following methods work only on real device, not iOS Simulator, and you should have apps like Instagram, iPhoto, etc. already installed into your device!
================= */
-(void)shareImageToAllAppsAvailable {
NSLog(#"This code works only on device. Please test it on iPhone!");
// makes an NSURL file to the processed Image that needs to be saved
NSURL *fileURL;
docIntController.delegate = self;
//Saves the Image to default device directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"My Selfie.jpg"];
UIImage *image = combinedImage;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
//Load the Image Path
NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:#"My Selfie.jpg"];
// this blank line here creates error 'use of undeclared identifier 'showActivityViewController''
// Create the URL path to the Image to be saved
fileURL = [[NSURL alloc] initFileURLWithPath:getImagePath];
// Open the Document Interaction controller for Sharing options
-(void)showActivityViewController
{
//-- set up the data objects
NSString *textObject = _aTextView.text;
UIImage *image = [UIImage imageNamed:#"My Selfie.jpg"];
NSArray *activityItems = [NSArray arrayWithObjects:textObject, url, image, nil];
//-- initialising the activity view controller
UIActivityViewController *avc = [[UIActivityViewController alloc]
initWithActivityItems:activityItems
applicationActivities:nil];
//-- define the activity view completion handler
avc.completionHandler = ^(NSString *activityType, BOOL completed){
NSLog(#"Activity Type selected: %#", activityType);
if (completed) {
NSLog(#"Selected activity was performed.");
} else {
if (activityType == NULL) {
NSLog(#"User dismissed the view controller without making a selection.");
} else {
NSLog(#"Activity was not performed.");
}
}
};
}
You're missing a closing } before your -(void)showActivityViewController declaration.
It looks like you're trying to call that method by defining it within another method, which is not valid Objective-C. Use the self construct to reference methods defined in the same class.
#pragma mark - SHARING OPTIONS (using a DocumentInteractionController) =============
/* =================
NOTE: The following methods work only on real device, not iOS Simulator, and you should have apps like Instagram, iPhoto, etc. already installed into your device!
================= */
-(void)shareImageToAllAppsAvailable {
NSLog(#"This code works only on device. Please test it on iPhone!");
// makes an NSURL file to the processed Image that needs to be saved
NSURL *fileURL;
docIntController.delegate = self;
//Saves the Image to default device directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *savedImagePath = [documentsDirectory stringByAppendingPathComponent:#"My Selfie.jpg"];
UIImage *image = combinedImage;
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:savedImagePath atomically:NO];
//Load the Image Path
NSString *getImagePath = [documentsDirectory stringByAppendingPathComponent:#"My Selfie.jpg"];
// this blank line here creates error 'use of undeclared identifier 'showActivityViewController''
// Create the URL path to the Image to be saved
fileURL = [[NSURL alloc] initFileURLWithPath:getImagePath];
// Open the Document Interaction controller for Sharing options
[self showActivityViewController]; //added
} //added
-(void)showActivityViewController
{
//-- set up the data objects
NSString *textObject = _aTextView.text;
UIImage *image = [UIImage imageNamed:#"My Selfie.jpg"];
NSArray *activityItems = [NSArray arrayWithObjects:textObject, url, image, nil];
//-- initialising the activity view controller
UIActivityViewController *avc = [[UIActivityViewController alloc]
initWithActivityItems:activityItems
applicationActivities:nil];
//-- define the activity view completion handler
avc.completionHandler = ^(NSString *activityType, BOOL completed){
NSLog(#"Activity Type selected: %#", activityType);
if (completed) {
NSLog(#"Selected activity was performed.");
} else {
if (activityType == NULL) {
NSLog(#"User dismissed the view controller without making a selection.");
} else {
NSLog(#"Activity was not performed.");
}
}
};
}
What is the right way to move a core data model that allows external storage into a UIManagedDocument? I have a core data store that I am trying to move into a UIManagedDocument. I have users with lots of data. Some of it is 2 - 3 minute audio clips. I am subclassing UIManaged document and overriding the configurePersistentStoreCoordinatorForURL. Then copying the files over into the UIManagedDocument bundle. It all seems to work great accept for the Audio files that are stored externally. In my Core Data Model, my audio files are set up to allow external storage. These files are no longer connected after the move and when I try to play them int the app after the move, I get an audio session error. Thanks for any help you can offer on the topic. Here is my code that I am using to override the UIMD…
- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)storeURL
ofType:(NSString *)fileType
modelConfiguration:(NSString *)configuration
storeOptions:(NSDictionary *)storeOptions
error:(NSError *__autoreleasing *)error{
[self printFileDir];
// If legacy store exists, create a UIManaged Document and store it there
NSURL *docsDir = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *legacyStoreURL = [docsDir URLByAppendingPathComponent:#"RRLevelBook.sqlite"];
NSFileManager* fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:legacyStoreURL.path])
{
NSLog(#"Old db exists");
//swap files
NSURL *storeURLshm = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *storeURLwal = [NSURL URLWithString:[[storeURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *supportFiles = [[storeURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".persistenStore_SUPPORT"];
NSURL *legacyStoreURLshm = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-shm"]];
NSURL *legacyStoreURLwal = [NSURL URLWithString:[[legacyStoreURL absoluteString] stringByAppendingString:#"-wal"]];
NSURL *legacySupportFiles = [[legacyStoreURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:#".RRLevelBook_SUPPORT"];
NSError* thisError = nil;
//swap the sqlite file
[fileManager replaceItemAtURL:storeURL
withItemAtURL:legacyStoreURL
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -shm file
[fileManager replaceItemAtURL:storeURLshm
withItemAtURL:legacyStoreURLshm
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//swap the -wal file
[fileManager replaceItemAtURL:storeURLwal
withItemAtURL:legacyStoreURLwal
backupItemName:nil
options:NSFileManagerItemReplacementUsingNewMetadataOnly
resultingItemURL:nil
error:&thisError];
//Move in the Support files
[fileManager moveItemAtURL:legacySupportFiles toURL:supportFiles error:nil];
//delete old files that have been swapped
[fileManager removeItemAtURL:legacyStoreURL error:nil];
[fileManager removeItemAtURL:legacyStoreURLwal error:nil];
[fileManager removeItemAtURL:legacyStoreURLshm error:nil];
[fileManager removeItemAtURL:legacySupportFiles error:nil];
NSLog(#"%#",[thisError localizedDescription]);
}
[self printFileDir];
return [super configurePersistentStoreCoordinatorForURL:storeURL ofType:fileType modelConfiguration:configuration storeOptions:storeOptions error:error];
}
Well, here is what I ended up doing - for better or worse:
Open the new UIManagedDocument.
Open up the legacy Core Data Model.
Copy each audio file (NSData) from Legacy CoreData Context to the UIManagedDocument Context.
Reconnect all relationships based on the Legacy CoreData Context.
NSManagedObjectContext *legacyMOC = [[NSManagedObjectContext alloc]init];
[legacyMOC setPersistentStoreCoordinator:psc];
//fetch all audio recordings from legacyStore
NSArray *legacyRecordingArray = [self fetchAudioRecordingsfrom:legacyMOC];
//fetch all audio recordings form UIMDStore
NSArray *uimdRecordingArray = [self fetchAudioRecordingsfrom:self.managedObjectContext];
//for each audio recording, copy the audio object from legacy and save it to UIMDStore
for (int i = 0; i < legacyRecordingArray.count; i++) {
//save audio to core data
RunningRecord *legacyRR = (RunningRecord *)legacyRecordingArray[i];
RunningRecord *uimdRR = (RunningRecord *)uimdRecordingArray[i];
uimdRR.audioData = [NSData dataWithData:legacyRR.audio.file];
uimdRR.audio.file = nil;
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
I have my app set up to send a custom level in a form of a array to another person during a p2p connection. The receiving device saves the array to file for later use. I set up gamekit in my application, it will successfully search and connect to another device without any problems. Though a problem arises when I send data to a device, the receiving device will receive the data (and save the custom level like it should) but it will immediately crash afterwords.
Here are my methods that I use to send and receive data.
-(void) sendDataToPeers:(NSData *) data
{
if (currentSession)
{
//send the data
[self.currentSession sendDataToAllPeers:data withDataMode:GKSendDataReliable error:nil];
//Alerting the user that the custom level has been sent.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Sent!" message:#"Your custom level has been sent." delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
[alert release];
}
}
-(void) btnSend
{
//Data that will be sent
NSMutableData *theData = [NSMutableData data];
//Archiver
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];
//Desired level to send
int theLevel =[[CTManager sharedInstance]getLevel];
//Path to the custom levels
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory=[paths objectAtIndex:0];
NSString *customLevelsSen = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"customLevels"]];
//Custom levels array
NSArray *theLevels = [[NSArray alloc] initWithContentsOfFile: customLevelsSen];
//Gets the desired level array from array of custom levels
NSArray *myArray = [[NSArray alloc]initWithArray:[theLevels objectAtIndex:theLevel-51]];
//prepare data
[archiver encodeObject:myArray forKey:#"level"];
[archiver finishEncoding];
//send the data
[self sendDataToPeers:theData];
//cleanup
[archiver release];
[theLevels release];
[myArray release];
}
-(void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context
{
//Archiver
NSKeyedUnarchiver *archiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
//Gets the custom level in form of an array from data.
NSArray *level = [archiver decodeObjectForKey:#"level"];
[archiver finishDecoding];
[archiver release];
//Path to the array of custom levels
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory=[paths objectAtIndex:0];
NSString *customLevelsRec = [documentsDirectory stringByAppendingPathComponent: [NSString stringWithFormat:#"customLevels"]];
//Gets the array of custom levels
NSMutableArray *customLevelArray = [[NSMutableArray alloc] initWithContentsOfFile:customLevelsRec];
//Adds a new array to the array of custom levels
[customLevelArray addObject:level];
//Saves the array.
[customLevelArray writeToFile:customLevelsRec atomically:YES];
//cleanup
[customLevelArray release];
//Message saying a custom level has been recieved
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Received!" message:#"A custom level has been saved." delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
[alert release];
}
Testing this has been a pain since I don't have two devices of my own currently, so I send a beta build to my friend who inturns tests them (he has ipod and iphone). Any help with this is appreciated...
If I can't find the problem I will most likely send the entire xcode project to him and via screen share work with the project on his computer to efficiently build and test the application. And I will be able to use debug mode.
I don't know if you ever found an answer to this question or not, I hope you did. But if you didn't I highly recommend that you try the new features of the new SDK. Instead of going through the whole encode/decode process, they have made it simple by having you do the following (in your send methodology):
data = [NSKeyedArchiver archivedDataWithRootObject:anObject];
where anObject can be pretty much any object, array, dictionary, whatever...
In your receive methodology:
NSObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:data];
where object can also be pretty much any object.
As far as the crash you are experiencing, have you verified on which line the crash occurs? Are you sure it happens in the code you posted? Or is it happening somewhere else?
I don't see anything wrong in your receiveData method.
Have you checked that the folder where you are trying to save the data (customLevels) exists?
I have succeeded to connect via an application using GameKit a device and the iPhone simulator. It's really handy to debug.
I haven't check if it was by bluetooth or wifi.
NSData* data = [#"TEXT" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
[self.session sendData:data toPeers:peerID withDataMode:GKSendDataReliable error:&error];
(void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context {
NSString* message = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];//#"TEXT"
NSString* nameOfTheTransmitter = [session displayNameForPeer:peer];// name who sent
}