1 - How can I include a picture using NSMutableDictionary with RestKit Client?
My server receives data from NSMutableDictionary in my code below.
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:usernameTextfield.text forKey:#"username"];
[client put:#"/main/insert" params:params delegate:self];
but how can I attach an image at the same time?
On the other hand, RKParams doesn't work for me on my server side.
RKParams *params=[[RKParams alloc]init];
imageData = UIImagePNGRepresentation(imageField.image);
[params setData:imageData MIMEType:#"image/png" forParam:#"image"];
Rest server code:
public function insert_put()
{
$username = $this->put('username');
$this->model->insertPost($username);
//Question number 2.
$data['success'] = 'added successfully';
$this->response($data, 200);
}
2 - How can I receive it from my server(php) ? I'm using put option to this.
I just do the following
- (void) upload: (UIImage *) pic onLoad:(RKObjectLoaderDidLoadObjectBlock) loadBlock onError:(RKRequestDidFailLoadWithErrorBlock)failBlock{
RKParams* imageParams = [RKParams params];
NSData* imageData = UIImageJPEGRepresentation(pic, 0.7f);
[imageParams setData:imageData MIMEType:#"image/jpg" forParam:#"FileUpload"];
NSString *resourcePath = #"/api/upload/";
RKObjectMapping *mapping = [[RKObjectManager sharedManager].mappingProvider objectMappingForClass:[KFMedia class]];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPOST;
loader.params = imageParams;
[self settingsForLoader:loader withMapping:mapping onLoad:loadBlock onError:failBlock];
}];
}
- (void) settingsForLoader: (RKObjectLoader *) loader withMapping: (RKObjectMapping *) mapping onLoad:(RKObjectLoaderDidLoadObjectBlock) loadBlock onError:(RKRequestDidFailLoadWithErrorBlock)failBlock{
loader.objectMapping = mapping;
loader.delegate = self;
loader.onDidLoadObject = loadBlock;
loader.onDidFailWithError = ^(NSError * error){
//NSLog(#"%#",error);
};
loader.onDidFailLoadWithError = failBlock;
loader.onDidLoadResponse = ^(RKResponse *response) {
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
}
Your RestKit code looks fine, I think it is your php code that is failing. RestKit will upload the image as part of a multipart/form-data. This info will not appear in your normal _PUT array like your username did. I'm not sure what frameworks you are using (Is that the REST Controller library?), but you can access the file using _FILES['image'] or whatever your server framework uses. $this->files('image') maybe?
EDIT:
To create a mixed param file try this:
NSDictionary* strings = [NSDictionary dictionaryWithObjectsAndKeys: usernameTextfield.text, #"username", nil];
NSData* image = UIImagePNGRepresentation(imageView.image);
RKParams* params = [[RKParams alloc] initWithDictionary: strings];
[params setData: image MIMEType: #"image/png" withParameter: #"image"];
//And send it
[client put:#"/main/insert" params:params delegate:self];
But now you can not use $this->put, but instead $this->upload. For example:
$data = $this->upload->data();
$username = $data['username'];
$imagefile = $data['image'];
Related
Problem
I've been trying to post to the server with a multiform request that includes an image attachment. I haven't had trouble getting the image to the server, it is the other information that is not sending correctly.
Details
I'm using object mapping to configure several different attributes when receiving objects from the server:
//Using a custom class to map object I receive to
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"user", #"participants", #"tags", #"text", #"kind", #"video", #"location", nil];
[memoryMapper mapKeyPath:#"_id" toAttribute:#"memoryID"];
//MediaMapper handles the data needed for the Image attachments
RKObjectMapping * mediaMapper = [RKObjectMapping mappingForClass:[MemoMedia class]];
[mediaMapper mapKeyPath:#"processed" toAttribute:#"processed"];
[mediaMapper mapKeyPath:#"original" toAttribute:#"original"];
[mediaMapper mapKeyPath:#"mime" toAttribute:#"mimeType"];
[memoryMapper mapKeyPath:#"media" toRelationship:#"rawMedia" withMapping:mediaMapper];
//
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memories"];
[RKObjectManager sharedManager].serializationMIMEType = RKMIMETypeFormURLEncoded;
[RKObjectManager sharedManager].acceptMIMEType = RKMIMETypeJSON;
Then, when it comes time to post a photo I update configurations as follows:
RKObjectMapping * memoryMapper = [RKObjectMapping mappingForClass:[MemoContent class]];
[memoryMapper mapAttributes:#"created", #"participants", nil];
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:memoryMapper forClass:[MemoContent class]];
[[RKObjectManager sharedManager].mappingProvider setMapping:memoryMapper forKeyPath:#"memory"];
Participants are people tagged with the photo. Here is how I'm posting it, similar to this https://github.com/RestKit/RestKit/wiki/Attach-a-File-to-an-RKObjectLoader
[[RKObjectManager sharedManager] postObject:theMemory usingBlock:^(RKObjectLoader * loader){
RKObjectMapping* serializationMapping = [[[RKObjectManager sharedManager] mappingProvider] serializationMappingForClass:[MemoContent class]];
NSLog(#"serializationMapping: %#", serializationMapping);
loader.delegate = APP; //main app delegate posting, updating
NSError* error = nil;
RKObjectSerializer * serializer = [[RKObjectSerializer alloc] initWithObject:theMemory mapping:serializationMapping];
NSDictionary * dictionary = [serializer serializedObject:&error];
RKParams * params = [RKParams paramsWithDictionary:dictionary];
NSData * imageData = UIImagePNGRepresentation(theMemory.photo); //image data
[params setData:imageData MIMEType:#"image/png" forParam:#"attachment"];
loader.params = params;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
}];
The server is receiving the image as planned, and actually does receive the 'created' and 'participants' unfortunately it's in a strange format that the server doesn't understand. It includes line breaks and such participants (\n 19843589323 \n created: \n 3-31-2012 00:00 (something like that, I will update when I have access to the logs.
I will give you any extra info you need. Would offer reputation for it if I had enough to do so ;)
In RestKit 0.20.0-pre3, RKObjectManager does have method multipartFormRequestWithObject:method:path:parameters:constructingBodyWithBlock:
An example of this task can be found at the RestKit Github page:
Article *article = [Article new];
UIImage *image = [UIImage imageNamed:#"some_image.png"];
// Serialize the Article attributes then attach a file
NSMutableURLRequest *request = [[RKObjectManager sharedManager] multipartFormRequestWithObject:article method:RKRequestMethodPOST path:nil parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:UIImagePNGRepresentation(image)
name:#"article[image]"
fileName:#"photo.png"
mimeType:#"image/png"];
}];
RKObjectRequestOperation *operation = [[RKObjectManager sharedManager] objectRequestOperationWithRequest:request success:nil failure:nil];
[[RKObjectManager sharedManager] enqueueObjectRequestOperation:operation]; // NOTE: Must be enqueued rather than started
It won't let me attached the params to the request, what am I doing wrong? Params is a Dictionary and endString adds to the sharedClient baseURL.
[[RKClient sharedClient] get:endString usingBlock:^(RKRequest *loader){
loader.params = [RKParams paramsWithDictionary:params];
loader.onDidLoadResponse = ^(RKResponse *response) {
[self parseJSONDictFromResponse:response];
};
loader.onDidFailLoadWithError = ^(NSError *error) {
NSLog(#"error2:%#",error);
};
}];
I get this error:RestKit was asked to retransmit a new body stream for a request. Possible connection error or authentication challenge?
I think you are on the right track. Below is from a working example I found here, about 2/3 the way down the page. Another option for you may be to append the params directly to the URL. I'm not sure if that's feasible for you, but if your parameters are simple then it may be.
- (void)authenticateWithLogin:(NSString *)login password:(NSString *)password onLoad:(RKRequestDidLoadResponseBlock)loadBlock onFail:(RKRequestDidFailLoadWithErrorBlock)failBlock
{
[[RKClient sharedClient] post:#"/login" usingBlock:^(RKRequest *request) {
request.params = [NSDictionary dictionaryWithKeysAndObjects:
#"employee[email]", login,
#"employee[password]", password,
nil];
request.onDidLoadResponse = ^(RKResponse *response) {
id parsedResponse = [response parsedBody:NULL];
NSString *token = [parsedResponse valueForKey:#"authentication_token"];
//NSLog(#"response: [%#] %#", [parsedResponse class], parsedResponse);
if (token.length > 0) {
NSLog(#"response status: %d, token: %#", response.statusCode, token);
[[RKClient sharedClient] setValue:token forHTTPHeaderField:#"X-Rabatme-Auth-Token"];
if (loadBlock) loadBlock(response);
}
[self fireErrorBlock:failBlock onErrorInResponse:response];
};
request.onDidFailLoadWithError = failBlock;
}];
}
You should also take a look at this SO question: RestKit GET query parameters.
I am using the Google Directions iOS API. I am getting the data using JSON instead of XML. But I am using AFNetworking to simplify this for me. AFNetworking is available at github. Right now, I am able to display an overlay route from one location to another on an MKMapView. Here is my code:
// AFNETWORKING ==========================================================
AFHTTPClient *_httpClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://maps.googleapis.com/"]];
[_httpClient registerHTTPOperationClass: [AFJSONRequestOperation class]];
[_httpClient setDefaultHeader:#"Accept" value:#"application/json"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
[parameters setObject:[NSString stringWithFormat:#"%f,%f", location.coordinate.latitude, location.coordinate.longitude] forKey:#"origin"];
[parameters setObject:[NSString stringWithFormat:#"%f,%f", location2.coordinate.latitude, location2.coordinate.longitude] forKey:#"destination"];
[parameters setObject:#"false" forKey:#"sensor"];
[parameters setObject:#"driving" forKey:#"mode"];
[parameters setObject:#"metric" forKey: #"units"];
NSMutableURLRequest *request = [_httpClient requestWithMethod:#"GET" path: #"maps/api/directions/json" parameters:parameters];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
AFHTTPRequestOperation *operation = [_httpClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSInteger statusCode = operation.response.statusCode;
if (statusCode == 200) {
[self parseResponse:responseObject];
} else {
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
[_httpClient enqueueHTTPRequestOperation:operation];
// ROUTE SETUP AND RESPONSE RECIEVED METHOD ==========================================================
- (void)parseResponse:(NSDictionary *)response {
NSArray *routes = [response objectForKey:#"routes"];
NSDictionary *routePath = [routes lastObject];
if (routePath) {
NSString *overviewPolyline = [[routePath objectForKey: #"overview_polyline"] objectForKey:#"points"];
NSLog(#"Status: %#", [response objectForKey: #"status"]);
NSLog(#"Legs: %#", [routePath objectForKey: #"legs[]"]);
_path = [self decodePolyLine:overviewPolyline];
NSInteger numberOfSteps = _path.count;
CLLocationCoordinate2D coordinates[numberOfSteps];
for (NSInteger index = 0; index < numberOfSteps; index++) {
CLLocation *location = [_path objectAtIndex:index];
CLLocationCoordinate2D coordinate = location.coordinate;
coordinates[index] = coordinate;
}
polyLine = [MKPolyline polylineWithCoordinates:coordinates count:numberOfSteps];
[self.mapView addOverlay:polyLine];
}
}
This is the main code to get the route functioning. And it works well! But now what I want to do is get direction list and complete duration and summary. So I took a deep look at Google Directions API Documentation and it tells me to use its different dictionaries and arrays and objects. But I have had no luck. I get a status of OK. When I log the count of the routes array, its only one object. Then the MOST important, legs[] array is NULL.
NSLog(#"Legs is: %#", [routePath objectForKey: #"legs[]"]);
Output:
Legs is (null)
Legs[] includes everything important like the direction list and duration. Summary is not NULL, it gives the name of one of the streets that the route bypasses. I dont know how that is a summary. Might be because there is only one object in the routes array. waypoint_order and warnings[] are NULL too. bounds and of course overview_polyline are valid, thats how I got the route working.
So whats the problem here? Why are so many of the objects Google Directions API provides NULL when I need them?
Thanks!
Alright, the problem was that the response for directions was taking too long. I expected the legs[] response to instantly deliver along with the overview_polyline. So I just had to wait till the response came in, this gives me an opportunity to insert a buffer UI.
I just installed the framework restkit 0.9.3 and followed the Discussion Board example. Well, everything just worked great, however when I tried to use Core Data my User NSManagedObject class is duplicating even after declaring his primaryKeyAttribute (userID). For example, when I send a login request to my web-server, I return {"user":{"id":1, "username":"teste", ...}} .. but it seems to create a new row every time it invoques objectLoader:didLoadObjects.
User table:
Example code:
~ AppDelegate.m didFinishLaunching
RKManagedObjectMapping* userMapping = [RKManagedObjectMapping mappingForClass:[User class]];
userMapping.primaryKeyAttribute = #"userID";
userMapping.setDefaultValueForMissingAttributes = YES; // clear out any missing attributes (token on logout)
[userMapping mapKeyPathsToAttributes:
#"id", #"userID",
#"email", #"email",
#"username", #"username",
#"password", #"password",
nil];
[objectManager.mappingProvider registerMapping:userMapping withRootKeyPath:#"user"];
~ User.m loginWithDelegate
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
~ User.m didLoadObjects (RKObjectLoaderDelegate)
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray *)objects {
if ([objectLoader wasSentToResourcePath:#"/login"]) {
[self loginWasSuccessful];
}
NSLog(#"number of user rows: %i", [User findAll].count);
}
What am I doing wrong?
Are you correctly implementing RKManagedObjectCache? For debugging I had it simply return nil and forgot about that. A little while later I found I had duplicates also.
The cache works by fetching local objects and comparing with server returned objects. Any local objects that are not in the server response will be deleted. In earlier versions it used a fetch request but in newer versions you must manually perform the request and return actual objects.
If you return nil, it thinks this object is not in your cache and will add a duplicate. Try implementing this method:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
For example:
+ (NSManagedObject *)findInstanceOfEntity:(NSEntityDescription *)entity
withPrimaryKeyAttribute:(NSString *)primaryKeyAttribute
value:(id)primaryKeyValue
inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext {
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
[request setFetchLimit: 1];
[request setPredicate:[NSPredicate predicateWithFormat:#"%K = %#", primaryKeyAttribute, primaryKeyValue]];
NSArray *results = [NSManagedObject executeFetchRequest:request inContext: managedObjectContext];
if ([results count] == 0)
{
return nil;
}
return [results objectAtIndex:0];
}
I have found the problem related to targetObject (RKObjectLoader)
/**
* The target object to map results back onto. If nil, a new object instance
* for the appropriate mapping will be created. If not nil, the results will
* be used to update the targetObject's attributes and relationships.
*/
So when I set it to nil the postObject calls findOrCreateInstanceOfEntity:withPrimaryKeyAttribute:andValue
- (void)loginWithDelegate:(NSObject<UserAuthenticationDelegate>*)delegate {
_delegate = delegate;
[[RKObjectManager sharedManager] postObject:self delegate:self block:^(RKObjectLoader* loader) {
loader.resourcePath = #"/login";
loader.targetObject = nil;
loader.serializationMapping = [RKObjectMapping serializationMappingWithBlock:^(RKObjectMapping* mapping) {
[mapping mapAttributes:#"username", #"password", nil];
}];
}];
}
As of the latest RESTKit version (0.23.2) you can define the primary key like this:
[_mapping addAttributeMappingsFromDictionary:#{ #"id" : #"objectId", #"name" : #"name" }];
[_mapping setIdentificationAttributes:#[ #"objectId" ]];
Whereas objectId is you primary key on the core data object.
I'm afraid I'm a newbie to objective-c programming, and I am having a problem that I have spent all day trying to figure out and I cannot, so I am humbly asking you guys if anyone can help.
I am trying to read the details from a JSON page online (for instance a local services directory) and have installed the JSON library into Xcode and it seems to work fine. I'm developing for the iPhone by the way, and have the latest versions all installed.
The problem is, what with me being a newb and all, I seem unable to retrieve all the information I need from the JSON file.
the JSON data I am testing with is this:
"testlocal_response" = {
header = {
query = {
business = newsagent;
location = brighton;
page = 1;
"per_page" = 1;
"query_path" = "business/index";
};
status = ok;
};
records = (
{
address1 = "749 Rwlqsmuwgj Jcyv";
address2 = "<null>";
"average_rating" = 0;
"business_id" = 4361366;
"business_keywords" = "<null>";
"business_name" = "Gloucester Newsagents";
"data_provider_id" = "<null>";
"display_details" = "<null>";
"distance_in_miles" = "0.08";
fax = "<null>";
gridx = "169026.3";
gridy = "643455.7";
"image_url" = "<null>";
latitude = "50.82718";
"logo_path" = Deprecated;
longitude = "-0.13963";
phone = 97204438976;
postcode = "IY1 6CC";
"reviews_count" = 0;
"short_description" = "<null>";
"touch_url" = "http://www.test.com/business/list/bid/4361366";
town = BRIGHTON;
url = "<null>";
}
);
};
}
Now, in my code ViewController.m page, in the 'connectionDidFinishLoading' area I have added:
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
// make sure JSON has all been pulled in
NSLog(#"This is from the JSON page:\n");
NSLog(responseString);
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
// using either of these seems to make no difference?
NSDictionary *touchDirect = [json objectWithString:responseString error:&error];
//NSArray *touchDirect = [json objectWithString:responseString error:&error];
[responseString release];
if (touchDirect == nil)
label.text = [NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]];
else {
NSMutableString *text = [NSMutableString stringWithString:#"Test directory details:\n"];
[text appendFormat:#"%#\n", [[[[touchDirect objectForKey:#"testlocal_response"] objectForKey:#"header"] objectForKey:#"query"] objectForKey:#"business"]];
label.text = text;
Testing this I get the value for the business ('newsagent') returned, or location ('brighton') which is the correct. My problem is, I cannot go further into the JSON. I don't know how to pull out the result for the actual 'records' which, in the test example there is only one of but can be more divided using brackets '(' and ')'.
As soon as I try to access the data in these record areas (such as 'address1' or 'latitude' or 'postcode') it fails and tells me 'unrecognized selector sent to instance'
PLEASE can anyone tell me what I'm doing wrong?! I've tried so many different things and just cant get any further! I've read all sorts of different things online but nothing seems to help me.
Any replies deeply appreciated. I posted up a question on iPhone SDK too but havent had a useful response yet.
many thanks,
-Robsa
Have you validated your JSON?
It's not clear how you are trying to access the objects that you say are erroring. The specific line(s) you are having trouble with would be helpful.
It's usually easier to set a pointer to the dictionary you are going to be accessing for readability..
NSDictionary *records = [[objectForKey:#"testlocal_response"] objectForKey#"records"];
then...
NSString *businessName = [records objectForKey:#"business_name"];
float latitude = [[records objectForKey:#"latitude"] floatValue];
Well, I finally sorted it out! I put the records in an NSString, which I could then access using objectAtIndex.
Here is the main Viewcontroller.m code for it:
- (void)viewDidLoad {
[super viewDidLoad];
responseData = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"*URL TO JSON DATA HERE*"]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableArray *touchDirect = [json objectWithString:responseString error:&error];
NSString *touchRecord = [[touchDirect objectForKey:#"touchlocal_response"] objectForKey:#"records"];
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
label.text = [NSString stringWithFormat:#"Connection failed: %#", [error description]];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJSON *json = [[SBJSON new] autorelease];
//Retrieves the JSON header Data, returning info such as 'status' [which should return 'ok']
NSMutableArray *touchDirect = [json objectWithString:responseString error:&error];
//Puts the records returned into a string called touchRecord
NSString *touchRecord = [[touchDirect objectForKey:#"touchlocal_response"] objectForKey:#"records"];
[responseString release];
// test results
[text appendFormat:#" Address: %#\n", [[touchRecord objectAtIndex:0] objectForKey:#"address1"]];
[text appendFormat:#" Phone: %#\n", [[touchRecord objectAtIndex:0] objectForKey:#"phone"]];
[text appendFormat:#" Address Data record 2: %#\n", [[touchRecord objectAtIndex:1] objectForKey:#"address1"]];
[text appendFormat:#" Phone Data record 2: %#\n", [[touchRecord objectAtIndex:1] objectForKey:#"phone"]];
This now seems to work fine. I also have a if..else if statement to catch errors now. Does this code look Ok?
Thanks for the tip, Nick - I was just trying to get the output right before tidying the code up. I am using an NSMutableArray to put my JSON into initially, is this OK? What is the benefit of putting it into an NSDictionary?
regards,
Robsa