Access App Identifier Prefix programmatically - iphone

How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).
I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.

Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.
So, try this:
In your Info.plist, add an info about app identifier prefix.
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
You can then retrieve it programmatically with Objective-C:
NSString *appIdentifierPrefix =
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"AppIdentifierPrefix"];
and with Swift:
let appIdentifierPrefix =
Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String
Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.

You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.
+ (NSString *)bundleSeedID {
NSString *tempAccountName = #"bundleSeedID";
NSDictionary *query = #{
(__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount : tempAccountName,
(__bridge NSString *)kSecAttrService : #"",
(__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess) {
return nil;
}
status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
return bundleSeedID;
}

Here is the Swift version of #David H answer:
static func bundleSeedID() -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "bundleSeedID" as AnyObject,
kSecAttrService as String: "" as AnyObject,
kSecReturnAttributes as String: kCFBooleanTrue
]
var result : AnyObject?
var status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if status == errSecItemNotFound {
status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
}
if status == noErr {
if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
let components = accessGroup.components(separatedBy: ".")
return components.first
}else {
return nil
}
} else {
print("Error getting bundleSeedID to Keychain")
return nil
}
}

This is a good question but to achieve what you were intended to do, there could have been a solution
that does not require to retrieve the Bundle Seed ID.
From this article, about the same keychain wrapper you're using:
By default it will pick the first access-group specified in your
Entitlements.plist when writing and will search across all
access-groups when none is specified.
The key will then be search in all groups where access is granted.
So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set

If you search in Xcode on your team's ID then you will see that this value is hosted in the build settings under the key DEVELOPMENT_TEAM.
You can retrieve this key by putting in your Info.plist file:
<key>DEVELOPMENT_TEAM</key>
<string>$(DEVELOPMENT_TEAM)</string>
Make sure to put this in every target's Info.plist file where you want to retrieve it using this code:
let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String
This solution will give you the team ID without the dot suffix.
The solution in https://stackoverflow.com/a/28714850/2743633 worked for me only to get the team ID from the main app target. It would not retrieve the team ID when doing the same for a Share Extension target.

Related

Uniquely identify IOS device [duplicate]

