Communicating and persisting data between apps with App Groups - ios

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()

Related

Where is the UserDefaults data stored when sharing it with app and extension?

I have created an application which uses "UserDefaults" for storing the data. I am using the App Groups capability to share the data between the host app and extension. Where can I see the data stored in it?
When using the default (UserDefaults.standard) UserDefaults I am able to see the data stored under "Library -> Preferences -> appbundlename.plist.
The application is displaying the data even when I kill it from background.(data is persisting)
let sharedDefaults = UserDefaults(suiteName: ListManager.GroupId)
let groupPath = FileManager().containerURL(forSecurityApplicationGroupIdentifier: ListManager.GroupId)!.appendingPathComponent("Library/Preferences")
Will get you the path to the shared group defaults.
Just be sure to enable the App Groups capabilities for your app (and extensions). Otherwise, your group defaults will just appear in the same directory as the standard user defaults (and thus persisting between launches) but will be inaccessible by your other apps or extensions.

how to access a sound file stored in another app in swift?

this question is a lot like Share data between two or more iPhone applications except:
I'm looking for a way to do this in an iOS 8+ (or 9+) application using swift
I want to be able to use the sound file contained in the first app so the easter egg I'm making is only available when the user has both apps installed
since this is part of an easter egg, i don't want to use any method that would cause anything extra to be displayed on screen such as a browser redirect or some kind of permission popup
(this basically rules out using the share sheet and custom url's to pass data as described in the post above)
I am using AVAudioPlayer and AVAudioSession in the first app to play the sound if that is at all helpful.
Use App Group
You can share actual NSData through the NSUserDefaults:
if let userDefaults = NSUserDefaults(suiteName: <group>) {
userDefaults.setObject(obj, forKey: key)
}
and retrieve from another app in the same group like so:
if let userDefaults = NSUserDefaults(suiteName: <group>) {
if let obj = userDefaults.objectForKey(key) {
// magic
}
}
It appears that the only limitation for passing data through the user defaults is the device storage capacity, and since NSUserDefaults accepts NSData as a storage format, it makes a prime candidate for sharing tidbits information.
If both apps are yours you can implement a custom url scheme in the second app, and then from the first app ask if it knows how to open an URL with that scheme. If the answer is yes, the app is installed. The function is called canOpenURL. It's an instance method of UIApplication.
I vaguely remember that in iOS 9 and later, Apple added a restriction that you have to register the URLs you are going to ask about in your info.plist, but I don't remember the details. That won't prevent this scheme from working, but it is an extra step you have to take.

iOS App - include rating-link - choose type of App ID

I'm close to submitting an app to the App Store and would like to include a rating-link in the first published version to present the user an easy way to rate the app or write a review.
So first I generated a new app via iTunes Connect without any further steps - just an empty hull. Inside the app there is an area named “App-Information” with a link at the bottom named “Show in App Store”. Of course this link is not active, because the app is not “Ready for Sale” at this time. But perhaps this link can be modified with GET-Parameters for example, to accomplish this task once the app is ready for sale.
Do you know how I can build an appropriate link for rating/writing reviews before the app is approved?
Then I don't know for sure whether to choose a wildcard App ID or not for a created app. Is the only criterion the need of using a service expecting an explicit App ID? For example, if I would like to store app related data inside iCloud then I would create and choose an explicit ID, and otherwise I also can use an already existing wildcard ID?
And can I first create an App with a wildcard App ID and choose an explicit App ID later when needed? I think I don’t understand the need or advantage of wildcard IDs in common, because a new App ID (explicit or not) is created within a minute with very little effort.
you can use this
itms-apps://itunes.apple.com/app/idYOUR_APP_ID
For versions lower than iOS 7 use the old one:
itmsapps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=YOUR_APP_ID
in code
define YOUR_APP_STORE_ID 545174222 //Change this one to your ID
static NSString *const iOS7AppStoreURLFormat = #"itms-apps://itunes.apple.com/app/id%d";
static NSString *const iOSAppStoreURLFormat = #"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%d"
[NSURL URLWithString:[NSString stringWithFormat:([[UIDevice currentDevice].systemVersion floatValue] >= 7.0f)? iOS7AppStoreURLFormat: iOSAppStoreURLFormat, YOUR_APP_STORE_ID]]; // Would contain the right link

Sharing file/data between two iOS apps that I own

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")

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.

Resources