Sharing an array of custom objects with Today Extension (widget) with NSUserDefaults - ios

this is my first stack post so please be constructive when reviewing my posting technique!
Basically, my problem is that I have an array of custom objects that I need to share with a today extension. The objects represent tasks in a to-do list, and their properties are used to store info about each task (name, location, dueDate, thumbnail, etc). The objects are stored in an array which is used to populate my to-do list. All I want to do is pass this array to my widget so that I can populate a second tableview which will act as a condensed version of the first (for the widget view).
I should point out that my widget is properly set up, as in I have properly linked it and the containing app together in 'groups'. I have also successfully used NSUserDefaults to pass an array of NSStrings to the widget, however, when I try to pass the array of objects to the widget, it crashes and my log reads:
*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (Xitem)'
I understand that this crash is related to archiving the object (Xitem), which seems to be a necessary step towards saving custom objects in NSUserDefaults. However, I have tested saving/loading the array within the same class of the containing app, and that works fine! (code below)
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.Xitems];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.AaronTest"];
[defaults setObject:encodedObject forKey:#"myArray"];
[defaults synchronize];
NSUserDefaults *defaults2 = [[NSUserDefaults alloc] initWithSuiteName:#"group.AaronTest"];
NSData *encodedObject2 = [defaults2 objectForKey:#"myArray"];
NSArray *array2 = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject2];
for (Xitem *t in array2){
NSLog(#"*****%#*****", t.itemName);
}
Okay so as explained, the above code works as expected. However, when i insert the second 'unarchiver' half of this code into my today widget, i get the aforementioned error. Below is my code to show how I encode/decode the object (it may be worth noting that this object was created for the simplicity of my debugging and only contains a NSString property):
Xitem.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface Xitem : NSObject <NSCoding>
{
NSString *itemName;
}
-(void)encodeWithCoder:(NSCoder*)encoder;
-(id)initWithCoder:(NSCoder*)decoder;
#property NSString *itemName;
#end
Xitem.m
#import "Xitem.h"
#implementation Xitem
#synthesize itemName;
-(void)encodeWithCoder:(NSCoder*)encoder
{
[encoder encodeObject:self.itemName forKey:#"iName"];
}
-(id)initWithCoder:(NSCoder*)decoder
{
self = [super init];
self.itemName = [decoder decodeObjectForKey:#"iName"];
return self;
}
#end
I could also post my widget and containing app code, but it doesn't differ from the first set of code i posted (apart from the renamed variables such as 'defaults2'). I should point out that I really have exhausted resources while trying to solve this problem, but the fact that using NSKeyedArchiver solely in the containing app works, has left me stumped.
I realise that this post is very similar to my own problem, but the author decides to opt for a workaround, whereas I would actually like to know why this doesn't work. I'm a new developer and I'm doing my best to pickup on the best working practices so any advice would be greatly appreciated.
I think it's also possible to replace my object (class) with an NSDictionary? However I would like to avoid this if possible because it would cause many conflicts in the main app, but obviously if that is the correct method I will tackle that problem. On a side note, if a dictionary would be better than an object for my requirements (to-do list with properties of UIImage, CLLocation, etc) for any other reasons (memory or accessibility for example) please do elaborate and help me to understand why!
Many thanks for anyones time :)

Okay so I just fixed this. Incase anyone has the same problem, go to: 'Targets' > 'Widget' > 'Build Phases' > 'Compile Sources' > add custom class there (Xitem.m)

Related

-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object Return then Get from NSUserDefaults

The following code is returning an exception with the following error message and My Application Crashed, in my code all data store in NSMutableDictionary and then store in NSUserDefaults
After get the data and assign global NSMutableDictionary and i will try to update data in NSMutableDictionary app crash and showing error
-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary * cacheUploadDataDic = [[defaults objectForKey:#"SalaryIncomeData"] mutableCopy];
Exactly, it crashes with mutable type issue, all the objects you retrieved from user defaults are inmutable, it uses the plist file to serialized the data list to file, check the plist programming guide about the supported types.
You shall regard the user defaults as a simple plist file storage wrapper.
Use NSMutableDictionary's 'initWithDictionary:' method instead.
Since an answer to the question in my comment does not appear to be forthcoming, I'm just going to go out on a limb and make the guess that your cacheUploadDataDic dictionary contains some more NSDictionary objects inside of it, and that your crash is occurring when you try to mutate one of those dictionaries. This fails because mutableCopy performs a shallow copy; only the dictionary object itself is copied, and all of the objects inside the dictionary, including any additional dictionaries, remain their original immutable selves.
You can fix this by making a deep copy of the dictionary instead. There are a few ways to do this, but the simplest is probably to use the CFPropertyListCreateDeepCopy function. Unfortunately, we have to do some bridging, since this API is only available at the CoreFoundation level, the reason for which is one of those eternal mysteries.
Anyway, do something like this:
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] objectForKey:#"SomeKey"];
NSMutableDictionary *mutableDict = CFBridgingRelease(
CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
(__bridge CFDictionaryRef)dict,
kCFPropertyListMutableContainersAndLeaves)
);
You will now be able to mutate the contents of mutableDict to your heart's content.
Follow your code, there shows no problem.
Then if possible, you can check the class of cacheUploadDataDic before update data and after NSMutableDictionary * cacheUploadDataDic = [[defaults objectForKey:#"SalaryIncomeData"] mutableCopy].
If there are one NSMutableDictionary and the other is NSDictionary then you must change cacheUploadDataDic at other line.
if you can't find out where you change the object,you can try KVO.

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.

iOS how to store data even if the app is closed

I am doing an iOS app. The goal is to send suggestions from user to improve the service I propose.
The app is a native app. I want to create a global NSMutableArray where I store all the suggestions. The problem is my device can be not connected to internet.
There is two cases:
If the device is connected when the user send his suggestion, it is send.
If not, the suggestion is saved in my NSMutableArray and send later.
Is it possible to create a global variable which store all my suggestions ? If my app is crashed/closed this variable can't be dealloc/reset.
Is a singleton enough to do this ?
first check for internet status of your device.if status is connected then send the array.if status in not connected then save the array into NSUserDefaults.To save suggestions array
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray * suggestions = [[NSMutableArray alloc] init];
[defaults setObject: suggestions forKey:#"suggestions"];
to Access those suggestions
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray * suggestions = [[NSMutableArray alloc]initWithArray:[defaultsobjectForKey:#"suggestions"]];
A singleton is not enough because you are going to lose all your data if your app is killed or crashes.
If there are only a few suggestions and they are short, the easiest approach would be to save them in your UserDefaults.
If you are going to save too much suggestions with long texts and several fields you will need to implement a database. Core Data would be a good solution for your problem.
An easy (single line of code) way to do this is to use plists and the command
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
on an NSArray
From the description:
Writes the contents of the array to a file at a given path.
If the array’s contents are all property list objects (NSString, NSData, NSArray, or NSDictionary objects), the file written by this method can be used to initialize a new array with the class method arrayWithContentsOfFile: or the instance method initWithContentsOfFile:. This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
In your example you would have (say) an NSArray of NSDictionaries with each NSDictionary containing the suggestions. Calling writeToFile would write it out in the event of no network availability. Then (say on app foreground event) you would read the file (arrayWithContentsOfFile call). If there is any content you would try to push to the server again.
You can use NSUserDefaults.
//write to NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSNumber numberWithInteger:userRatingInteger] forKey:#"userRating"];
[defaults synchronize];
//read from NSUserDefaults
NSNumber *userRatingNumber = [defaults objectForKey:#"userRating"];
if (userRatingNumber) { // the user has rated.
// Use the value as needed.
}

Need help understanding a conditional crash when accessing my NSDictionary

I am keeping my data in a property called practiceRecords (an NSArray of dictionaries).
I check to see if the data already exists in the documents folder.
If yes, I load the data into self.practiceRecords.
If not, I build the array of dictionaries (using literal syntax), keeping this data in the self.practiceRecords property, and then write the data out to the documents folder.
(I am NOT reloading the data after writing it out)
As far as I am able to tell, there are no problems occurring during this process.
Then I have a step where I modify my data as follows ...
-(void)incNumberOfTriesFor:(NSString *)stringOfIndex {
if (self.practiceRecords)
{
int index = [stringOfIndex intValue];
int numberOfTries = [(NSNumber *)(self.practiceRecords[index][#"tries"]) intValue] + 1;
//CRASHING on this next line.
self.practiceRecords[index][#"tries"] = #(numberOfTries);
//message to helper method
[self writePracticeRecords];
}
}
So the first time through (when the array is built and written out) I get a crash at the indicated line.
The error is:
-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance
I quit the app, check the documents folder and see the data file written out with no issues.
I re-run the app, and then get no crash and the data file still looks great.
This is repeatable.
If the data file exists, no crash.
If the data first needs to be created, then a crash.
(In all cases, I manually look inside the resulting data file and see exactly what I expect to see - no issues there)
I'm not sure where to even begin squashing this bug, and would really like to understand the details of why this is happening.
Thanks very much for any help!
Just to recap the correct comments above:
-[__NSDictionaryI setObject:forKeyedSubscript:]: unrecognized selector sent to instance
NSDictionary does not implement any of the set... methods because it is immutable. You state that you're creating with literals syntax when the data is not found on disk. The literal syntax creates immutable containers
Instead, try...
// try to initialize from disk, but if not
// we can still use literal (immutable) syntax, but in a mutable container
self.practiceRecords = [NSMutableDictionary
dictionaryWithDictionary:#{ #"key" : #"value" }];

stringWithContentsOfFile and initWithContentsOfFile return null after several runs

I am creating an iOS app which reads in a text file and displays the contents in a UIText field.
For the 1st three consecutive runs of thee app (Restarting a new session without exiting),
the data is read in fine. However on the fourth attempt, the data returned from the file is all nulls.
I've verified the file integrity. The issue exists when using stringWithContentsOfFile or initWithContentsOfFile.
After many hours of troubleshooting, I believe the issue is somehow related to a buffer being cleared within the above mentioned methods.
Any insight regarding this issue is greatly appreciated. I've tried many things with no luck.
Here's the code I use to read in the file:
TheString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:#"My_TextFile" ofType:#"txt"] encoding:NSUTF8StringEncoding error:NULL];
Here's the code I use to display certain contents of the file (The contents are placed in an array of type NSArray):
NSArray *My_Array;
My_Array= [TheString componentsSeparatedByString:#"\n"];
/* Obtain specific data to display */
DisplayedData = [My_Array objectAtIndex:M[l]-1];
:
:
/* Display the data in the view */
MyUITextView.text = DisplayedData;
/* Log the data */
NSLog(#"%#", MyUITextView.text);
On the 4th invocation of the code above, the data returned is blank and NSLOG is returning nulls
Thanks so much for any help!
Maybe I'm a little bit late with answer, but, anyway, maybe somebody will find it useful.
OK, I have also spent a day trying to figure out why my custom class for scrollable view is working 3 times and refuse at the 4-th time... I found that the problem has quite the same attributes as yours: nested NSString objects unexpectedly disappear. Though pointers point to the same address in memory, memory is already filled with quite arbitrary objects instead my NSStrings.
And I paid attention that I created these NSStrings using the following class method:
+ (id)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
So, I'm not the owner of these NSStrings.
And I assumed that to be the owner can be a solution, so I created my NSStrings through alloc and
- (id)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
instance method.
App was repaired!

Resources