I am developing an iOS app which calls web-service for login and at that time i send login credentials to web server along with vendor identifier (identifierForVendor),to identify device uniquely for those credentials.So user can have only one device and one credential.
I got identifierForVendor with
NSString *uuid = [[UIDevice currentDevice] identifierForVendor].UUIDString
This identifier will then store in database of web server and also in device database.Next time when user opens application and will try to download data from web server firstly local identifierForVendor on users device will compare with identifier stored on web server.
Problem occurs when user uninstall app and reinstall it, I found that identifierForVendor is changed. So user cannot proceed further.
I read apple documentation UIDevice Documentation
As mention there, if all app from same vendor uninstalls from device then at time of new installation of any app from that vendor will take new identifierForVendor.
So how to deal with this in my case ?
You may keep it in KeyChain
-(NSString *)getUniqueDeviceIdentifierAsString
{
NSString *appName=[[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
NSString *strApplicationUUID = [SSKeychain passwordForService:appName account:#"incoding"];
if (strApplicationUUID == nil)
{
strApplicationUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[SSKeychain setPassword:strApplicationUUID forService:appName account:#"incoding"];
}
return strApplicationUUID;
}
Generally, don't use identifierForVendor. Instead, use NSUUID to generate a custom UUID and store that in the keychain (because the keychain isn't deleted if the app is deleted and reinstalled).
Addition to #nerowolfe's answer.
SSKeychain uses kSecAttrSynchronizableAny as a default synchronization mode. You probably don't want identifierForVendor to be synced across multiple devices so here is a code:
// save identifierForVendor in keychain without sync
NSError *error = nil;
SSKeychainQuery *query = [[SSKeychainQuery alloc] init];
query.service = #"your_service";
query.account = #"your_account";
query.password = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
query.synchronizationMode = SSKeychainQuerySynchronizationModeNo;
[query save:&error];
You can try use KeyChain to save your VendorIdentifier, that will exist till your device is reset, even if you uninstall your app.
Ok. I didn't want to use a third party - namely SSKeychain. So this is the code I tried, fairly simple and works well:
NSString *bundleId = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"];
KeychainItemWrapper *keychainItem = [[KeychainItemWrapper alloc] initWithIdentifier:bundleId accessGroup:nil];
if(![keychainItem objectForKey:(__bridge id)(kSecValueData)]){
NSString *idfa = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
[keychainItem setObject:idfa forKey:(__bridge id)(kSecValueData)];
NSLog(#"saving item %#", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}else{
NSLog(#"saved item is %#", [keychainItem objectForKey:(__bridge id)(kSecValueData)]);
}
Swift version
func UUID() -> String {
let bundleName = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
let accountName = "incoding"
var applicationUUID = SAMKeychain.passwordForService(bundleName, account: accountName)
if applicationUUID == nil {
applicationUUID = UIDevice.currentDevice().identifierForVendor!.UUIDString
// Save applicationUUID in keychain without synchronization
let query = SAMKeychainQuery()
query.service = bundleName
query.account = accountName
query.password = applicationUUID
query.synchronizationMode = SAMKeychainQuerySynchronizationMode.No
do {
try query.save()
} catch let error as NSError {
print("SAMKeychainQuery Exception: \(error)")
}
}
return applicationUUID
}
There is no definite way to link a unique number to a device any more, this is not allowed with the Apple privacy guidelines.
You can try to save your own Unique ID in the keychain, but if the user clear his device this ID is also gone.
Generally is it just wrong to link a device to a user, since you are not longer identifying users but devices. So you should just change your API so that the user can re-login and that the vendor ID is bound to the users account.
Also what happens when the user has more then one device, like an iPhone and iPad, and uses you app on both? Since you authentication is based an unique ID this can not be done.
I had used KeychainAccess pod for this problem.
In your pod file :
pod 'KeychainAccess', '~> 2.4' //If you are using Swift 2.3
pod 'KeychainAccess' //Defaults to 3.0.1 which is in Swift 3
Import KeychainAccess module in file where you want to set UUID in keychain
import KeychainAccess
Use below code to set and get UUID from keychain :
Note : BundleId is key and UUID is value
var bundleID = NSBundle.mainBundle().bundleIdentifier
var uuidValue = UIDevice.currentDevice().identifierForVendor!.UUIDString
//MARK: - setVenderId and getVenderId
func setVenderId() {
let keychain = Keychain(service: bundleID!)
do {
try keychain.set(venderId as String, key: bundleID!)
print("venderId set : key \(bundleID) and value: \(venderId)")
}
catch let error {
print("Could not save data in Keychain : \(error)")
}
}
func getVenderId() -> String {
let keychain = Keychain(service: bundleID!)
let token : String = try! keychain.get(bundleID!)!
return token
}

How to check if a NSDictionary already has a key, and then add items under that same key?

I am setting up a NSDictionary object so that NSDictionary *scoreObject has the name of the player for its key, and then a mutable dictionary of { date : score } for its values. To get the data, I am pulling a custom class I made in Parse, which have the attributes "Name", "Score" and "createdAt".
I am trying to set up the structure so that the above could be automatically pulled across each row of data in Parse, but am running into trouble when I have two rows of data for the same Name, which gets set as keys in my scoreObject. For example, if Bob has two scores and two createdAt dates, how would I be ale to simply expand the values dictionary so that both could still be stored under the key = "Bob"?
Thanks!
Try something like this:
NSDictionary *dict;
//this is the dictionary you start with. You may need to make it an NSMutableDictionary instead.
//check if the dictionary contains the key you are going to modify. In this example, #"Bob"
if ([[dict allKeys] containsObject:#"Bob"]) {
//there already is an entry for bob so we modify its entry
NSMutableDictionary *entryDict = [[NSMutableDictionary alloc] initWithDictionary:dict{#"Bob"}];
[entryDict setValue:#(score) forKey:#"Date"];
[dict setValue:entryDict forKey:#"Bob"];
}
else {
//There is no entry for bob so we make a new one
NSDictionary *entryDict = #{#"Date": #(score)};
[dict setValue:entryDict forKey:#"Bob"];
}
Here's some code to help you. You may have to adapt something to your case:
Allocating your main dict somewhere:
// assuming its a property
self.scoreObject = [NSMutableDictionary new];
Now, whenever you will set a new pair date/score for a name, first check if that name already has any entry. If yes, use the previous allocated NSMutableDictionary to store the new pair. If not, allocate one and then set the new pair.
I'm encapsulating it in a method that receives the date and the score.
-(void)addNewScore:(NSString*)score AndDate:(NSString*)date forUsername:(NSString*)username
{
NSMutableDictionary *scoresForUser = self.scoreObject[username]; //username is a string with the name of the user, e. g. #"Bob"
if (!scoresForUser)
{
scoresForUser = [NSMutableDictionary new];
self.scoreObject[username] = scoresForUser
}
scoresForUser[date] = score; //setting the new pair date/score in the NSMutableDictionary of scores of that giver user.
}
ps: I used date and score as string in the example, but you can user NSDate or NSNumber with no change if you want.
Now, you are able to list all scores of a user with something like this:
-(void)listScoresForUser:(NSString*)username
{
NSMutableDictionary *scoresForUser = self.scoreObject[username];
for (NSString *date in [scoresForUser allKeys]) {
NSString *score = scoresForUser[date];
NSLog(#"%# - score: %#, createdAt: %#", username, score, date);
}
}
In this way, you should be able to store the data in the structure you want. Please let me know if that is something like you were looking for.

Is there a way to retrieve the Bundle Seed ID of an app at runtime? [duplicate]

How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).
I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.
Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.
So, try this:
In your Info.plist, add an info about app identifier prefix.
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
You can then retrieve it programmatically with Objective-C:
NSString *appIdentifierPrefix =
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"AppIdentifierPrefix"];
and with Swift:
let appIdentifierPrefix =
Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String
Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.
You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.
+ (NSString *)bundleSeedID {
NSString *tempAccountName = #"bundleSeedID";
NSDictionary *query = #{
(__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount : tempAccountName,
(__bridge NSString *)kSecAttrService : #"",
(__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess) {
return nil;
}
status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
return bundleSeedID;
}
Here is the Swift version of #David H answer:
static func bundleSeedID() -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "bundleSeedID" as AnyObject,
kSecAttrService as String: "" as AnyObject,
kSecReturnAttributes as String: kCFBooleanTrue
]
var result : AnyObject?
var status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if status == errSecItemNotFound {
status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
}
if status == noErr {
if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
let components = accessGroup.components(separatedBy: ".")
return components.first
}else {
return nil
}
} else {
print("Error getting bundleSeedID to Keychain")
return nil
}
}
This is a good question but to achieve what you were intended to do, there could have been a solution
that does not require to retrieve the Bundle Seed ID.
From this article, about the same keychain wrapper you're using:
By default it will pick the first access-group specified in your
Entitlements.plist when writing and will search across all
access-groups when none is specified.
The key will then be search in all groups where access is granted.
So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set
If you search in Xcode on your team's ID then you will see that this value is hosted in the build settings under the key DEVELOPMENT_TEAM.
You can retrieve this key by putting in your Info.plist file:
<key>DEVELOPMENT_TEAM</key>
<string>$(DEVELOPMENT_TEAM)</string>
Make sure to put this in every target's Info.plist file where you want to retrieve it using this code:
let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String
This solution will give you the team ID without the dot suffix.
The solution in https://stackoverflow.com/a/28714850/2743633 worked for me only to get the team ID from the main app target. It would not retrieve the team ID when doing the same for a Share Extension target.

iPhone fetch data dictionary from keychain

So I'm trying to convert an old project to Automatic Reference Counting. I'm trying to use the conversion tool that xCode has but it says to fix a couple things before it can convert. I have no idea how to fix this error. It's in the implementation of the keychain file. This method is the one that returns the error, specifically the line with the SecItemCopyMatching. The error I am getting says: " Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef*' (aka 'const void**') is disallowed with ARC. I've been looking all over google, apple docs, and a bunch of other crap and can't find a better way to fetch an existing data dictionary in the keychain. Any help appreciated. Thanks!
-(NSMutableDictionary*)fetchDictionary {
NSMutableDictionary *genericPasswordQuery = [self buildSearchQuery];
NSMutableDictionary *outDictionary = nil;
OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef)genericPasswordQuery, (CFTypeRef*)&outDictionary);
if (DEBUG) printf("FETCH: %s\n", [[self fetchStatus:status] UTF8String]);
if (status == errSecItemNotFound) return NULL;
return outDictionary;
}
You don't need to disable ARC for this; you just need to declare the query's result as a CFDictionaryRef, then cast it to an NSDictionary after the call.
/*1*/ CFDictionaryRef cfquery = (__bridge_retained CFDictionaryRef)genericPasswordQuery;
/*2*/ CFDictionaryRef cfresult = NULL;
/*3*/ OSStatus status = SecItemCopyMatching(cfquery, (CFTypeRef *)&cfresult);
/*4*/ CFRelease(cfquery);
/*5*/ NSDictionary *result = (__bridge_transfer NSDictionary *)cfresult;
Couple of remarks:
In line 1, we convert the query from Cocoa land to Core Foundation country. We use __bridge_retained to make sure ARC does not release and deallocate the object while we are working with it. This kind of bridging cast retains the object, so to prevent leaks, it must be followed with a corresponding CFRelease somewhere. SecItemCopyMatching definitely will not release the query for us, so if we use a retained bridge, then we need to release the resulting Core Foundation object ourself. (Which we do in line 4.)
Lines 2, 3, and 4 are pure C code using Core Foundation types, so ARC will have nothing to do or complain about them.
In line 5, we tell ARC that SecItemCopyMatching has created its result with a retain count of 1, which we are responsible for releasing. (We know this because it has "Copy" in its name.) __bridge_transfer lets ARC know about this responsibility, so it will be able to do it for us automatically.
Don't cast the immutable Core Foundation dictionary returned by SecItemCopyMatching to NSMutableDictionary; that's just wrong. Also, it is against general Cocoa style conventions that buildSearchQuery returns an NSMutableDictionary. Simple NSDictionarys would work fine in both cases.
The rule of thumb here is that __bridge_retained needs to be followed by a CFRelease, while the result from a "Copy" or "Create" function must be cast into Cocoa-land using __bridge_transfer.
Method 3: Let ARC do the heavy lifting (or a combination of method 1 and method 2):
NSMutableDictionary* query = [NSMutableDictionary dictionaryWithDictionary:
#{
(__bridge id) kSecClass : (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService : nssService,
#if ! TARGET_IPHONE_SIMULATOR
(__bridge id) kSecAttrAccessGroup : #"PRODUCT.com.COMPANY.GenericKeychainSuite",
#endif
(__bridge id) kSecMatchLimit : (__bridge id) kSecMatchLimitOne,
(__bridge id) kSecReturnAttributes : (__bridge id) kCFBooleanTrue,
}];
if ( [nssAccount length] != 0 )
[query setObject:nssAccount forKey:(__bridge id) kSecAttrAccount];
CFDictionaryRef cfresult;
auto err = ::SecItemCopyMatching((__bridge CFDictionaryRef)query,
(CFTypeRef*)&cfresult);
if ( err == errSecItemNotFound )
return std::wstring();
else if ( err != noErr)
throw std::exception();
NSDictionary* result = (__bridge_transfer NSDictionary*) cfresult;
SecItemCopyMatching doesn't need to own the incoming dictionary, so __bridge is
adequate and then ARC continues to manage the lifetime of query.
And by transferring ownership of the result to arc, it will manage the lifetime
of result as well and free us of the need to remember to CFRelease it on all code
paths.
Method 2: When you use it once, why need a retaining or transfering? example from bottom work at glance for me, testing, debuging(memleaks) all pass :) Only one thing did leak unreleased autoretained variable when key will be found (1)
CFDictionaryRef keyAttributes = NULL; /* variable for store attributes */
NSMutableDictionary *credQuery = [NSMutableDictionary dictionary]; // credential Query
/* Here you add some options for search your key */
OSStatus errGather = SecItemCopyMatching(
(__bridge CFDictionaryRef)credQuery,
(CFTypeRef *)&keyAttributes
);
if (errGather == errSecSuccess) {
// Gather stored key
NSDictionary *keychainDict = (__bridge NSDictionary *)keyAttributes;
NSData *passData = keychainDict[(__bridge id<NSCopying>)kSecValueData]; // password
...
/* work with gathered data :) */
...
CFRelease(keyAttributes); // (1) HERE. Release when CFType is retained really :)
credQuery = nil;
keychainDict = nil;
passData = nil;
}

copy image and text to the UIPasteboard

I want to copy image and text (both) to UIPasteBoard.
Is it possible to copy both the text and image.
Here I can copy image only or text only .
How to copy both ?
My code for copy image is as follows,
UIPasteboard *pasteBoard = [UIPasteboard pasteboardWithName:UIPasteboardNameGeneral create:NO];
pasteBoard.persistent = YES;
NSData *data = UIImagePNGRepresentation(newImage);
[pasteBoard setData:data forPasteboardType:(NSString *)kUTTypePNG];
Thanks in advance !!!!!
Here is my code and it is working perfectly on my device.
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.persistent = NO;
NSMutableDictionary *text = [NSMutableDictionary dictionaryWithCapacity:1];
[text setValue:captionLabel.text forKey:(NSString *)kUTTypeUTF8PlainText];
NSMutableDictionary *image = [NSMutableDictionary dictionaryWithCapacity:1];
[image setValue:gratitudeImageView.image forKey:(NSString *)kUTTypePNG];
pasteboard.items = [NSArray arrayWithObjects:image,text, nil];
You should be setting the items property of the pasteboard-
The description of items from the reference is-
items
The pasteboard items on the pasteboard. #property(nonatomic,copy)
NSArray *items Discussion
The value of the property is an array of dictionaries. Each dictionary
represents a pasteboard item, with the key being the representation
type and the value the data object or property-list object associated
with that type. Setting this property replaces all of the current
pasteboard items.
So, you can add two dictionaries to an array, with key value pairs being & and set this array to the items property.
In my experience, the official way simply does not work in iOS. Instead of creating an individual dictionary for each item and adding those to the array (as stated in the documentation), add all items to a single dictionary, then make an array with that single dictionary and set that to the pasteboard.
Like this:
NSMutableDictionary * pasteboardDict = [NSMutableDictionary dictionary];
[pasteboardDict setObject:someData forKey:someUTIkey];
[pasteboardDict setObject:someOtherData forKey:someOtherUTIkey];
[[UIPasteboard generalPasteboard]setItems:[NSArray arrayWithObject:pasteboardDict]];
This question was asked a long time ago, but it's still relevant - and especially since Apple docs don't make Swift multi-format UIPasteboard APIs very clear. Having struggled to figure out how to do multiple-format copy & paste, I thought I'd share my solution in case it helped anyone else. In my case, I needed to support an internal format (containing all the particulars), as well as image and text versions for pasting into other apps.
First, you need to get access to the UTI constants - you'll get unresolved symbols without adding this at the top of your file:
import MobileCoreServices
Then define your format UTI:
let my_private_uti = "com.mydomain.myapp.myformat"
Here's the code for an example multi-format copy (in my case for a music program):
externalRepresentation = "[A7]"
internalRepresentation = "A7:0 0 2 0 2 0"
image = UIImage()
// fill image with chord diagram...
let pasteboard =
[ [kUTTypeUTF8PlainText as String : externalRepresentation],
[kUTTypePNG as String: UIImagePNGRepresentation(image!)!],
[my_private_uti: internalRepresentation]]
UIPasteboard.general.setItems(pasteboard)
And now for the the paste. I want to accept my internal format if it's available, and fall back to processing text if it's not. (Don't do anything with a graphical format in my case.)
//Handle internal format
if let pastedata = UIPasteboard.general.data(forPasteboardType:my_private_uti, inItemSet:nil) {
if pastedata.count > 0 {
if let ourformat = String(data: pastedata[0] as! Data, encoding: .utf8) {
// Process ourformat string
print("Pasted internal representation: \(ourformat)")
return
}
}
}
// Handle plain text format
if let pastedata = UIPasteboard.general.data(forPasteboardType:kUTTypeUTF8PlainText as String, inItemSet:nil) {
if pastedata.count > 0 {
if let textformat = String(data: pastedata[0] as! Data, encoding: .utf8) {
// Process normal text
print("Pasted external representation: \(textformat)")
return
}
}
}
Seems like it's pointless to set the persistent boolean in iOS, from Apple:
iOS, public (system) pasteboards are persistent, but by default private (application) pasteboards are not. These private pasteboards do not continue to exist when the application that creates them quits. However, you can mark application pasteboards as persistent.