I am trying to download a pdf file from a server to the device. Here is the code that I am using
- (id)initwithURL:(NSString*)remoteFileLocation andFileName:(NSString*)fileName{
//Get path to the documents folder
NSString *resourcePathDoc = [[NSString alloc] initWithString:[[[[NSBundle mainBundle]resourcePath]stringByDeletingLastPathComponent]stringByAppendingString:#"/Documents/"]];
localFilePath = [resourcePathDoc stringByAppendingString:fileName];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:localFilePath];
if (fileExists == NO) {
NSURL *url = [NSURL URLWithString:remoteFileLocation];
NSData *data = [[NSData alloc] initWithContentsOfURL: url];
//Write the data to the local file
[data writeToFile:localFilePath atomically:YES];
}
return self;
}
where remoteFileLocation is a NSString and has the value http://topoly.com/optimus/irsocial/Abs/Documents/2009-annual-report.pdf
On running the app crashes, just on NSData giving a SIGABRT error. The only useful information it gives is
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSURL length]: unrecognized selector sent to instance 0xc87b600'
How can this be fixed ?
As your PDF file is too large in size so if you do Synchronous Download, it will take too Long to download, so i insist you to create an Asynchronous Downloader and Use it. I have put code for the same.
Step 1 :Create a file 'FileDownloader.h'
#define FUNCTION_NAME NSLog(#"%s",__FUNCTION__)
#import <Foundation/Foundation.h>
#protocol fileDownloaderDelegate <NSObject>
#optional
- (void)downloadProgres:(NSNumber*)percent forObject:(id)object;
#required
- (void)downloadingStarted;
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data;
- (void)downloadingFailed:(NSURL *)url;
#end
#interface FileDownloader : NSObject
{
#private
NSMutableURLRequest *_request;
NSMutableData *downloadedData;
NSURL *fileUrl;
id <fileDownloaderDelegate> delegate;
double totalFileSize;
}
#property (nonatomic, strong) NSMutableURLRequest *_request;
#property (nonatomic, strong) NSMutableData *downloadedData;
#property (nonatomic, strong) NSURL *fileUrl;
#property (nonatomic, strong) id <fileDownloaderDelegate> delegate;
- (void)downloadFromURL:(NSString *)urlString;
#end
Step 2 : Create a .m file with FileDownloader.m
#import "FileDownloader.h"
#implementation FileDownloader
#synthesize _request, downloadedData, fileUrl;
#synthesize delegate;
- (void)downloadFromURL:(NSString *)urlString
{
[self setFileUrl:[NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
self._request = [NSMutableURLRequest requestWithURL:self.fileUrl cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0f];
NSURLConnection *cn = [NSURLConnection connectionWithRequest:self._request delegate:self];
[cn start];
}
#pragma mark - NSURLConnection Delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if([delegate respondsToSelector:#selector(downloadingStarted)])
{
[delegate performSelector:#selector(downloadingStarted)];
}
totalFileSize = [response expectedContentLength];
downloadedData = [NSMutableData dataWithCapacity:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[downloadedData appendData:data];
if([delegate respondsToSelector:#selector(downloadProgres:forObject:)])
{
[delegate performSelector:#selector(downloadProgres:forObject:) withObject:[NSNumber numberWithFloat:([downloadedData length]/totalFileSize)] withObject:self];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if([delegate respondsToSelector:#selector(downloadingFailed:)])
{
[delegate performSelector:#selector(downloadingFailed:) withObject:self.fileUrl];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if([delegate respondsToSelector:#selector(downloadingFinishedFor:andData:)])
{
[delegate performSelector:#selector(downloadingFinishedFor:andData:) withObject:self.fileUrl withObject:self.downloadedData];
}
}
#end
Step 3 : Import file #import "FileDownloader.h" and fileDownloaderDelegate in your viewController
Step 4: Define following Delegate methods in .m file of your viewCOntroller
- (void)downloadingStarted;
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data;
- (void)downloadingFailed:(NSURL *)url;
Step 5 : Create Object of FileDownloader and set URL to Download thats it.
FileDownloader *objDownloader = [[FileDownloader alloc] init];
[objDownloader setDelegate:self];
[objDownloader downloadFromURL:#"Your PDF Path URL here];
Step 6 : Save your file where you want in
- (void)downloadingFinishedFor:(NSURL *)url andData:(NSData *)data; method.
It appears that your remoteFileLocation parameter value is really an NSURL object and not an NSString. Double check how you get/create remoteFileLocation and verify it really is an NSString.
There are also several other issues with this code. The proper way to create a path to the Documents directory is as follows:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = paths[0];
NSString *localFilePath = [resourcePathDoc stringByAppendingPathComponent:fileName];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:localFilePath];
if (!fileExists) {
NSURL *url = [NSURL URLWithString:remoteFileLocation];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
//Write the data to the local file
[data writeToFile:localFilePath atomically:YES];
}
GCD can be used for massive files. You can download the file synchronous on a second thread and post back to the main thread if you like. You can also use Operation queues.
You can indeed also use the delegate method from NSURLConnection allowing you to handle the callbacks on the main thread. It is however obsolete to define your own delegate since you can just implement the delegate from NSURLConnection itself.
Related
This is my code to download the pdf files using NSUrlConnection and i downloading them to the document directories.
-(void)startDownloadFile:(UIButton *)sender
{
ResourceTelephones * resource = [array objectAtIndex:sender.tag];
NSString * currentURL = resource.website;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:currentURL]];
NSURLConnection *conn = [[NSURLConnection alloc] init];
(void)[conn initWithRequest:request delegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:currentURL]];
dispatch_sync(dispatch_get_main_queue(), ^{
});
NSString * filePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:[NSString stringWithFormat:#"myPDF%ld.pdf",(long)sender.tag]];
NSLog(#"PDF path: %#",filePath);
[data writeToFile:filePath atomically:YES];
});
}
and am viewing the file using below code
-(void)viewAction:(UIButton *)sender
{
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [searchPaths objectAtIndex:0];
NSURL *url = [NSURL fileURLWithPath:[documentPath stringByAppendingPathComponent:[NSString stringWithFormat:#"myPDF%ld.pdf",(long)sender.tag]]];
if (url) {
// Initialize Document Interaction Controller
_documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:url];
// Configure Document Interaction Controller
[_documentInteractionController setDelegate:self];
// Preview PDF
[_documentInteractionController presentPreviewAnimated:YES];
}
}
now i want to put the progress bar for each cell corresponding to each pdf file for downloading..and after downloading the file..i want to change the name of the button "download" to "view" and change the corresponding action..and one more thing i should able to view the file after re opening the application because it is already downloaded..
For progress bar updates, use NSURLConnection delegates,
Example:
(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//here you will get total length
totalLength = [response expectedContentLength];
}
(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// here calculate the downloaded length
[self.pdfData appendData:data];
//calculate downloaded percent
progress = ([self.pdfData length] * 100) / totalLength;
//and update the progress bar
}
(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//save file here
//flip the corresponding button to view here
}
And to view the downloaded pdf file, check the file is already downloaded
Example:
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
// show the view button
}
else
//start download
To calculate the progress of downloading using NSURLconnection use the following delegate function.
-(void) connection:(NSURLConnection *) connection
didReceiveResponse:(NSURLResponse *) response
{
//In this function you will get the total size of PDF data you are receiving
fTotalSize=(float)[response expectedContentLength];
if(!datWebServiceData)
datWebServiceData =[[NSMutableData alloc]init];
//datWebServiceData = [[NSMutableData data] retain];
[datWebServiceData setLength: 0];
}
-(void) connection:(NSURLConnection *) connection
didReceiveData:(NSData *) data
{
//Here calculate the progress
float val=(((float) [datWebServiceData length] / (float) fTotalSize))*100;
lblProgress.text=[NSString stringWithFormat:#"%0.0f%%",val];
}
lblProgress.text will show the progress of downloading and datWebServiceData is the NSData which will contain the whole data of PDF.
Now to show progress for each cell, save the connection object in NSMutableArray. Then check for the connection object of delegate and all connection objection in array, if they matched then take the array index and then update the entry of that cell accordingly.
firstly create a property of UIButton as below-
#property (strong,nonatomic) IBOutlet UIButton *btn;
Then make the connectivity of it. Also in viewDidLoad method do as follow-
[_btn setTitle:#"DOWNLOADING" forState:UIControlStateNormal];
[_btn addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
Therefore ,in your startDownloadFile method add following below-
NSLog(#"PDF path: %#",filePath);
[data writeToFile:filePath atomically:YES];
/* ADD BELOW LINE */
[_btn setTitle:#"VIEW" forState:UIControlStateNormal];
Then for buttonClicked: method as-
-(void)buttonClicked:(UIButton *)sender
{
if([sender.titleLabel.text isEqualToString:#"VIEW"])
{
/* Now as title is VIEW so call viewAction method */
[self viewAction:sender]
}
else
{
/*When tapped in DOWNLOADING mode nothing will happen */
}
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 8 years ago.
Improve this question
I have a same set of coding that have to be used in different view controller.what I have to do, to avoid duplication of coding in every view controller.I couldn't find the exact solution in google.Can any one help me please.
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
//NSLog(#"%d",rowno);
NSString *urlString=[NSString stringWithFormat:#"http://www.tranzlogix.com/tranzlogix_webservice/vehiclelist.php?format=json"];
NSURL *url=[NSURL URLWithString:urlString];
NSData *data=[NSData dataWithContentsOfURL:url];
NSError *error;
//NSLog(#"%#",data);
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
//NSLog(#"%#",json);
results = [json valueForKey:#"posts"];
//NSLog(#"%#", results);
//NSLog(#"Count %d", results.count);
NSArray *res = [results valueForKey:#"post"];
//NSLog(#"%#", res);
Vehicle_No=[res valueForKey:#"vehicle_no"];
//NSLog(#"%#", Vehicle_No);
Vehicle_No_Org =[Vehicle_No objectAtIndex:rowno];
NSString *CellText=[NSString stringWithFormat:#"%#",Vehicle_No_Org];
//NSLog(#"%#",CellText);
//MAP VIEW WebService
NSString *urlMapString=[NSString stringWithFormat:#"http://www.tranzlogix.com/tranzlogix_webservice/map.php?format=json&truckno=%#",CellText];
//NSLog(#"%#",urlMapString);
NSURL *urlMap=[NSURL URLWithString:urlMapString];
NSData *dataMap=[NSData dataWithContentsOfURL:urlMap];
NSError *errorMap;
//NSLog(#"%#",dataMap);
NSDictionary *jsonMap = [NSJSONSerialization JSONObjectWithData:dataMap options:kNilOptions error:&errorMap];
//NSLog(#"%#",jsonMap);
NSArray *resultsMap = [jsonMap valueForKey:#"posts"];
NSLog(#"%#", resultsMap);
//NSLog(#"Count %d", resultsMap.count);
NSArray *resMap = [resultsMap valueForKey:#"post"];
//NSLog(#"%#", resultsMap);
NSArray *latitudeString=[resMap valueForKey:#"latitude"];
NSLog(#"%#", latitudeString);
NSString *latOrgstring = [latitudeString objectAtIndex:0];
NSLog(#"%#", latOrgstring);
double latitude=[latOrgstring doubleValue];
//NSLog(#"latdouble: %f", latitude);
NSArray *longitudeString=[resMap valueForKey:#"longitude"];
NSLog(#"%#", longitudeString);
NSString *longOrgstring = [longitudeString objectAtIndex:0];
NSLog(#"%#", longOrgstring);
double longitude=[longOrgstring doubleValue];
NSLog(#"latdouble: %f", longitude);
This is what i need in more than two view controller one in map view and next in table view...
Create a base view controller with your main code and create subclasses of it.
For example, your main view controller would be:
#interface MainViewController : UIViewController
And then subclass it:
#interface OneViewController : MainViewController
Those subclasses will inherit the code.
Here you can use Custom Delegates in ios..
just create Custom Delegate(Protocol) in AppDelegate or Singleton Class.Here im using AppDelegate
#import <UIKit/UIKit.h>
**AppDeleagte.h**
#protocol parserDelegate <NSObject>
-(void)sendDataToCorrespondingViewController:(id)data andServiceName:(NSString *)serviceName;
#end
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
}
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic,strong) id <parserDelegate> delegate;
-(void)getMethodURL:(NSString *)urlString andServiceName:(NSString *)serviceName;
#end
**AppDeleagte.m**
//Implementing getMethod() in AppDelegate.m File
-(void)getMethodURL:(NSString *)urlString andServiceName:(NSString *)serviceName;
{
NSURL *url =[NSURL URLWithString:urlString];
NSError *error;
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 2), ^{
NSData *data =[[NSData alloc]initWithContentsOfURL:url];
if (data == nil) {
NSLog(#"error bcz data is nil:%#",error.localizedDescription);
}
else
{
NSError *error;
id response =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
[self.delegate sendDataToCorrespondingViewController:response andServiceName:serviceName];
}
});
}
}
Now import AppDelegate in required ViewController and Create instance For AppDelegate
**ViewController.h**
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#interface ViewController : UIViewController<parserDelegate>
{
}
#property (nonatomic,strong) AppDelegate *appDelegate;
#end
**ViewController.m**
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize appDelegate;
- (void)viewDidLoad
{
self.appDelegate =(AppDelegate *)[[UIApplication sharedApplication] delegate];
self.appDelegate.delegate=self;
[super viewDidLoad];
}
-(IBAction)getSeviceData:(id)sender
{
//call AppDeleagte method for Webservice
[self.appDelegate getMethodURL:#"http://www.tranzlogix.com/tranzlogix_webservice/vehiclelist.php?format=json" andServiceName:#"VehicleList"];
}
//implement Deleagte method
-(void)sendDataToCorrespondingViewController:(id)data andServiceName:(NSString *)serviceName
{
NSLog(#"response data: %#",data); //Here is the response from websevice
NSLog(#"serviceName: %#",serviceName); // Here Service name differentiate,if we call 2 webservices in ViewController
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I'm working on my diploma project, which includes an iOS client with a Core Data database and a Ruby on Rails server. I'm using RestKit for the communication between them. Currently I'm having a big issue getting the whole system to work: as I try to map a response to objects from the server, I get the following exception:
2013-02-08 22:40:43.947 App[66735:5903] *** Assertion failure in -[RKManagedObjectResponseMapperOperation performMappingWithObject:error:], ~/Repositories/App/RestKit/Code/Network/RKResponseMapperOperation.m:358
2013-02-08 23:04:30.562 App[66735:5903] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx)'
*** First throw call stack:
(0x1de9012 0x1c0ee7e 0x1de8e78 0x16a4f35 0x8f56e 0x8d520 0x1647d23 0x1647a34 0x16d4301 0x23a253f 0x23b4014 0x23a52e8 0x23a5450 0x90ac6e12 0x90aaecca)
libc++abi.dylib: terminate called throwing an exception
I'm trying to load a list (an array) of contacts from the server, which should be saved as "Users" in Core Data.
I've structured all my Core Data code in a Data Model class, like I saw in this video: http://nsscreencast.com/episodes/11-core-data-basics. Here it is:
Header file:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface AppDataModel : NSObject
+ (id)sharedDataModel;
#property (nonatomic, readonly) NSManagedObjectContext *mainContext;
#property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)modelName;
- (NSString *)pathToModel;
- (NSString *)storeFilename;
- (NSString *)pathToLocalStore;
#end
Implementation file:
#import "AppDataModel.h"
#interface AppDataModel ()
- (NSString *)documentsDirectory;
#end
#implementation AppDataModel
#synthesize managedObjectModel = _managedObjectModel;
#synthesize persistentStoreCoordinator = _persistentStoreCoordinator;
#synthesize mainContext = _mainContext;
+ (id)sharedDataModel {
static AppDataModel *__instance = nil;
if (__instance == nil) {
__instance = [[AppDataModel alloc] init];
}
return __instance;
}
- (NSString *)modelName {
return #"AppModels";
}
- (NSString *)pathToModel {
return [[NSBundle mainBundle] pathForResource:[self modelName]
ofType:#"momd"];
}
- (NSString *)storeFilename {
return [[self modelName] stringByAppendingPathExtension:#"sqlite"];
}
- (NSString *)pathToLocalStore {
return [[self documentsDirectory] stringByAppendingPathComponent:[self storeFilename]];
}
- (NSString *)documentsDirectory {
NSString *documentsDirectory = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentsDirectory = [paths objectAtIndex:0];
return documentsDirectory;
}
- (NSManagedObjectContext *)mainContext {
if (_mainContext == nil) {
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_mainContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
}
return _mainContext;
}
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel == nil) {
NSURL *storeURL = [NSURL fileURLWithPath:[self pathToModel]];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:storeURL];
}
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator == nil) {
NSLog(#"SQLITE STORE PATH: %#", [self pathToLocalStore]);
NSURL *storeURL = [NSURL fileURLWithPath:[self pathToLocalStore]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *e = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&e]) {
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:e forKey:NSUnderlyingErrorKey];
NSString *reason = #"Could not create persistent store.";
NSException *exc = [NSException exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:userInfo];
#throw exc;
}
_persistentStoreCoordinator = psc;
}
return _persistentStoreCoordinator;
}
#end
The User class is pretty straightforward, auto-generated with xCode.
Header file:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface User : NSManagedObject
#property (nonatomic, retain) NSString * email;
#property (nonatomic, retain) NSString * firstName;
#property (nonatomic, retain) NSString * lastName;
#property (nonatomic, retain) NSNumber * userID;
#end
Implementation file:
#import "User.h"
#implementation User
#dynamic email;
#dynamic firstName;
#dynamic lastName;
#dynamic userID;
#end
Just like the data model class, I have a server manager class which I use for communication:
Header file:
#import <Foundation/Foundation.h>
#import <RestKit/RestKit.h>
#import "AppServerProtocol.h"
#import "AppDataModel.h"
#interface AppServer : NSObject <AppServerDelegate>
+ (id)sharedInstance;
#property (strong, nonatomic) RKObjectManager *objectManager;
#property (strong, nonatomic) RKEntityMapping *userMapping;
#end
And implementation file:
#import "AppServer.h"
#import "User.h"
#import "Device.h"
#import "Ping.h"
#import "AppAppDelegate.h"
#interface AppServer ()
#property BOOL initialized;
#end
#implementation AppServer
+ (id)sharedInstance {
static AppServer *__instance = nil;
if (__instance == nil) {
__instance = [[AppServer alloc] init];
__instance.initialized = NO;
}
if (![__instance initialized]) {
[__instance initServer];
}
return __instance;
}
- (void)initServer {
// initialize RestKit
NSURL *baseURL = [NSURL URLWithString:#"http://localhost:3000"];
_objectManager = [RKObjectManager managerWithBaseURL:baseURL];
// enable activity indicator spinner
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
// initialize managed object store
_objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];
_userMapping = [RKEntityMapping mappingForEntityForName:#"User" inManagedObjectStore:_objectManager.managedObjectStore];
[_userMapping addAttributeMappingsFromDictionary:#{
#"email" : #"email",
#"firstName" : #"first_name",
#"lastName" : #"last_name"
}];
[_userMapping setIdentificationAttributes: #[#"userID"]];
RKResponseDescriptor *contactsResponseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:_userMapping pathPattern:#"/contacts" keyPath:nil statusCodes:nil];
[_objectManager addResponseDescriptor:contactsResponseDescriptor];
_initialized = YES;
}
// contacts
- (void)getContactsForCurrentUser {
NSString *authToken = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppAuthenticationToken"];
[_objectManager getObjectsAtPath:#"/contacts" parameters:#{#"auth_token": authToken} success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of contacts: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
}
#end
So when I open the Contacts Table View, which is set up correctly to use a fetched results controller (successfully pulling entities out of the DB), I have a dangerous refresh button, which calls the method you've just read above:
- (void)downloadContacts {
[[AppServer sharedInstance] getContactsForCurrentUser];
}
Here is the format of the response:
[
{
"created_at":"2013-01-11T14:03:57Z",
"email":"john#example.com",
"first_name":"John",
"id":2,
"last_name":"Doe",
"updated_at":"2013-02-07T10:57:16Z"
},
{
"created_at":"2013-01-11T14:03:57Z",
"email":"jane#example.com",
"first_name":"Jane",
"id":3,
"last_name":"Doe",
"updated_at":"2013-02-07T10:57:16Z"
}
]
And before the exception the console states the following:
2013-02-08 22:40:36.892 App[66735:c07] I restkit:RKLog.m:34 RestKit logging initialized...
2013-02-08 22:40:36.994 App[66735:c07] SQLITE STORE PATH: ~/Library/Application Support/iPhone Simulator/6.0/Applications/D735548F-DF42-4E13-A7EF-53DF0C5D8F3B/Documents/AppModels.sqlite
2013-02-08 22:40:37.001 App[66735:c07] Context is ready!
2013-02-08 22:40:43.920 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:154 GET 'http://localhost:3000/contacts?auth_token=s78UFMq8mCQrr12GZcyx'
2013-02-08 22:40:43.945 App[66735:c07] I restkit.network:RKHTTPRequestOperation.m:181
The line of the RestKit library, that fails before the whole exception is thrown is:
NSAssert(self.managedObjectContext, #"Unable to perform mapping: No `managedObjectContext` assigned. (Mapping response.URL = %#)", self.response.URL);
I have followed that back to the initServer method in the AppServer.m file, in which, before the method returns, the properties of the RKObjectManager class are like this: http://imgur.com/LM5ZU9m
As I have debugged, I've traced that the problem is not with the server side or the communication of the app - I can see the JSON received and deserialized into an array, but the moment it's passed to the next method which is supposed to save it to Core Data, the whole app goes kaboom because of the NSAssert of the managed object context.
Any help is greatly appreciated!
After a few days of debugging, I finally found out what went wrong: it looks like I also had to set the path to my local persistence store and generate managed object contexts for the managed object store myself.
Here's where I found the solution: https://github.com/RestKit/RestKit/issues/1221#issuecomment-13327693
I've just added a few lines in my server init method:
NSError *error = nil;
NSString *pathToPSC = [[AppDataModel sharedDataModel] pathToLocalStore];
_objectManager.managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:[[AppDataModel sharedDataModel] managedObjectModel]];
[_objectManager.managedObjectStore addSQLitePersistentStoreAtPath:pathToPSC fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
if (error != nil) {
NSLog(#"\nSerious object store error!\n");
return;
} else {
[_objectManager.managedObjectStore createManagedObjectContexts];
}
I managed to do it using this function RKApplicationDataDirectory() to get the application directory and set my database path.
// Initialize HTTPClient
NSURL *baseURL = [NSURL URLWithString:#"http://myapiaddress.com"];
AFHTTPClient* client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
//we want to work with JSON-Data
[client setDefaultHeader:#"Accept" value:RKMIMETypeJSON];
// Initialize RestKit
RKObjectManager *objectManager = [[RKObjectManager alloc] initWithHTTPClient:client];
NSURL *modelURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"NameOfMyCoreDataModel" ofType:#"momd"]];
//Iniitalize CoreData with RestKit
NSManagedObjectModel *managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] mutableCopy];
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
NSError *error = nil;
NSString *path = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"nameOfDB.sqlite"];
objectManager.managedObjectStore = managedObjectStore;
[objectManager.managedObjectStore addSQLitePersistentStoreAtPath:path fromSeedDatabaseAtPath:nil withConfiguration:nil options:nil error:&error];
[objectManager.managedObjectStore createManagedObjectContexts];
I've been through several different tutorials trying to get this working, but they seem to gloss over some crucial steps that a beginner might not know.
I have a JSON file at a URL with name, latitude, and longitude listed. How can I import that to an array or dictionary (I don't know the difference), and then iterate over it and create a new annotation with each iteration.
IOS6, Storyboards
_ Added Code _
ViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface ViewController : UIViewController {}
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#property (nonatomic, strong) NSMutableData *downloadData;
#end
ViewController.m
#import "ViewController.h"
#import "MapViewAnnotation.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_downloadData = [NSMutableData new];
NSURL *requestURL = [NSURL URLWithString:#"OMITTED/apptest/locations.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id parsed = [NSJSONSerialization JSONObjectWithData:_downloadData options:kNilOptions error:nil];
for (NSDictionary *pointInfo in parsed)
{
NSLog([parsed objectForKey:#"name"]);
double xCoord = [(NSNumber*)[parsed objectForKey:#"lat"] doubleValue];
double yCoord = [(NSNumber*)[parsed objectForKey:#"lon"] doubleValue];
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(xCoord, yCoord);
MKPointAnnotation *point = [MKPointAnnotation new];
point.coordinate = coords;
point.title = [parsed objectForKey:#"name"];
[self.mapView addAnnotation:point]; // or whatever your map view's variable name is
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)viewDidUnload {
[super viewDidUnload];
// ARC Problem --- [_mapView release];
self.mapView = nil;
}
#end
With iOS 5 there's a JSONSerializer class that can convert the raw JSON data from your URL into an array or dictionary as appropriate.
You'll need to download the data from the server:
NSURL *requestURL = [NSURL URLWithString:#"<your url here>"];
NSURLRequest *request = [NSURLRequest requestWithURL:requestURL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
Then you'll add these delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_downloadData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
id parsed = [NSJSONSerialization JSONObjectWithData:_downloadData options:kNilOptions error:nil];
}
_downloadData is an instance variable or property of your class of type NSMutableData.
That parsed variable will contain your data from the server. It's probably an array if it's a list of points, so you can iterate through it using fast enumeration:
for (NSDictionary *pointInfo in parsed) {
double xCoord = [(NSNumber*)[parsed objectForKey:#"<key for lat coord>"] doubleValue];
double yCoord = [(NSNumber*)[parsed objectForKey:#"<key for long coord>"] doubleValue];
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(xCoord, yCoord);
MKPointAnnotation *point = [[MKPointAnnotation new] autorelease];
point.coordinate = coords;
point.title = [parsed objectForKey:#"<key for title>"];
[self.mapView addAnnotation:point]; // or whatever your map view's variable name is
}
I have an open source project on GitHub that uses the serializer with the NSCoding protocol so you can automatically create instances right from the JSON stream.
It's here.
I am using ASIHTTPRequest framework for making network calls in my iOS applications. But I don't want to use it directly in all the controllers in my application. So I thought of writing a layer around ASIHTTPRequest. My code use this layer to use ASIHTTPRequest. The benefit wil be in future I should be able to replace this framework with some other framework and my code will be unchanged just the layer would change. I want to know what should be the strategy to do so. Should I subclass my class from ASIHTTPRequest class, or implement my own class. What should be the method I should consider implementing.
Currently I am implementing it like this.
My wrapper is
MyRequestHandler.h : NSObject
#property ASIHTTPRequest *asiHttpReq;
-(void) sendAsyncGetRequest
{
self.asiRequest = [ASIHTTPRequest requestWithURL:self.url];
if(self.tag != 0){
self.asiRequest.tag = self.tag;
}
[self.asiRequest setDelegate:self];
[self.asiRequest startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request{
MyResponseObj *respone = <From request obj>
if([delegate respondsToSelector:#selector(requestFinished:)]){
[delegate performSelector:#selector(requestFinished:) withObject:response];
}
}
And in my viewcontroller I would do this:
MyViewController.h : UIViewContoller
#property MyRequestHandler *reqHandler;
-(void) fireRequest
{
NSString* requestUrl = <create URL>;
if(requestUrl){
// [self showLoadingIndicatorView];
// Proceed for request.
NSURL *url = [NSURL URLWithString:requestUrl];
reqHandler = [MyRequestHandler requestWithURL:url];
reqHandler.tag = 1000;
[reqHandler setDelegate:self];
[reqHandler sendAsyncGetRequest];
}
}
- (void)requestFinished:(MyResponse*) responseData{
// Do Your parsing n all here.
}
- (void)requestFailed:(MyResponse*) responseData{
// Handle the error here.
}
Is this the right way to do it. The problem here is as I have created property of myrequesthandler in viewcontroller I can only make one request at a time, and loosing the capability of ASIHTTPRequest of making multiple request simultaneously.
Can you suggest me how to approach problems like this.
This is what I'm using:
#import "ASIFormDataRequest.h"
#interface RequestPerformer : NSObject {
id localCopy; // to avoid exec_bad_access with arc
ASIHTTPRequest *getRequest;
ASIFormDataRequest *postRequest;
}
#property (nonatomic, retain) id delegate;
#property (nonatomic, readwrite) SEL callback;
#property (nonatomic, readwrite) SEL errorCallback;
- (void)performGetRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector;
- (void)performPostRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary dataDictionary:(NSDictionary *)dataDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector;
#end
//
#import "RequestPerformer.h"
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#implementation RequestPerformer
#synthesize delegate;
#synthesize callback, errorCallback;
- (void)performGetRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector {
localCopy = self;
self.delegate = requestDelegate;
self.callback = requestSelector;
self.errorCallback = errorSelector;
NSMutableString *requestStringData = [[NSMutableString alloc] init];
if (stringDictionary)
for (NSString *key in [stringDictionary allKeys])
[requestStringData appendFormat:#"%#=%#&", key, [stringDictionary objectForKey:key]];
NSString *resultString = [requestStringData substringToIndex:[requestStringData length]-1];
getRequest = [[ASIFormDataRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#?%#", string, [resultString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];
[getRequest setDelegate:self];
[getRequest setRequestMethod:#"GET"];
//NSLog(#"request url = %#", [getRequest.url absoluteString]);
[getRequest startAsynchronous];
}
- (void)performPostRequestWithString:(NSString *)string stringDictionary:(NSDictionary *)stringDictionary dataDictionary:(NSDictionary *)dataDictionary delegate:(id)requestDelegate requestSelector:(SEL)requestSelector errorSelector:(SEL)errorSelector {
localCopy = self;
self.delegate = requestDelegate;
self.callback = requestSelector;
self.errorCallback = errorSelector;
NSURL *url = [NSURL URLWithString:string];
postRequest = [[ASIFormDataRequest alloc] initWithURL:url];
[postRequest setDelegate:self];
[postRequest setRequestMethod:#"POST"];
if (stringDictionary)
for (NSString *key in [stringDictionary allKeys])
[postRequest setPostValue:[stringDictionary objectForKey:key] forKey:key];
if (dataDictionary)
for (NSString *key in [dataDictionary allKeys])
[postRequest setData:[dataDictionary objectForKey:key] forKey:key];
//NSLog(#"request url = %#", [postRequest.url absoluteString]);
[postRequest startAsynchronous];
}
#pragma mark - ASIHTTPRequest Delegate Implementation
- (void)requestFinished:(ASIHTTPRequest *)crequest {
NSString *status = [crequest responseString];
if (self.delegate && self.callback) {
if([self.delegate respondsToSelector:self.callback])
[self.delegate performSelectorOnMainThread:self.callback withObject:status waitUntilDone:YES];
else
NSLog(#"No response from delegate");
}
localCopy = nil;
}
- (void)requestFailed:(ASIHTTPRequest *)crequest {
if (self.delegate && self.errorCallback) {
if([self.delegate respondsToSelector:self.errorCallback])
[self.delegate performSelectorOnMainThread:self.errorCallback withObject:crequest.error waitUntilDone:YES];
else
NSLog(#"No response from delegate");
}
localCopy = nil;
}
#end
To use it, just import RequestPerformer.h in your UIViewController and do smth like:
[requestManager performGetRequestWithString:tempString stringDictionary:stringDictionary dataDictionary:dataDictionary delegate:self requestSelector:#selector(requestSucceeded:) errorSelector:#selector(requestFailed:)];
Parameters:
(NSString *)string - url string, where to post your request;
(NSDictionary *)stringDictionary - dictionary, which contains all the
text information (such as name, id etc.);
(NSDictionary *)dataDictionary - dictionary, which contains all data information (such as photos, files, etc.);
(id)requestDelegate - delegate to perform selectors below;
(SEL)requestSelector - selector, which will be executed while successfully request;
(SEL)errorSelector - selector, which will be executed, while error occurred.
In above answer (demon9733), wrapper class has been created delegate property with retain. In general, delegate property should be assign to remove retain cycle.