How to get data in iOS pasteboard to stay multiple access - ios

What I'm trying to do currently is basically I have 2 apps and based on if the other one is installed or not, behave in a certain way. I came upon Pasteboard for inter-app communication so I thought I should use those. Well here is the problem, both apps do something like this
UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:#"com.company.app.key" create:YES];
[pasteboard setPersistent:YES];
NSData *data = [pasteboard dataForPasteboardType:#"com.company.otherapp"];
if(data != nil)
{
// Do stuff
}
NSData *selfKey = [pasteboard dataForPasteboardType:#"com.company.thisapp"];
if(data == nil)
{
[pasteboard setValue:#"string" forPasteboardType:#"com.company.thisapp"];
}
So I installed one app and ran the other, and the first time it worked, data wasn't nil, and if I converted the NSData to NSString using the NSString initializer with NSData, I read "string".
The problem is that all I do is close the second app, run it again and suddenly data is nil. I thought at first that after I access it, the pasteboard just deletes it, so I added the following line in // Do stuff
[pasteboard setValue#"string" forPasteboardType:#"com.company.otherapp"];
Nope data is still nil if I run the app the second time. I need the string to stay in the Pasteboard forever since its all I have to tell me if my other app is installed or not, so does anyone have any ideas why data is returning nil only after 1 run?
I must note that all I am doing is pressing the home button and then running the app again,
so the situation is
-Run app1
-Run app2 (gets the data from Pasteboard)
-press home button
-Run app2 (data now nil)

When you load your pasteboard, you should set it to being persistent. After the first line in your code above, add:
pasteboard.persistent = YES;

So I figured out why app2 could not see it again. What setValue:forPasteboardType does is that it overwrites the first item in the Pasteboard. So every time I ran my apps, they would overwrite each other's key in the pasteboard.
The solution is using the class's multiple pasteboard item to search for the index of the pasteboardtype and getting data from that.

Related

Obtain the path of app Group from FileManager

I am trying to update my app for WatchKit and I save a NSKeyedArchiver file to the NSDocumentsDirectory normally.
With updating to app groups I need to store it in the app groups folder. The issue I am having is I cant figure out how to just get the path, and not have it referenced as a file I am looking for.
The way it is set up now is to find the file it gives the path as a NSString
/Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
but when I store to app groups, no matter which way I access the folder, it is returned
file:///Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
What is the best way to just obtain the path to the shared group, rather than have the app looking for the direct file?
So coffee deprived me had forgotten about the .path for filemanager.
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com"];
NSString *tmpPath = [NSString stringWithFormat:#"%#", fileManagerURL.path];
NSString *finalPath = [NSString stringWithFormat:#"%#",[string stringByAppendingString:#"/Favourites2"]];
I was running into the same problem. I was going through the whole process of building a string to my save location and now I'm switching over to app groups and using the
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupID];
Well, the problem is, now instead of a string to the location that starts with "/Users/yourname/Library..." you get "file:///Users/yourname/Library..."
Here's what I did. I created the NSURL. Then I called absoluteString on it.
NSURL groupPath = [[fileManager containerURLForSecurityApplicationGroupIdentifier:groupID] absoluteString];
I now have a string that I need to strip off the first 7 characters of, then my old code works just fine, except now instead of being in the Documents directory, it's in a shared app group that can be accessed by both my old code and my new watchkit extension.
Here's the code to strip off the first 7 characters (index 6 since you start with 0), you should be able to use either method...
NSString *newGroupPath = [groupPath substringFromIndex:6];
or
NSString *newGroupPath = [groupPath substringWithRange:NSMakeRange(6, [str length]-6)];
This just removes the "file://" from the absoluteString that was made from the NSURL and gives you back your older string path the starts "/Users/YourName/Library/Developer/yada yada yada"
Hope that helps you, I have spent 4 hours figuring it out.
It seems to work for me on the simulator, I haven't tried it on the Watch yet. But at least my app is now working the way it was before, just saving the data in a shared app group. (I have a singleton that manages all of my data throughout my app, and I want that same singleton to provide data to my watch app).

iOS8 extension : share images between container and extension

I'm making an iOS 8 extension. Here's what I'm trying to do: Users select images from the photo library in the container app, and these images will be shared with the extension and for the further use.
Right now I'm doing it in this way (If you don't want to read this part, please skip below to read the actual codes): Use App Group and NSUserDefaults to share datas. Convert UIImage into NSData and then save all the images in a NSArray, then save the array into a NSDictionary (I have many arrays and this is the way I organize them - so I have to save them into dictionary), finally save the dictionary into user default.
Here's the coding:
NSArray *imageArray = ...
//this array contains all the images.
//photoDataArray is a NSMutableArray;
photoDataArray = [[NSMutableArray alloc]init];
for (UIImage *images in imageArray) {
[photoDataArray addObject:UIImagePNGRepresentation(images)];
}
NSThread * creationThread = [[NSThread alloc] initWithTarget:self selector:#selector(handleData) object:nil];
[creationThread start];
-(void)handleData{
NSDictionary *dic = [[NSDictionary alloc]init];
[dic SetObject:photoDataArray forKey:#"testImageArray"];
NSUserDefaults * def = [[NSUserDefaults alloc] initWithSuiteName:#"group.myCompany.myApp"];
[def setObject:dic forKey:#"dataDic"];
//done with saving data
[self.navigationController popViewControllerAnimated:YES];
//Navigation
}
When I want to retrieve the images:
NSUserDefaults * def = [[NSUserDefaults alloc] initWithSuiteName:#"group.myCompany.myApp"];
NSDictionary *dic = [def ObjectForKey:#"dataDic"];
NSArray *dataArray = [dic objectForKey:#"testImageArray"];
NSMutableArray *convertedArray = [[NSMutableArray alloc] init];
for (NSData *imageData in dataArray) {
[convertedArray addObject:[UIImage imageWithData:imageData]];
}
convertedArray would be the array of images I want to get.
Apparently, there are a lot of problems if I do it this way.
For example, the two major issues:
Doing this takes a lot of resources including memory. It takes about half minute to actually finish the process.If I have a array with about 20 images, I'll get "didRecieveMemoryWarning" about 3 times (I'm using a iPad mini as a test device). Sometimes the datas are not saved correctly. After the viewController is popped out(which means it runs to the last line of my storing code), I get nil for the array I just saved into the UserDefault! I'm sure my coding all worked normal, and this issue is caused by low memory because if the array has less than 15 images, I can save and retrieve them perfectly.
It's hard to save new images into a previously saved array. When I want to do that, I have to retrieve the previous array and add new image datas into that array, and then save the new array into the UserDefault. As mentioned before, saving an array into the UserDefault takes a lot of memory.
So my questions are pretty straight foward and specific:
Are there any other ways to transfer images from one target to another? In other words: How can I transfer images from the container app to the extension?
If not, are there any ways to solve the issue in my codes? Is this a proper way to do it?
Those are all I want to ask, but if you could answer following questions for me also, it will be really nice:
Why would I get more than one "didRecieveMemoryWarning" in one saving process? When the system received memory warning, will it stop the action immediately?
(Just to make sure) Is that safe to use UIImagePNGRepresentation for all the images including PNG and JPG?
Thank you.
From Apple's Documentation on App Extension Programming
Sharing Data with Your Containing App
The security domains for an app extension and its containing app are distinct, even though the extension bundle is nested within the containing app’s bundle. By default, your extension and its containing app have no direct access to each other’s containers.
You can, however, enable data sharing. For example, you might want to allow your app extension and its containing app to share a single large set of data, such as prerendered assets.
.....
When you set up a shared container, the containing app—and each contained app extension that you allow to participate in data sharing—have read and write access to the shared container. To avoid data corruption, you must synchronize data accesses. Use Core Data, SQLite, or Posix locks to help coordinate data access in a shared container.

How to implement Open In apps for plain text

I would like to provide the ability for users to tap the Action button and up pops the usual share sheet, which should include other apps to the right of the Messages, Facebook, etc icons - applications that can work with .txt files, or just an NSString.
I am currently displaying a Share sheet via UIActivityViewController, which is working great but it does not include other apps in the list. From reading other SO questions I concluded it's only possible to get those other apps to appear if you use UIDocumentInteractionController instead. I looked into creating a .txt file in a temp directory to share that file (instead of just sharing an NSString), but only Mail (no Copy) shows up when I tap the Share button. [Do note that if I run it on a real device not the simulator more apps other than Mail will appear and AirDrop too.] When I tap Mail, the app crashes: Unable to get data for URL: The operation couldn’t be completed. (Cocoa error 260.) Something is wrong with the way I'm creating/retrieving the .txt file.
My questions are:
Why is my code resulting in a crash when attempting to share the .txt file?
How can I get the Copy option to appear in the same Share sheet as the one that includes other apps?
To summarize: I need a share sheet that includes: Copy, AirDrop, Messages, Mail, Facebook, Twitter, Pages, Dropbox, etc for a simple string of text. Thanks!
The following lines of code lie inside my IBAction share button tap function:
UIActivityViewController approach:
UIActivityViewController *activityView = [[UIActivityViewController alloc] initWithActivityItems:#[self.myUITextField.text] applicationActivities:nil];
[self presentViewController:activityView animated:YES completion:nil];
Result:
UIDocumentInteractionController approach:
NSString *fileName = [NSString stringWithFormat:#"%#mytextfile.txt", NSTemporaryDirectory()];
[self.myUITextField.text writeToFile:fileName
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:nil];
NSURL *textFileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:#"mytextfile.txt"]];
UIDocumentInteractionController *documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:textFileURL];
[documentInteractionController presentOptionsMenuFromBarButtonItem:sender animated:YES];
Result (will show more apps and AirDrop if I run on a real device):
Example of what I want to obtain - minus the 3 extra options at the bottom:
If I cannot obtain the above screenshot with a string (instead of a photo) for some reason, I am willing to implement it how Dropbox has done it. They added an Open In button at the bottom that presents a different sheet that only shows additional apps. Note that I would still need a Copy option on the original sheet.
Question 1: Why is my code resulting in a crash
Cocoa error 260 is an NSFileReadNoSuchFileError according to the Foundation Constants Reference document. Looking at your code, the only way how I can see that file creation might fail is if self.myUITextField is nil.
I suggest that you check this first. If the property is not nil, then check whether writeToFile:atomically:encoding:error: returns an error.
Question 2: How can I get the Copy option to appear
First, assign a delegate to the controller:
documentInteractionController.delegate = self;
Then implement the following two delegate methods:
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller canPerformAction:(SEL)action
{
if (#selector(copy:) == action)
return YES;
else
return NO;
}
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller performAction:(SEL)action
{
if (#selector(copy:) != action)
return NO;
// Perform the copy: action
return YES;
}
Both methods are marked deprecated since iOS 6, but they still seem to work in iOS 7. Unfortunately, I have no idea how to implement the copy: action without those two methods - and neither does Apple, or so it seems to me, since they do not offer a replacement, and the official Document Interaction Programming Topics for iOS document still happily refers to the methods without indication that they are deprecated.
Anyway, here's a simple but complete implementation of the second delegate method:
- (BOOL) documentInteractionController:(UIDocumentInteractionController*)controller performAction:(SEL)action
{
if (#selector(copy:) != action)
return NO;
NSStringEncoding usedEncoding;
NSError* error;
NSString* fileContent = [NSString stringWithContentsOfURL:controller.URL
usedEncoding:&usedEncoding
error:&error];
UIPasteboard* pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setString:fileContent];
return YES;
}

Move data/images between two iOS apps using custom URL handler

After googling around and searching SO for a while, I stil couldn't find an answer -
I've wondered, How could I transfer data between two of my apps using custom URL handlers? Specifically images or an NSData object for that matter.
I know about being able to open specific parts of my app using custom handlers such as myapp1://start , myapp2://start , but I'm not sure how to go on transferring large amounts of data (~80k) through these handlers.
Would love to hear any creative solutions :)
p.s. The solution should be iOS >= 4.3 Compatible
Use the custom URL handlers in combination with UIPasteboard. Save something from your first app (say, an image) to the general pasteboard, like so:
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[[UIPasteboard generalPasteboard] setImage:myImage];
Then use the custom URL schemes to switch apps.
Then retrieve your image from within the new app when you need it:
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
UIImage *tempImg = pasteboard.image;
Battle-tested. ; )
One Solution could be:
Implement a Webserver
Open up your second app via the custom url scheme with the IP-adress and the port of your custom web server included in the url
Add the route or parameters to your image also to your URL
Download and enjoy your photo :-)
Another Solution:
Start a Bonjour service
in a network the second app can find this service
do some magic to pass the data in between the apps
EDIT:
BETTER OTHER SOLUTION:
Just found another, but much more efficient way to do exchanges of larger data sets:
It is called UIPasteboard
Best reference for that:
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIPasteboard_Class/Reference.html
And another resource:
http://kmithi.blogspot.in/2012/03/sharing-data-among-ios-applications.html
That should do it.
For a webserver: There are tons of implementations found using Google
[http://www.codeproject.com/Articles/43447/How-to-Use-UIPasteBoard-to-Implement-Custom-Copy-a][1]
As you know many of the controls in UIKit now come pre-loaded with the ability to copy and paste text. You can also use this new ability in your own apps to copy and paste other things including: images, SQLite databases, text or any file. This is a great way to share data between your apps if you want to provide users with a suite of apps with integrated functionality.
CopyFrom Source Code
-(IBAction)copyImageToPasteBoard{
UIPasteboard *appPasteBoard = [UIPasteboard pasteboardWithName:#"CopyFrom"
create:YES];
appPasteBoard.persistent = YES;
NSData *data = UIImagePNGRepresentation([UIImage imageNamed:#"Old-Time-Photo.jpg"]);
[appPasteBoard setData:data forPasteboardType:#"com.appshop.copyfrom.imagedata"];
}
-(IBAction)copyStringToPasteBoard{
UIPasteboard *appPasteBoard = [UIPasteboard pasteboardWithName:#"CopyFrom"
create:YES];
appPasteBoard.persistent = YES;
[appPasteBoard setString:textView.text];
}
PasteTo Source Code
-(IBAction)pasteImageToPasteBoard{
UIPasteboard *appPasteBoard = [UIPasteboard pasteboardWithName:#"CopyFrom"
create:YES];
NSData *data = [appPasteBoard dataForPasteboardType:#"com.appshop.copyfrom.imagedata"];
imageView.image = [UIImage imageWithData:data];
}
-(IBAction)pasteStringToPasteBoard{
UIPasteboard *appPasteBoard = [UIPasteboard pasteboardWithName:#"CopyFrom"
create:YES];
textView.text = [appPasteBoard string];
}
Using UIPasteBoard in iPhone programming is amazingly simple and opens up some possibilities that we did not have a year ago. To use UIPasteBoard you simply create a instance using pasteboardWithName, put stuff into the paste board and then set the persistant property equal to YES. Then any app can get a reference to your paste board and use the data contained within. You can use it for simple strings and even data that you can put into NSData like SQLite databases.
Well, as far as I can tell ~80k is too much for a custom URL handler. But you could try to Base64-encode your data and append it to the custom URL. But I doubt that it will work with lots of data.
You could also have a look on the UIDocumentInteractionController, which is intended to open files with other applications that support them. This is what the Mail application does when you open an attachment with another app.
https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIDocumentInteractionController_class/Reference/Reference.html#//apple_ref/doc/uid/TP40009304
using custom URL handler
it is specified, so it will not meet your specification.
But if you don't care about that, I would write to internal memory (file system)the 80k image, even if 80Mb and pass the "url" for the other app.
Big ego, and 0 effort in research:
copy to images folder
IPC communication via url
and my solution doesn't work... I am not giving fish, just teaching hot to get a fish.

Using UIPasteboard to share data with iOS Numbers App

I'm wanting to share data in my app with iOS Numbers via the Pasteboard. I've written the following code: -
NSData *data1 = [#"Col1,1\tCol1,2\n\nCol2,1\tCol2,2\n" dataUsingEncoding:NSASCIIStringEncoding];
[[UIPasteboard generalPasteboard] setData:data1 forPasteboardType:#"com.apple.iWork.TSPNativeData"];
However, when you use NSLog to check what's stored in the pasteboard - it's empty?
Initially I've tried using the basic code: -
[UIPasteboard generalPasteboard].string = #"Col1,1\tCol1,2\n\nCol2,1\tCol2,2\n";
However when that pastes into Numbers all the text is stored within one cell, rather than being outputted into the separate cells.
Has any one had any experiencing in trying to share data from their apps and Numbers?
Thanks,
Martyn

Resources