Send NSArray Via AirDrop - ios

I have an NSMutableArray self.certificates
This array is made up of saved strings and core data. I want to send this through AirDrop. I have checked out serialization and and im trying to send it with the folowing
- (void)send{
NSData *jsonData2 = [NSJSONSerialization dataWithJSONObject:self.certificates options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData2 encoding:NSUTF8StringEncoding];
NSLog(#"Electrical Certificates List:\n%#", jsonString);
UIActivityViewController *activityCtr = [[UIActivityViewController alloc] initWithActivityItems:#[jsonString]
applicationActivities:nil];
NSMutableArray *excludedActivities = [self iOSActivities].mutableCopy;
[excludedActivities addObject:UIActivityTypeAddToReadingList];
[excludedActivities addObject:UIActivityTypePostToFlickr];
[excludedActivities addObject:UIActivityTypePostToTencentWeibo];
[excludedActivities addObject:UIActivityTypePostToVimeo];
[activityCtr setExcludedActivityTypes:excludedActivities];
[self presentViewController:activityCtr
animated:YES
completion:nil];
}
This gives me the following error
'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Certificate)'
I have converted to data to a string so not sure what im missing here
Ive researched NSInvalidArgumentException, reason: 'Invalid type in JSON write (__NSDate)' and How to send NSArray to web service

The exception is thrown by JSONSerialization, before the array is converted to data.
To share a custom data type, you'll want to implement NSCoding and UIActivityItemSource on your model object:
#interface CertificateGroup : NSObject <NSCoding, UIActivityItemSource>
#property(copy, nonatomic) NSArray *certificates;
#end
#implementation CertificateGroup
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Save all your custom properties
[aCoder encodeObject:self.certificates forKey:#"certificates"]l
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
// Read back properties
self.certificates = [aDecoder decodeObjectForKey:#"certificates"];
}
return self;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
//Let the activity view controller know NSData is being sent by passing this placeholder.
return [NSData data];
}
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
//Serialize this object for sending. NSCoding protocol must be implemented for the serialization to occur.
return [NSKeyedArchiver archivedDataWithRootObject:self];
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController dataTypeIdentifierForActivityType:(NSString *)activityType {
return #"com.mycompany.myapp.certificates";
}
#end
Then, when you create your activity view controller:
CertificatesGroup *group = [CertificatesGroup new];
group.certificates = self.certificates;
UIActivityViewController *activityCtr = [[UIActivityViewController alloc] initWithActivityItems:#[group]
applicationActivities:nil];
...
You're app delegate should implement -application:openURL:sourceApplication:annotation: and decode the incoming certificates.
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSData *groupData = [NSData dataWithContentsOfURL:url];
CertificatesGroup *group = [NSKeyedUnarchiver unarchiveObjectWithData:groupData];
NSLog(#"%#", group.certificates);
return YES;
}
For more info, see Apple's AirDropSample project, especially APLProfile.h/.m, APLProfileViewController.h/.m and AppDelegate.m.

Related

Background fetch and refresh completed after viewDidLoad in iOS 10

I'm trying to implement background fetch as well as refresh in iOS 10.
I'm using XML parsing to parse the data and then storing it in a file in the document's directory. For parsing XML I'm using a custom class (XMLParser) that confirms the NSXMLParserDelegate protocol.
The background fetch works fine. But I'm having problems in displaying the refreshed data, both when I click on the refresh button as well as in viewDidLoad.
I'm calling the refreshData method in viewDidLoad.
Here's how far I've gotten.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//--Set background fetch--//
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
}
...
#pragma mark Background data fetch methods
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSDate *fetchStart = [NSDate date];
ArtsViewController *artsViewController = (ArtsViewController *)self.window.rootViewController;
[artsViewController fetchNewDataWithCompletionHandler:^(UIBackgroundFetchResult result) {
completionHandler(result);
NSDate *fetchEnd = [NSDate date];
NSTimeInterval timeElapsed = [fetchEnd timeIntervalSinceDate:fetchStart];
NSLog(#"Background Fetch Duration: %f seconds", timeElapsed);
}];
}
ArtsViewController.h
#interface ArtsViewController : UIViewController <UIPageViewControllerDataSource>
#property BOOL newsAvailable;
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler; // No problems here
#end
ArtsViewcontroller.m
#interface ArtsViewController ()
#property (nonatomic, strong) NSArray *arrNewsData;
-(void)refreshData;
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray;
#end
...
#implementation ArtsViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self refreshData];
//--Load the file that saves news--//
[self loadNews];
if (_newsAvailable == YES)
{
[self setupPageViewController];
}
else
{
[self showNoNewsMessage];
}
}
...
#pragma mark Data Fetch methods
-(void)refreshData{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
[self performNewFetchedDataActionsWithDataArray:dataArray];
}
else{
NSLog(#"%#", [error localizedDescription]);
}
}];
}
-(void)performNewFetchedDataActionsWithDataArray:(NSArray *)dataArray{
// 1. Initialize the arrNewsData array with the parsed data array.
if (self.arrNewsData != nil) {
self.arrNewsData = nil;
}
self.arrNewsData = [[NSArray alloc] initWithArray:dataArray];
// 2. Write the file and reload the view.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docDirectory = [paths objectAtIndex:0];
NSString * newsFilePath = [NSString stringWithFormat:#"%#",[docDirectory stringByAppendingPathComponent:#"arts2"]]; // NewsFile
if (![self.arrNewsData writeToFile:newsFilePath atomically:YES]) {
_newsAvailable = NO;
NSLog(#"Couldn't save data.");
}
else
{
_newsAvailable = YES;
NSLog(#"Saved data.");
[self viewWillAppear:YES];
}
}
-(void)fetchNewDataWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
XMLParser *xmlParser = [[XMLParser alloc] initWithXMLURLString:ArtsNewsFeed];
[xmlParser startParsingWithCompletionHandler:^(BOOL success, NSArray *dataArray, NSError *error) {
if (success) {
NSDictionary *latestDataDict = [dataArray objectAtIndex:0];
NSString *latestTitle = [latestDataDict objectForKey:#"title"];
NSDictionary *existingDataDict = [self.arrNewsData objectAtIndex:0];
NSString *existingTitle = [existingDataDict objectForKey:#"title"];
if ([latestTitle isEqualToString:existingTitle]) {
completionHandler(UIBackgroundFetchResultNoData);
NSLog(#"No new data found.");
}
else{
[self performNewFetchedDataActionsWithDataArray:dataArray];
completionHandler(UIBackgroundFetchResultNewData);
NSLog(#"New data was fetched.");
}
}
else{
completionHandler(UIBackgroundFetchResultFailed);
NSLog(#"Failed to fetch new data.");
}
}];
}
...
#pragma mark IBActions
- (IBAction)reloadNews:(UIBarButtonItem *)sender
{
[self viewDidLoad];
}
I've debugged the application and found that after viewDidLoad
completes execution, the data file is written but the view isn't
updated. I've also tried calling the refreshData method in the main
thread, but there's no change.
after viewDidLoad is complete the showNoNewNews method is called.
I'm suspecting that my logic isn't wrong but implementation is. Threads at play here..
Any help would be appreciated.
Update:
Hope this helps those with similar problems...
I moved the logic of viewDidLoad to a different method, called the method for the first time in viewDidLoad and again in refreshData, after
[self performNewFetchedDataActionsWithDataArray:dataArray];

How to manage openUrl method inside called application in iOS?

I suppose that this is duplicate but I can not figure it out.
I have to call other app from my iOS app using openUrl method. After finishing its work the other app must return to my app using the same method. I figure out how to call the other App and its open my App too. My problem is how to intercept the return to my App. I need to check the value from query string.
I find that method handleOpenURL intercepts return and I can handle my query string.
And here I am stuck - how to use that info inside my ViewController? I set breakpoint in viewDidLoad but it was not hit. Which method I have to use?
EDIT:
My Code is (inside AppDelegate):
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSLog(#"url recieved: %#", url);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
NSLog(#"url path: %#", [url path]);
NSDictionary *dict = [self parseQueryString:[url query]];
NSLog(#"query dict: %#", dict);
return YES;
}
- (NSDictionary *)parseQueryString:(NSString *)query {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:6];
NSArray *pairs = [query componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSArray *elements = [pair componentsSeparatedByString:#"="];
NSString *key = [[elements objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *val = [[elements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[dict setObject:val forKey:key];
}
return dict;
}
Which works fine.
Inside my ViewController (VC):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setNeedsStatusBarAppearanceUpdate];
// Instantiate App singleton
singApp = [PESsingApplication sharedInstance];
#try {
// Localize resources using currently saved setting for language
[self setLocalizedResources];
// Init visual buttons
[self baseInit];
// Add code for keyboard management
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardHide:)
name:UIKeyboardWillHideNotification
object:nil];
CGRect screenRect = [[UIScreen mainScreen] bounds];
_screenHeight = screenRect.size.height;
_screenWidth = screenRect.size.width;
}
#catch (NSException *exception) {
[self throwUnknownException:exception];
}
}
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
My url:
URL identifier: xx.mydomain.MyUrlScheme
URL shcemes: MyUrlScheme
I have breakpoints inside my VC (on each of the method shown above).
I use following string to call other app: #"otherApp://openApp?param1=value1&callbackUrl=MyUrlScheme";
They call me from the otherApp using callbackUrl param.
You need to make your own custom URL, please look below
How to implement Custom URL Scheme
Defining your app's custom URL scheme is all done in the Info.plist file. Click on the last line in the file and then click the "+" sign off to the right to add a new line. Select URL Types for the new item. Once that's added, click the grey arrow next to "URL Types" to show "Item 0". Set your URL identifier to a unique string - something like com.yourcompany.yourappname.
After you've set the URL identifier, select that line and click the "+" sign again, and add a new item for URL Schemes. Then click the grey arrow next to "URL Schemes" to reveal "Item 0". Set the value for Item 0 to be your URL scheme name.
Handling Custom URL Calls
In order for your app to respond when it receives a custom URL call, you must implement the application:handleOpenURL method in the application delegate class:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
// your code
}
Parsing the Custom URL
There are several parts to a URL:
scheme://host/path?query
The parts to the URL can be retrieved through the NSURL object that is passed into the application:handleOpenURL method. If you have a fairly simple URL naming scheme and want to allow access to specific pages/keys, you can just use the host name:
Custom URL Value of [url host]:
myapp://page1 page1
myapp://page2 page2
myapp://otherPage otherPage
To pass data into your app, you'll want to use the query string. Here's a simple method for parsing the query string from the url:
- (NSDictionary *)parseQueryString:(NSString *)query {
NSMutableDictionary *dict = [[[NSMutableDictionary alloc] initWithCapacity:6] autorelease];
NSArray *pairs = [query componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSArray *elements = [pair componentsSeparatedByString:#"="];
NSString *key = [[elements objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *val = [[elements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[dict setObject:val forKey:key];
}
return dict;
}
Testing The Custom URL
You can easily test your URL scheme in the simulator. Just add a test button to one of your views, and implement the IBAction method for it as follows:
- (IBAction)getTest:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:#"myappscheme://test_page/one?token=12345&domain=foo.com"]];
}
Then in your app delegate, implement the application:handleOpenURL method:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSLog(#"url recieved: %#", url);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
NSLog(#"url path: %#", [url path]);
NSDictionary *dict = [self parseQueryString:[url query]];
NSLog(#"query dict: %#", dict);
return YES;
}
Finally if you are looking method to receive your data anywhere you can use this two scenario.
You can simple use Local notification or NSUserDefault
NSUserDefault
- (BOOL)application:(UIApplication *)application handleopenURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSUserDefaults *userDefaults=[[NSUserDefaults alloc] init];
[userDefaults synchronize];
NSString *status = [defaults stringForKey:#"any status"];
}
Local notification
- (BOOL)application:(UIApplication *)application handleopenURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:VAL, #"value", nil];
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
}
If your viewDidLoad is not called perfectly try in viewWillAppear or viewDidAppear method.
For example purpose:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
NSDictionary *dict = [self parseQueryString:[url query]];
NSLog(#"query dict: %#", dict);
// add dictionary to standardUserDefaults for saving purpose, like
[[NSUserDefaults standardUserDefaults] setObject:dict forKey:#"DicKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
// add code for navigation/present view controller
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main"
bundle: nil];
YourViewController *yourController = (YourViewController *)[mainStoryboard
instantiateViewControllerWithIdentifier:#"YourViewControllerID"];
self.window.rootViewController = yourController;
return YES;
}
for retrieve
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSMutableDictionary *mutableRetrievedDictionary = [[[NSUserDefaults standardUserDefaults] objectForKey:#"DicKey"] mutableCopy];
// here parse the dictionary and do your work here, when your works is over
// remove the key of standardUserDefaults
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"DicKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Store the status from other app in NSUserdefaults, when the ViewController of your app launches fetch the status into a NSString from NSUserdefaults and rise it as an alert.
Call the handleopenURL in appdelegate
- (BOOL)application:(UIApplication *)application handleopenURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
NSUserDefaults *defaults=[[NSUserDefaults alloc] init];
[defaults synchronize];
NSString *status = [defaults stringForKey:#"status string from other app"];
}

How do I use UIActivityItemProvider to send an email with attachment with UIActivityViewController?

I am trying to use UIActivityItemProvider to share a file from within my app via email attachment. I also need to populate the subject line of the email and to specify the name of the attachment to be something different than the name of the file stored on the device.
Here is the code that I'm using. The problem is that the attachment is missing from the email.
#interface ItemProvider:UIActivityItemProvider
#property (nonatomic, strong) NSURL *filepath;
#property (nonatomic, strong) NSString *emailBody;
#property (nonatomic, strong) NSString *emailSubject;
#end
#implementation ItemProvider
- (id)initWithPlaceholderItem:(id)placeholderItem
{
//Initializes and returns a provider object with the specified placeholder data
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
//Generates and returns the actual data object
return [NSDictionary dictionary];
}
// The following are two methods in the UIActivityItemSource Protocol
// (UIActivityItemProvider conforms to this protocol) - both methods required
#pragma mark UIActivityItemSource
//- Returns the data object to be acted upon. (required)
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
if ([activityType isEqualToString:UIActivityTypeMail]) {
return #{#"body":self.emailBody, #"url":self.filepath};
}
return #{#"body":self.emailBody, #"url":self.filepath};
}
//- Returns the placeholder object for the data. (required)
//- The class of this object must match the class of the object you return from the above method
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return #{#"body":self.emailBody, #"url":self.filepath};
}
-(NSString *) activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return self.emailSubject;
}
#end
And then in my viewController I do this:
ItemProvider *provider = [[ItemProvider alloc] initWithPlaceholderItem:#{#"body":emailBody, #"url":filePath}];
provider.emailBody = emailBody;
provider.emailSubject = info.title;
provider.filepath = filePath;
NSArray *activityItems = #[provider];
// Build a collection of custom activities (if you have any)
// NSMutableArray *customActivities = [[NSMutableArray alloc] init];
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
For those still stumbling upon a solution for this, there is a more elegant solution for customizing UIActivityViewController. To address the original question, the reason the attachment is not showing up is because it is supposed to be a separate UIActivityItemProvider object.
So the solution is to create two UIActivityItemProvider subclasses, one to wrap the 'emailBody' and 'emailSubject' and another to wrap the attachment. The benefit to using a UIActivityItemProvider for the attachment is that you have the opportunity to delay processing the attachment until it is needed, rather than doing so before presenting UIActivityViewController.
Implement the AttachmentProvider class to provide the attachment like so:
#implementation AttachmentProvider : UIActivityItemProvider
- (id)item {
if ([self.activityType isEqualToString:UIActivityTypeMail]) {
/* Replace with actual URL to a file. Alternatively
* you can also return a UIImage.
*/
return [NSData dataWithContentsOfURL:dataURL];
}
return nil;
}
#end
Implement EmailInfoProvider class to provider the email body and subject class like so:
#implementation EmailInfoProvider : UIActivityItemProvider
- (id)item {
return #"Your email body goes here";
}
- (NSString *)activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
if ([activityType isEqualToString:UIActivityTypeMail]) {
return #"Your subject goes here";
}
return nil;
}
#end
You can then create a UIActivityViewController with both these items in your viewController like so:
- (void)shareAction {
AttachmentProvider *attachment = [[AttachmentProvider alloc] init];
EmailInfoProvider *emailContent = [[EmailInfoProvider alloc] init];
// You can provider custom -(id)init methods to populate EmailInfoProvider
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:#[attachment, emailContent] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
i'm sending email with attachment without ItemProvider. its working well :-)
NSMutableArray *selDocs = [[NSMutableArray alloc] init];
for (Document *theDoc in self.selectedDocs) {
NSURL *fileUrl = [NSURL fileURLWithPath:theDoc.filePath];
[selDocs addObject:fileUrl];
}
NSArray *postItems = [NSArray arrayWithArray:selDocs];
UIActivityViewController *avc = [[UIActivityViewController alloc] initWithActivityItems:postItems applicationActivities:nil];
[avc setValue:#"Your email Subject" forKey:#"subject"];
avc.completionHandler = ^(NSString *activityType, BOOL completed){
NSLog(#"Activity Type selected: %#", activityType);
if (completed) {
NSLog(#"Selected activity was performed.");
} else {
if (activityType == NULL) {
NSLog(#"User dismissed the view controller without making a selection.");
} else {
NSLog(#"Activity was not performed.");
}
}
};
[self presentViewController:avc animated:YES completion:nil];

Getting UITableView to populate with data from another class

I am quite new to Objective-C and this is the first time I have attempted to implement MVC. I have a model class where l have an NSArray which will be populated with data from a JSON object. I want to populate my UITableView (in my view controller class), with objects from this array.
Please review my code:
Droplets.h
#interface Droplets : NSObject {
NSArray *dropletsArray;
}
// Get droplets data
- (void) getDropletsList;
//Object initilization
- (id) init;
//Public properties
#property (strong, nonatomic) NSArray *dropletsArray; // Used to store the selected JSON data objects
#end
Droplets.m
#define kBgQueue dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define kDigialOceanApiURL [NSURL URLWithString:#"http://inspiredwd.com/api-test.php"] //Droplets API call
#import "Droplets.h"
#interface Droplets ()
//Private Properties
#property (strong, nonatomic) NSMutableData *data; // Used to store all JSON data objects
#end
#implementation Droplets;
#synthesize dropletsArray;
#synthesize data;
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
- (void) getDropletsList {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = kDigialOceanApiURL; // Predefined Digital Ocean URL API http request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection connectionWithRequest:request delegate:self]; //Should be: [[NSURLConnection alloc]initiWithRequest:request delegate:self]; ...however the instance of NSURLConnection is never used, which results in an "entity unsed" error.
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
data = [[NSMutableData alloc]init]; // mutable data dictionary is allocated and initilized
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData {
[data appendData:theData]; // append 'theData' to the mutable data dictionary
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
//JSON foundation object returns JSON data from a foundation object. Assigned returned data to a dictionary 'json'.
NSDictionary* jsonData = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions error:0];
self.dropletsArray = [jsonData objectForKey:#"droplets"]; //dictionary of arrays
NSLog(#"Droplets %#", self.dropletsArray);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// If the application is unable to connect to The Digital Ocean Server, then display an UIAlertView
UIAlertView *errorView = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Unable to connect to The Digital Ocean Server, please ensure that you are connected via either WIFI or 3G." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO; // Turn of the network activity indicator
}
#end
DropletsList.h
#class Droplets;
#interface DropletsList : UITableViewController
- (Droplets *) modelDroplets;
#end
DropletsList.m
#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#interface DropletsList ()
//Private properties
#property (strong, nonatomic) Droplets *modelDroplets;
#property (strong, nonatomic) NSArray *tableData;
#end
#implementation DropletsList
#synthesize tableData;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
NSLog(#"get my data from model");
}
return self;
}
- (Droplets *) modelDroplets
{
if (!_modelDroplets) _modelDroplets = [[Droplets alloc]init];
return _modelDroplets;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_modelDroplets = [[Droplets alloc]init];
self.tableData = [_modelDroplets dropletsArray];
[_modelDroplets getDropletsList];
[self.tableView reloadData]; // reload the droplets table controller
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
return 1; // Return the number of sections.
}
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [_modelDroplets.dropletsArray count]; // Return the number of rows in the section.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// The cell identified by "dropletsList", is assiged as the UITableViewCell
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:#"dropletsList"];
//NSLog(#"Droplets Name: %#",self.dropletsArray);
// The UITableView text label is assigned the contents from 'dropletsArray', with the object key "name"- name of the droplet
cell.textLabel.text=[[tableData objectAtIndex:indexPath.row]objectForKey:#"name"];
// The UITableView text detail label is assigned the contents from 'dropletsArray', with the object key "status"- status of the droplet
cell.detailTextLabel.text=[[tableData objectAtIndex:indexPath.row]objectForKey:#"status"];
//Evalulate the status of each droplet, setting the colour appropriate to the staus
if ([[[tableData objectAtIndex:indexPath.row] objectForKey:#"status"] isEqualToString:#"active"]) {
//Set the detail text label colour
cell.detailTextLabel.textColor = RGB (35,179,0);
}
return cell;
}
#end
Basically my table doesn't populate. Please could someone help?
- (void)viewDidLoad
{
[super viewDidLoad];
_modelDroplets = [[Droplets alloc]init];
self.tableData = [_modelDroplets dropletsArray];
[_modelDroplets getDropletsList];
[self.tableView reloadData]; // reload the droplets table controller
}
In this method you are fetching droplets from a webservice. It is asynchronous, by the time tableView reloads the data it might not have completed fetching the data. You need to have a callback which will reload the tableView on completion of webservice.
EDIT :
Create a class method in Droplets to fetch all data
//Droplets.h
typedef void (^NSArrayBlock)(NSArray * array);
typedef void (^NSErrorBlock)(NSError * error);
//Droplets.m
+ (void)getDropletsWithCompletion:(NSArrayBlock)arrayBlock onError:(NSErrorBlock)errorBlock
{
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:kDigialOceanApiURL];
[urlRequest setHTTPMethod:#"GET"];
[urlRequest setCachePolicy:NSURLCacheStorageNotAllowed];
[urlRequest setTimeoutInterval:30.0f];
[urlRequest addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
if (error) {
errorBlock(error);
}else{
NSError *serializationError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&serializationError];
arrayBlock(json[#"droplets"]);
}
}];
}
//DropletsList.h
- (void)viewDidLoad
{
[super viewDidLoad];
[Droplets getDropletsWithCompletion:^(NSArray *array) {
self.modelDroplets = droplets;
[self.tableView reloadData];
} onError:^(NSError *error) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:error.localizedDescription delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}];
}
Disclaimer : Tested and verified :)

Decoder crashing through a bluetooth connection

I have an object that I want to send to another device via bluetooth. I have successfully setup the bluetooth connection and transferred an encoded NSString; however, I haven't figured out how to use the archiving and encoding tools correctly to send an Object.
I want to send the object defined below called ChatMessage. It implements the NSCoding delegate methods initWithCoder and encodeWithCoder as seen below.
In the second code snippet, I have the code for sending and receiving the data i.e. the methods that result in the de-encoder being called.
It keeps crashing on the last line of the decode method. I've been struggling to figure it out what is going wrong. Any help would be greatly appreciated!
#interface ChatMessage : NSObject <NSCoding> {
NSString *sender;
NSString *message;
}
#property (nonatomic, retain) NSString *sender;
#property (nonatomic, retain) NSString *message;
#end
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:sender forKey:#"sender"];
[coder encodeObject:message forKey:#"message"];
}
- (id)initWithCoder:(NSCoder *)coder {
sender = [[coder decodeObjectForKey:#"sender"] retain];
message = [[coder decodeObjectForKey:#"message"] retain];
return self;
}
In my View, the protocol for the PeerPicker Delegate functions.
- (void) receiveData:(NSData *)data
fromPeer:(NSString *)peer
inSession:(GKSession *)session
context:(void *)context {
ChatMessage *aMsg = [[ChatMessage alloc] init];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData:data];
#try {
aMsg = [unarchiver decodeObjectForKey:#"myMessage"];
}
#catch (NSException *exception) {
NSLog(#"Error: %#", exception);
}
#finally {
}
if (!messages) messages = [[NSMutableArray alloc] init];
[messages addObject:aMsg];
// reload the table
[messageList reloadData];
[unarchiver finishDecoding];
[unarchiver release];
[data release];
}
--- The code was crashing because I had
[data release];
I found this using the instruments tool.

Resources