Share Plist between iOS App and WatchKit Extension - ios

I have an app that saves and uses data from a plist file. I'm working on a WatchKit extension that needs to access the same plist file to display data and save to the file. I know I need to be using app groups but I don't know how to share the plist between the iOS app and the WatchKit extension.
Here is how I'm saving to the plist in the iOS app currently.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docPath = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"locations.plist"];
BOOL fileExists = [fileManager fileExistsAtPath:docPath];
NSError *error = nil;
if (!fileExists) {
NSString *strSourcePath = [[NSBundle mainBundle]pathForResource:#"locations" ofType:#"plist"];
[fileManager copyItemAtPath:strSourcePath toPath:docPath error:&error];
}
NSString *path = docPath;
NSMutableArray *plistArray = [[NSMutableArray alloc]initWithContentsOfFile:path];
NSDictionary *locationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:self.locationNameTextField.text, #"locationName", latString, #"latitude", longString, #"longitude", nil];
[plistArray insertObject:locationDictionary atIndex:0];
[plistArray writeToFile:docPath atomically:YES];

Once you've setup your app group (in both your primary iPhone app and the Watch Extension), you can get the path to the shared folder:
NSURL *groupContainerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"YourAppGroupSuiteName"];
NSString *groupContainerPath = [groupContainerURL path];
You can then use the groupContainerPath to build your docPath. Otherwise, your code should work as-is.

I was able to successfully use plist data from my main existing iPhone app in my WatchKit app using Swift.
There are two steps:
Enable WatchKit App Extension Target Membership for each plist you want to use. Click on the plist, then:
Here's the Swift code I used to read the plist, which has "id" and "name" fields.
func valueFromPlist(value: Int, file: String) -> String? {
if let plistpath = NSBundle.mainBundle().pathForResource(file as String, ofType: "plist") {
if let entries = NSArray(contentsOfFile: plistpath) as Array? {
var entry = Dictionary<String, Int>()
for entry in entries {
if let id = entry.objectForKey("id") as? Int {
if id == value {
if let name = entry.objectForKey("name") as? String {
return name
}
}
}
}
}
}
return nil
}

Related

How to protect iOS bundle files like plist ,image,sqlite,media files

