Sharing file/data between two iOS apps that I own - ios

I have apps A and B and both are developed by me. I want to send file from A to B but it has to happen without using any sharing services like dropbox or anything that involves internet connection.
OpenURL/CustomURLSchemes doesn't seem to work since you are limited with the data you can pass and the file size can be big. And because of the sandboxing I can't write the file and pass the url...
UIActivityViewController and UIDocumentInteractionController are options only if there is a way to display just one app which I fail to achieve so far..
Is this possible?
I own both apps and the file extension is custom.

You actually don't need extensions to share data between your own apps. You can use app groups for this.
In both MyApp1 and MyApp2 goto the target, then capabilities, then click on the app groups capability. Let Xcode help you get app group entitlement setup in your apple developer account.
Now you can use that app group ID to share data between you apps. For instance:
In MyApp1 put this in your appDelegate:
NSUserDefaults *myDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.mycompany.myappgroup"];
[myDefaults setObject:#"foo" forKey:#"bar"];
And in MyApp2 appDelegate put this:
NSUserDefaults *myDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.mycompany.myappgroup"];
NSLog(#"Show me something: %#",[myDefaults objectForKey:#"bar"]);
Make sure that the string you used for the suite name is the exact same as what is under the app group capabilities section in Xcode and also the string in your entitlements plist that was automatically added to your project.
You can also share files using the same idea:
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:
#"group.mycompany.myappgroup"];
And for those of you who want to see it in Swift:
var userDefaults = NSUserDefaults(suiteName: "group.mycompany.myappgroup")
var fileManager = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier("group.mycompany.myappgroup")

Related

SiriKit SendPaymentIntent

We are trying to implement SiriKit SendPaymentIntent in our app. We need a local stored data from our main app.
We try to get this string from keychain and user defaults, nothing worked. My question is if we want to get a located stored string, how can we access it with SiriKit.
You should be able to use suiteName for sharing UserDefaults and keychain-access-groups for sharing your keychain. You'll have to add that to your entitlements in the project file.
Here's how I'm using the suiteName for userDefaults sharing:
_userDefaults = [[NSUserDefaults alloc] initWithSuiteName:[config suiteName]];

Share appstore url with other apps

Is there any way to share web url (specifically link to an app in appstore) using UIDocumentInteractionController?
If that is not feasible what are the other options to enable the user to share the appstore url with other apps? (I am aware of ShareKit)
In this case you can use App Groups.
Create the same app group in both apps, and share with nsuserdefaults like this:
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.name"];

Today Extension (App Groups)

I have been browsing the Apple Documentation for hours now and there is so little on the Today Extension, so I just can't get to the bottom of this problem...
I am trying to access a plist file stored in the NSDocumentDirectory but am having no luck. The today extension is all set up correctly with a separate target in the project and my bundle identifier starts with 'group.' but I get a null value every time?
This is my code...
NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.ORGANISATION.APPNAME.wigit"];
NSString *string = [storeURL.path stringByAppendingPathComponent:#"DataFile.plist"];
NSMutableArray *content = [NSMutableArray arrayWithContentsOfFile:filePath];
Thank you for your help in advance!
If I understand your question correctly, you are trying to share data between the AppExtension and the containing app. By default your extension and its containing app have no direct access to each other’s containers. So the NSDocumentDirectory of your app will not be accessible form the extension. You could use NSUserDefaults to share the data.
Read it here Sharing Data with Your Containing App

iOS: Detect whether my SDK is installed on another apps on the device

I am developing a location based Q&A SDK for mobile devices.
When a question is asked about a specific location, the server side targets the most relevant user and sends the question to that user. If the user fails to answer, the question is sent to the second best user, and so on.
The problem is that my SDK might be installed on more than one application on the device, meaning that the user can get a question more than once.
Is there a way to detect whether my SDK is installed on more than one app? I thought that sending the UDID to the server might work, but iOS UDIDs differ between applications.
You can use UIPasteboard to share data between applications on the device.
The UIPasteboard class enables an app to share data within the app and with another app. To share data with any other app, you can use system-wide pasteboards; to share data with another app that has the same team ID as your app, you can use app-specific pasteboards.
In your SDK, do something like this:
#interface SDKDetector : NSObject
#end
#implementation SDKDetector
+ (void)load
{
int numberOfApps = (int)[self numberOfAppsInDeviceUsingSDK];
NSLog(#"Number of apps using sdk:%d", numberOfApps);
}
+ (NSInteger)numberOfAppsInDeviceUsingSDK
{
static NSString *pasteboardType = #"mySDKPasteboardUniqueKey";
NSData *value = [[UIPasteboard generalPasteboard] valueForPasteboardType:pasteboardType];
NSMutableArray *storedData = [[NSKeyedUnarchiver unarchiveObjectWithData:value] mutableCopy];
if (!storedData) {
storedData = [NSMutableArray new];
}
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
if (![storedData containsObject:bundleId]) {
[storedData addObject:[[NSBundle mainBundle] bundleIdentifier]];
}
value = [NSKeyedArchiver archivedDataWithRootObject:storedData];
[[UIPasteboard generalPasteboard] setData:value forPasteboardType:pasteboardType];
return [storedData count];
}
#end
If you only want to provide an SDK, it is not possible. Apple has added security steps to prevent that for user privacy. Keychain sharing will not work, because apps must share the same bundle seed ID (see here for more info).
If you want to provide an app along with your SDK, then you could do something like Facebook does, where app sends a "helo" message, Facebook asks user and finally Facebook sends "ehlo" message.
Your App -> "I would like to use the SDK; please give me token" -> SDK Controller App -> (Remember which apps have requested use) -> "OK, you can use the SDK; here is a token: #123" -> Your App
The SDK controller app can now send the server the list of apps.
I think you can group the apps on the same device by IP address as they will use the same address to connect to your server.
So the IP address will represent the device and the API key will represent the app that uses the SDK.
Can you try using
advertisingIdentifier
Not sure whether it serves your purpose. It is explained in here ASIdentifierManager class reference : Apple doc
I think its possible using keychain, you can have an unique keychain key in which you can save anything you want, and can be accessed by other apps if available. So for your SDK, lets say if there is one app, it will register some value in keychain with a unique key which is private to your SDK only if the key doesn't exist, and if it exist you get to know, since you can save any value in keychain, you can try multiple options and combinations which suits you.
You can use KeychainItemWrapper for the implementations.
Elaboration
Lets say we have an method.
[MySDK register];
Which can be used anywhere, say in AppDelegate. The register method will generate a token for the app, for the device, which we will save in the keychain using an unique key we have defined in the SDK, say in com.mysdk.key. And while saving in keychain the SDK can actually do a registration.
We consider the above method is implemented in multiple apps.
Now we have scenario.
User installs an App-A which uses the SDK, the register method will call and create a token and will save in keychain for the first time.
Now user installs another App-B which also uses the SDK, the same register method will call, but now it will check for the key com.mysdk.key in keychain, if exist it will just update the count for the token, which meant for the device.
Note
Keychain not meant to save only unique identifier, you can save other informations too.
Update
Check demo projects https://db.tt/7xpKrgMp
The wrapper I have used in the projects is same as SDK in your case which is same in both the projects.
Cheers.

Communicating and persisting data between apps with App Groups

iOS 8 revealed a new API yesterday concerning App Groups. It was kind of messy before to share data and communicate between apps and I believe that's precisely what App Groups is intended to correct.
In my app I have enabled App Groups and added a new group but I just can't find any documentation on how to use it. Documentation and API references only state how to add a group.
So what is App Groups really intended to do? Is there any documentation somewhere on how to use it?
Another benefit to App Groups is the ability to share a NSUserDefaults database. This also works for App Extensions (notification center widgets, custom keyboards, etc).
Initialize your NSUserDefaults object like this in all applications in the app group and they will share the database:
Objective-C:
[[NSUserDefaults alloc] initWithSuiteName:#"<group identifier>"];
Swift:
NSUserDefaults(suiteName: "<group identifier>")
Keep in mind everything from the [NSUserDefaults standardUserDefaults] database for each application will not carry over into this database.
The documentation gives a correct example as well (As of Beta 3).
And don't forget to synchronize the database:
[yourDefaults synchronize];
Sharing NSUserDefaults data between multiple apps
In order to have shared defaults between an app and an extension or between 2 apps you have to add an App Group in your settings using the following steps:
In the Project Navigator click on the *.xcodeproj file (should be at the top).
To the right of the Project Navigator look for Project and Targets. Under targets click on your primary target (should be the first thing under Targets).
Towards the top, click on the Capabilities tab.
In the App Groups section click the switch to the right to turn App Groups ON.
Click on the + button and add an App Group named group.com.company.myApp.
Go to the same place in your other apps and this group should now be available to select. Turn this group on for each app that will be using this shared data.
Note: If you go to the Apple Developer Portal (the Apple website that shows all of your Certificates, Identifiers, Devices and Provisioning Profiles) and go to Identifiers > App Groups you should see this new App Group.
To store data:
var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")!
userDefaults.setObject("user12345", forKey: "userId")
userDefaults.synchronize()
To retrieve data:
var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")
if let testUserId = userDefaults?.objectForKey("userId") as? String {
print("User Id: \(testUserId)")
}
Application groups, according to my interpretation of the existing documentation, are primarily targeted for extensions, more specifically, for widgets. Widgets are their own application bundle that coexist with your app. Since they are a separate application and therefore have their own sandbox, you will need to use App Groups to share files.
After some header grep'ing, I think I found the API needed, but was actually put in as part of iOS 7.
NSFileManager has a method on it containerURLForSecurityApplicationGroupIdentifier: where you can pass in the identifier you created when turning on App Groups for your apps:
NSURL *containerURL = [[NSFileManager defaultManager]
containerURLForSecurityApplicationGroupIdentifier:#"group.com.company.app"];
One important trap I tapped into today is the following:
In many projects I saw a single app target and with different bundle identifiers set for each configuration of that target. Here things get messy. What the developers intended was to create a debug app for the debug config and a production app for the release target.
If you do so both apps will share the same NSUserDefaults when they are set up like so
var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")
userDefaults!.setObject("user12345", forKey: "userId")
userDefaults!.synchronize()
This causes problems in many places:
Imagine you set YES for a key when a special app-intro-screen has been shown to the user. The other app will now also read YES and don't show the intro.
Yes some apps also store oAuth tokens in their user defaults. Anyways... Depending on the implementation, the app will recognize that there's a token and start retrieving data using the wrong token. The chance is high that this will fail with strange errors.
The solution to this problem in general is to prefix the defaults keys with the current configuration built. You can detect the configuration easily at runtime by setting different bundle identifiers for your configurations. Then just read the bundle identifier from NSBundle.mainBundle(). If you have the same bundle identifiers you need to set different preprocessor macros like
#ifdef DEBUG
NSString* configuration = #"debug";
#elif RELEASE
NSString* configuration = #"release";
#endif
In Swift it will look almost the same:
#if DEBUG
let configuration = "debug"
#elseif RELEASE
let configuration = "release"
#endif
iOS App Group
App Group allows you to share data(UserDefaults, Files, CoreData(manage model graph), POSIX locks) between different processes(applications, extensions...) from the same development team(account). It creates a shared container whit id which shoyld start from group. for saving/caching data which you are allowed to access thought url and IPC
To use App Group with UserDefaults
Add App Group capability with the same id for ALL targets(app, extension...) which you will get an access from
After creation you are able to check it on Apple Developer. Certificates, IDs & Profiles -> Identifiers -> ALL Groups
Write from Application1
let defaults = UserDefaults(suiteName: "group.goforit")
defaults?.setValue("Hello World!", forKey: "key1")
Read from Application2
let defaults = UserDefaults(suiteName: "group.goforit")
let result = defaults?.value(forKey: "key1") //Hello World!
shared container root location single URL.
let rootURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.goforit")
//file:///Users/alex/Library/Developer/CoreSimulator/Devices/1AF41817-FE2E-485A-A592-12C39C0B0141/data/Containers/Shared/AppGroup/DC14D43F-2C2C-4771-83BE-64A9F54BD2E1/
[iOS App Extension]
To store
let shared: NSUserDefaults = NSUserDefaults(suiteName: "group.abcapp")!
shared.setObject("abc.png", forKey: "favEmoji")
shared.synchronize()

Resources