I have created sample hello world project and then added Data.plist file to resource folder.
Now people can easily get the bundle files by unzipping the .ipa file. Is there any ways to protect the Data.plist file that saved in the application bundle of iPhone app?
Encryption is a decent method of scrambling the data but i don't know how to implement encription concept.
Do you have any sample code?
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
NSArray *arrData = [[NSArray alloc]initWithContentsOfFile:filePath];
NSData *datas = [NSKeyedArchiver archivedDataWithRootObject:arrData];
[datas writeToFile:filePath atomically:YES];
After extracting IPA file
encrypt the files on the mac... during deployment:
FIRST: don't add the files to be encrypted to the target
e.g. Encryption-Test.plist
THEN add a shell script phase to your xcode project to use openssl to encrypt&copy the files.
e.g.
openssl enc -e -aes-256-cbc -pass pass:asdasd
-in $PROJECT_DIR/test/Encryption-Test.plist
-out $TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/Encryption-Test.enc
add RNCryptor source files from github to your project. This makes decryption of openSSL encrypted AES files really easy. (Thanks rob!) https://github.com/RNCryptor/RNCryptor
(Apple's CCrypt api isn't nice to work with directly)
load the data and decrypt it:
e.g.
#implementation TestViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"Encryption-Test" ofType:#"enc"];
NSData *passEncryptedData =[[NSData alloc] initWithContentsOfFile:path];
NSString *pass = #"asdasd";
NSData *dataDecrypted = [RNOpenSSLDecryptor decryptData:passEncryptedData withSettings:kRNCryptorAES256Settings password:pass error:nil];
id plist = [NSPropertyListSerialization propertyListFromData:dataDecrypted mutabilityOption:NSPropertyListImmutable format:nil errorDescription:nil];
assert(plist);
self.text.text = [plist description];
}
#end
added full sample: https://github.com/Daij-Djan/encryptBundleFiles
I struggled with the above examples with no success and finally found some code that lets you decrypt an encrypted file (any file in fact) with openssl. I'm working in the documents folder for this example and I'm not using any shell scripting. Here's how to do it:
Encrypt your file in the Terminal like so:
openssl aes-256-cbc -in questions.plist -out questions.enc
Add the file(s) to your Xcode project by dragging them to the Supporting Files directory
Download the RNCryptor library here.
Find the RNCryptor library in the downloaded project: encryptBundleFiles-master/test/RNCryptor and add them into to your Xcode project
Import the RNDecryptor.h and RNOpenSSLDecryptor.h files into your class file, like ExampleViewController.m file.
Add the code below from where you want to call it, like a certain function.
And you're done. You can now use the plist file to for example populate a tableview.
// Copy the plist file from the resources folder to the documents folder
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:#"questions.enc"];
if ([fileManager fileExistsAtPath:txtPath] == NO) {
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"questions" ofType:#"enc"];
[fileManager copyItemAtPath:resourcePath toPath:txtPath error:&error];
}
NSString *filePath1 = [documentsDirectory stringByAppendingPathComponent:#"questions.encr"];
NSData *passEncryptedData =[[NSData alloc] initWithContentsOfFile:filePath1] ;
NSString *pass = #"asdf"; // Insert your password from step 1
NSData *dataDecrypted = [RNOpenSSLDecryptor decryptData:passEncryptedData withSettings:kRNCryptorAES256Settings password:pass error:&error];
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:#"questionsDECRYPTED.plist"]; //The Decrypted file saved here
[dataDecrypted writeToFile:appFile atomically:YES];
Based on Daij-Dan's solution, I created a couple of Swift String extensions. Requires RNCryptor
They are used like this:
//Decrypt a UIImage
let decryptedImage = "encyptedFileNameInBundle".decryptedImage(password: "test")
//Decrypt NSData
let decryptedData = "encyptedDataFileNameInBundle".decryptedData(password: "test")
The string is the file name of the resource that has been added to the bundle resources. It assumes the file has an "enc" extension by default, otherwise, pass the file extension with "fileExtension:".
Extension:
public extension String {
func decryptedImage(password: String, fileExtension: String = "enc") -> UIImage? {
//create encypted file with:
//openssl aes-256-cbc -in fileName.jpg -out fileName.enc
if let decryptedData = self.decryptedData(password: password, fileExtension: fileExtension) {
return UIImage(data: decryptedData)
}
return nil
}
func decryptedData(password: String, fileExtension: String = "enc") -> Data? {
//create encypted file with:
//openssl aes-256-cbc -in fileName.data -out fileName.enc
if let fileURL = Bundle.main.url(forResource: self, withExtension: fileExtension) {
do {
let encyptedData = try Data(contentsOf: fileURL)
return try RNOpenSSLDecryptor.decryptData(encyptedData, with: kRNCryptorAES256Settings, password: password)
} catch {
print("encryptedImage ERR: \(error.localizedDescription)")
}
}
return nil
}
}
Use a NSKeyedArchiver to create an NSData object from your dictionary (NSKeyedArchiver archivedDataWithRootObject:). Then encrypt the NSData with AES and write that to your file.
Reading just the reverse process: first, read the NSData, decrypt it via the method from the mentioned link, then pass the decrypted NSData to NSKeyedUnarchiver (NSKeyedUnarchiver unarchiveObjectWithData:) and you get your dictionary back. That you can use for plist file or as NSDictionary to keep your data safe.
Example 1:
Example 2:
EDIT 2:
NSDictionary *Your_NSDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
#"Obj1", #"Key1",
#"Obj2", #"Key2", nil];
//store dictionary
NSMutableData *yourData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:Your_NSDictionary forKey: #"key"];
[archiver finishEncoding];
[yourData writeToFile:#"FilePath" atomically:YES];
or
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"Data"
ofType:#"plist"];
NSDictionary* data = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: data forKey:#"accounts"];
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];

how to store an image path in a plist?

I know this is probably a silly question but I'm storing most of my game data in a plist - with that I'd like to include references to images used within my game - same hierarchal level as 'supporting files'. I have different types of images stored in 3 separate folders. One folder for example is called imageclue. How could I store the path in my plist, I'm stuck because I can't just store the path in my plist as string - filename.jpg. I've tried getting the path of the file but when I log it out it .
Sorry if I'm not explaining well and thank you in advance for any help :)
EDIT**
I have a plist file added to my program I don't want to programatically add to it as the images are constants - the screenshots below show a tutorial instead of the filename.jpg (because that won't work seen as my images are stored in a file) I wondered what path name do I use as a string.
The image is from a tutorial off of appcoda.com - where it says thumbnails are the image path files. If you look at where the images are stored on the left - they are stored with the program files. My images are in a folder in there so I'm confused as to what to enter in my plist for the image file.
Hope this clears up what I meant, sorry :)
Store three variables in .h file
#interface YourViewController : UIViewController
{
NSString *folder1;
NSString *folder2;
NSString *folder3;
}
in viewdidload:
-(void) viewdidLoad
{
//get the documents directory:
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//getting the folder name:
folder1 = [NSString stringWithFormat:#"%#/imageclue",
documentsDirectory];
folder2 = [NSString stringWithFormat:#"%#/folder2",
documentsDirectory];
folder3 = [NSString stringWithFormat:#"%#/folder3",
documentsDirectory];
}
-(NSArray*) getPlistFromFolder:(NSString*)folder imageName:(NSString*)image
{
NSString *imageTitle = [NSString stringWithFormat:#"%#/image",
folder];
NSArray *data = [[NSArray alloc] initWithContentsOfFile:plistName];
return data;
}
So in the plist file, just store the image name.
Hope this helps...
Do it like this,
NSDictionary *imagePaths = #{#"image 1": [NSHomeDirectory() stringByAppendingPathComponent:#"image 1"]};
[self writeToPlist:imagePaths];
- (void)writeToPlist:imagePaths:(id)plist{
NSError *error;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist format:kCFPropertyListXMLFormat_v1_0 options:0 error:&error];
if(error){
NSLog(#"Could not write to file");
return;
}
[data writeToFile:[self plistPath] atomically:YES];
}
Like wise loading is simple as this;
[self loadImagePathForImageNamed:#"image 1"];
- (NSString*)loadImagePathForImageNamed:(NSString*)imageName{
}
- (NSString*)loadImagePathForImageNamed:(NSString*)imageName{
NSData *data = [NSData dataWithContentsOfFile:[self plistPath]];
NSString *error;
NSPropertyListFormat format;
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
if(error){
NSLog(#"Could not open plist %#", error);
return nil;
}
return dictionary[imageName];
}
You may have to handle the error when the file is not there by creating a new one, otherwise this should work.
You are storing path right way, just need to store filename of image with extension in plist when your images are in your Application Bundle, for more reference you can define key name Instead "item1", "item2" in your plist.
Now coming to actual Question, how to access image from plist
Step 1 : Read your recipes.plist from Application Bundle
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"recipes" ofType:#"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:bundlePath];
Step 2 : Now Get Image/Thumbnails name out of it, which you want to load
Step 3 : Define following Function in your Controller, which returns image from name
- (UIImage *)getImageWithName:(NSString *)imageFileName
{
NSString *ext = [imageFileName pathExtension];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[imageFileName stringByDeletingPathExtension] ofType:ext];
return [UIImage imageWithContentsOfFile:imagePath];
}
HOW TO USE
Suppose you want to load Image with key "Item2" then write following code
NSString *imageFileName = [[dict objectForKey:#"Thumbnail"] valueForKey:#"Item2"];
UIImage *item2Image = [self getImageWithName:imageFileName];
For "Item6"
NSString *imageFileName1 = [[dict objectForKey:#"Thumbnail"] valueForKey:#"Item6"];
UIImage *item6Image = [self getImageWithName:imageFileName1];

iOS plist is null

I know there are a multitude of questions about this on SO but I can't see where I am making the mistake and am hoping some extra eyes will help. I've verified the plist is in my bundle and it is also in my docs directory and it contains data. Here's a screen capture of the app package with the plist at top:
I pass the plist in from another class and have verified that it is the correct plist.
Here's my code:
-(id)init {
if (self = [super init]) {
//set up the appTracker
appTracker = [[OAI_AppTracker alloc] init];
//set up a file manager and error container
fileManger=[NSFileManager defaultManager];
//docs directory path
documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
//track event
appTracker.appEvent = #"File Manager initialized";
[appTracker recordEvent];
}
return self;
}
- (NSDictionary* ) readPlist {
NSError* error;
//set up dictionary to hold our app data
NSDictionary* appData;
//set up destination path
NSString* destinationPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#", plistToRead]];
if ([fileManger fileExistsAtPath:destinationPath]){
//read plist
appData = [[NSDictionary alloc] initWithContentsOfFile:destinationPath];
} else {
//file doesn't exist so we have to move it to the doc folder
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:plistToWrite];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
//now read the plist
appData = [[NSDictionary alloc] initWithContentsOfFile:destinationPath];
}
NSLog(#"%#", appData);
return appData;
}
My log shows NULL instead of the data in the plist. Appreciate any help as to what I am doing wrong.
To read your plist try something like this:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"PlistFileName" ofType:#"plist"];
NSDictionary *plistData = [[NSDictionary alloc] initWithContentsOfFile:plistPath];

What is the correct way to save user data using NSFileManager?

I am having trouble initializing dictionaries I use throughout my program to store user achievements and scores.
I have almost identical code for the two dictionaries and only the gameCenterData dictionary seems to be working properly. I have tried altering the plist file name and contents yet nothing seems to make the playerData dictionary properly load info from the file as it should
In the Root View Controller I have the following code (playerData and gameCenterData are both NSMutableDictionaries and the plist files are in the proper place)
-(NSString *)scoreFilePath
{
NSArray *scorePath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [scorePath objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"PlayerScoreData.plist"];
}
-(NSString *)gameCenterFilePath
{
NSArray *gameCenterPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [gameCenterPath objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"GameCenterData.plist"];
}
then the view did load
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *playerDataPath = [self scoreFilePath];
if (! [[NSFileManager defaultManager] fileExistsAtPath:playerDataPath])
{
playerData = [NSMutableDictionary dictionaryWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"scoreData.plist"]];
[playerData writeToFile:[self scoreFilePath] atomically:YES];
NSLog(#"Player data file does not exist");
}
else
{
playerData = [[NSMutableDictionary alloc] initWithContentsOfFile:[self scoreFilePath]];
NSLog(#"player data file exists");
}
NSLog(#"scoreData is %#",playerData);
NSString *gameCenterPath = [self gameCenterFilePath];
if (! [[NSFileManager defaultManager] fileExistsAtPath:gameCenterPath])
{
gameCenterData = [NSMutableDictionary dictionaryWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"gameCenterData.plist"]];
[gameCenterData writeToFile:[self gameCenterFilePath] atomically:YES];
NSLog(#"game center data file does not exist");
}
else
{
gameCenterData = [[NSMutableDictionary alloc] initWithContentsOfFile:[self gameCenterFilePath]];
NSLog(#"game center data file exists");
}
NSLog(#"gameCenterData is %#",gameCenterData);
the output is as follows
2012-08-05 11:46:49.991 GlobeRoller[6410:1be03] Player data file does not exist
2012-08-05 11:46:49.992 GlobeRoller[6410:1be03] playerData is (null)
2012-08-05 11:46:50.061 GlobeRoller[6410:1be03] game center data file does not exist
2012-08-05 11:46:50.062 GlobeRoller[6410:1be03] gameCenterData is {
"Career Odometer" = 0;
"Career Score" = 0;
"Cities Found" = 0;
"Offline Games Played" = 0;
"Online Games Played" = 0;
"Online Games Won" = 0;
}
I have searched all of the questions and answers to see if I can find out why this isn't working for both methods. Any help you could offer, or resources you could point me to I would greatly appreciate.
Thank you,
CF
The plist file you are trying to load from the bundle is either not there, or has been created improperly. Directly from the documentation of dictionaryWithContentsOfFile:.
Return Value
A new dictionary that contains the dictionary at path, or
nil if there is a file error or if the contents of the file are an
invalid representation of a dictionary.
You should make sure you are using the proper file name, and then open your plist in Xcode to see if it is properly formatted.
iOS is case sensitive. Are you sure that your file in the bundle is lower case, i.e. "#"scoreData.plist", and not upper case like the name your code uses? Also, verify that these two files are in your bundle - check the build phase or select the files (one at a time) and look in the 3rd Xcode pane in the file attribute section (to verify they are included in your target). If all that looks good then when you try to retrieve the files from your bundle:
Also, don't try to find the file at the root level of the bundle - you should be using:
NSString *path = [[NSBundle mainBundle] pathForResource:#"GameCenterData" ofType:#"plist"];
NSLog(#"PATH is %#", path);
...then use path instead of the code you are using now

How to add and get the values from .plist in iOS

I am implementing a application based on web services. In that I need to add a string as property in .plist and I need to get the value from the .plist whenever I need in the code.
Here is a code sample:
NSString *path = [[NSBundle mainBundle] pathForResource: #"YourPLIST" ofType: #"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: path];
id obj = [dict objectForKey: #"YourKey"];
NSBundle* mainBundle = [NSBundle mainBundle]; 
 
// Reads the value of the custom key I added to the Info.plist
NSString *value = [mainBundle objectForInfoDictionaryKey:#"key"];
//Log the value
NSLog(#"Value = %#", value);
// Get the value for the "Bundle version" from the Info.plist
[mainBundle objectForInfoDictionaryKey:#"CFBundleVersion"];
// Get the bundle identifier
[mainBundle bundleIdentifier];
NSURL *url = [[NSBundle mainBundle] URLForResource:#"YOURPLIST" withExtension:#"plist"];
NSArray *playDictionariesArray = [[NSArray alloc ] initWithContentsOfURL:url];
NSLog(#"Here is the Dict %#",playDictionariesArray);
or you can use following also
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Sample.plist"];
Get from plist is very simple.
NSString *path = [[NSBundle mainBundle] pathForResource:#"SaveTags" ofType:#"plist"];
if (path) {
NSDictionary *root = [NSDictionary dictionaryWithContentsOfFile:path];
}
If you want to add something to a plist, maybe you can find the answer here:
How to write data in plist?
But if you only want save some message in you app, NSUserDefaults is the better way.
You can not do this. Any Bundle wether it is iOS or Mac OS is readonly, you can only read to it and you can't create files, write or do anything with the files in a bundle. This is part of the Security features of apple. You can use the NSDocumentsDirectory to writr and read your stuff you need for your app
Swift
I know this was asked 12+ years ago. But this was the first SO question to come up via google. So to save time for everyone, here is how to do this in swift:
struct Config {
// option 1
static var apiRootURL: String {
guard let value = (Bundle.main.object(forInfoDictionaryKey: "BASE_URL") as? String), !value.isEmpty else {
fatalError("Base URL not found in PLIST")
}
return value
}
// option 2
static var databaseName: String {
guard let value = (Bundle.main.infoDictionary?["DB_NAME"] as? String), !value.isEmpty else {
fatalError("DB NAME not found in PLIST")
}
return value
}
}
Notice the 2 functions use slightly diffrent methods to access the plist. But in effect they are almost the same.
In theory there might not be a plist. Hence infoDictionary is optional. But in this case the first method will also return an unexpected value, resulting in an error.
One actual difference as noted by Apple:
Refering to Bundle.main.object(forInfoDictionaryKey: "BASE_URL")
Use of this method is preferred over other access methods because it returns the localized value of a key when one is available.

Resources