I have an block to run to get a query set of data from a Azure Database :
[query readWithCompletion:^(NSArray *items, NSInteger totalCount, NSError *error) {
How can i get the *items and put it in a tableview ? As I cannot see this variable out of the block. I have tried to assign an external __ array in the block , but no use.
Has anyone tried to do this ?
thanks
Jason
i think you need some thing like this
[RSSParser parseRSSFeedForRequest:request success:^(NSArray *feedItems) {
self.linkArray=feedItems;//
dispatch_async(dispatch_get_main_queue(), ^{
//3
[self.tableView reloadData];
});
}
failure:^(NSError *error) { }];
The easiest way to see how this should work is to download the Quickstart application from the Windows Azure Portal after you create a Mobile Service. The quickstart is a Todo application that pulls down todo items you have added and displays them in a ListView. When you call your Mobile Service's read method, you specify a callback as seen here:
[query readWithCompletion:^(NSArray *results, NSInteger totalCount, NSError *error)
{
[self logErrorIfNotNil:error];
items = [results mutableCopy];
// Let the caller know that we finished
completion();
}];
In this method, a QSCompletionBlock called completion is called from the readWithCompletion method when it's received a response from your Mobile Service. In the Quickstart, that completion looks like this:
[self.todoService refreshDataOnSuccess:^
{
if (self.useRefreshControl == YES) {
[self.refreshControl endRefreshing];
}
[self.tableView reloadData];
}];
This then triggers the tableview to reload data. There are other methods that are part of the TableViewController class that are necessary to bind the data to the table view though so I'd highly recommend walking through the Quickstart code.
Related
In my iOS app, I am using the forecast.io API to get a weather forecast for 3 specific days. Once I get the array from all 3, I want to create an NSMutableArray and add all of those objects to it. The problem I am getting is that it is trying to create the NSMutableArray before the forecast data is retrieved. Here is what I have so far:
typedef void(^myCompletion)(BOOL);
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
[self myMethod:^(BOOL finished) {
if(finished){
NSMutableArray *allOfIt = [[NSMutableArray alloc] initWithObjects:self.weatherSaturday, self.weatherSunday, self.weatherMonday, nil];
NSLog(#"%#", allOfIt);
}
}];
}
-(void) myMethod:(myCompletion) compblock{
//do stuff
ForecastKit *forecast = [[ForecastKit alloc] initWithAPIKey:#"MY-API-KEY"];
// Request the forecast for a location at a specified time
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467475200 success:^(NSArray *saturday) {
// NSLog(#"%#", saturday);
self.weatherSaturday = saturday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467561600 success:^(NSArray *sunday) {
// NSLog(#"%#", sunday);
self.weatherSunday = sunday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467648000 success:^(NSArray *monday) {
// NSLog(#"%#", monday);
self.weatherMonday = monday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
compblock(YES);
}
When the code is ran, it fires the NSLog for allOfIt, which shows as null, before it gets any of the forecast data. What am I missing?
The problem I am getting is that it is trying to create the NSMutableArray before the forecast data is retrieved
Yup, exactly. The problem is simply that you don't understand what "asynchronous" means. Networking takes time, and it all happens in the background. Meanwhile, your main code does not pause; it is all executed instantly.
Things, therefore, do not happen in the order in which your code is written. All three getDailyForcastForLatitude calls fire off immediately and the whole method ends. Then, slowly, one by one, in no particular order, the server calls back and the three completion handlers (the stuff in curly braces) are called.
If you want the completion handlers to be called in order, you need each getDailyForcastForLatitude call to be made in the completion handler of the getDailyForcastForLatitude call that precedes it. Or, write your code in such a way that it doesn't matter when and in what order the completion handlers come back to you.
Hi I am trying to query some files from my Parse database and I want the files to be sorted according to the updateAt time. I have the following code. The query works and the results are sorted according to my condition, but when I load the files using getDataInBackground and then add to an array. The files are not sorted and they appear to be random in the array.
So My questions are
What can I do to make sure the files in the array are in the same order as the query results?
Any way to check the files/images against the objectID in the completion block of getDataInBackground?
p.s. I don't want to use getData since I don't want it to block the main thread.
Thank you very much in advance
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
[query orderByDescending:#"updateAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *photoStacks, NSError *error)
{
if (!error) {
// The find succeeded.
for (PFObject *photoImage in photoStacks) {
PFFile *userImageFile = photoImage[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData];
// need to check object id before adding into the stack to make sure the order is right
[photoImageStacks addObject:image];
if ([photoImageStacks count] == photoStacksCount)
{
[photoPile setArray:photoImageStacks];
}
}
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
use breakpoint and trace first photoImageStacks and second after response you should call reload method if you are using tableview or some delegate or fire a notification so that you can update ui accordingly after successful response.
I am using Azure Mobile Service as a backend for an iOS application. I have set up everything to work with offline sync which allows me to view, add, or modify data even when there is no network connection. I am running into a problem when I add a new object into a table. The add works well locally but when I synchronize data it creates a duplicate item on the local database with a slightly different objectId. The created item is not duplicated on the server side.
Here's how I am setup. By the way, thanks to #TheBasicMind for posting this model.
Here's a link to his explanation of the model: enter link description here
Here's what I do to setup the sync context and sync table:
// Initialize the Mobile Service client with your URL and key
MSClient *client = self.hpc.client;
NSManagedObjectContext *context = self.hpc.syncContext;
MSCoreDataStore *store = [[MSCoreDataStore alloc] initWithManagedObjectContext:context];
client.syncContext = [[MSSyncContext alloc] initWithDelegate:syncDelegate dataSource:store callback:nil];
// Add a Mobile Service filter to enable the busy indicator
self.client = [client clientWithFilter:self];
// Create an MSSyncTable instance to allow us to work with the Athlete table
self.syncAthleteTable = [self.client syncTableWithName:#"Athlete"];
Here's how I add a record for the moment:
NSDictionary *newItem = #{#"firstname": firstname, #"lastname": lastname, #"laterality" : laterality};
[self.athletesService addItem:newItem completion:^{
NSLog(#"New athlete added");
}];
-(void)addItem:(NSDictionary *)item completion:(CompletionBlock)completion
{
// Insert the item into the Athlete table
[self.syncAthleteTable insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
The add works as expected and it is added in a UITableView as I have an NSFetchedResultsController listening on my Main Context.
Here's where the problem occurs. When I synchronize data with the server using this function:
-(void)syncData:(CompletionBlock)completion
{
// push all changes in the sync context, then pull new data
[self.client.syncContext pushWithCompletion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self pullData:completion];
}];
}
-(void)pullData:(CompletionBlock)completion
{
MSQuery *query = [self.syncAthleteTable query];
// Pulls data from the remote server into the local table.
// We're pulling all items and filtering in the view
// query ID is used for incremental sync
[self.syncAthleteTable pullWithQuery:query queryId:#"allAthletes" completion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self refreshDataOnSuccess:completion];
}];
}
- (void) refreshDataOnSuccess:(CompletionBlock)completion
{
MSQuery *query = [self.syncAthleteTable query];
[query readWithCompletion:^(MSQueryResult *results, NSError *error) {
[self logErrorIfNotNil:error];
NSLog(#"Data that pulled from local store: ");
for ( NSDictionary *dict in results.items ) {
NSLog(#"%# %#", [dict objectForKey:#"firstname"], [dict objectForKey:#"lastname"] );
}
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
After the synchronization the NSFetchedResultsChangeInsert is called a second time for the same record with a slightly different objectID. Here's an example of the first and second objectIDs:
tD7ADE77E-0ED0-4055-BAF6-B6CF8A6960AE9
tD7ADE77E-0ED0-4055-BAF6-B6CF8A6960AE11
I am stuck here.
Any help is highly appreciated. Thank you!
In the past, when I've seen this happen, its because the "id" field the client is sending was being changed or ignored by the server logic.
Locally the store finds the object in core data using that field, so a change to it could result in the client SDK thinking it needs to insert a new object and not update an existing one.
One easy way to confirm this, is by using the tableOperation:complete: method on the data delegate and comparing the "id" column between the item originally and that being returned by operation execute.
I am using Azure Mobile Service as a backend for an iOS app. I have set up everything to work with offline sync which allows me to view, add, or modify data even when there is no network connection. I am now into testing and I run into an error: "The item provided was not valid" when I try to synchronize data.
Here's what I am doing:
I add a new athlete to the syncTableWithName:#"Athlete" with this:
NSDictionary *newItem = #{#"firstname": #"Charles", #"lastname": #"Lambert", #"laterality" : #"Orthodox"};
[self.athletesService addItem:newItem completion:^{
NSLog(#"New athlete added");
}];
Here's the addItem function:
-(void)addItem:(NSDictionary *)item completion:(CompletionBlock)completion
{
// Insert the item into the Athlete table
[self.syncTable insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
For now everything is fine and the item is in the syncTable. The problem is when I try to synchronize with the Azure Mobile Service. Here's the syncData function I am calling:
-(void)syncData:(CompletionBlock)completion
{
// push all changes in the sync context, then pull new data
[self.client.syncContext pushWithCompletion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self pullData:completion];
}];
}
The pushWithCompletion gets me the error: "The item provided was not valid." and same for the pullData function that gets called after:
-(void)pullData:(CompletionBlock)completion
{
MSQuery *query = [self.syncTable query];
// Pulls data from the remote server into the local table.
// We're pulling all items and filtering in the view
// query ID is used for incremental sync
[self.syncTable pullWithQuery:query queryId:#"allAthletes" completion:^(NSError *error) {
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
I have tried inserting directly in the MSTable and that works fine. It's really when I am using the MSSyncTable that I run into this error. Although when I insert data manually in my database and that I synchronize my context I can fetch data and display within my UITableView.
Lookin forward to see what you guys think about this. Thanks a lot!
I just edited my question thanks to #phillipv.
When I add an item using NSDictionary just like I did I run into the error "The item provided was not valid". So I tried adding an item by first inserting it to my managedObjectContext and then calling:
NSDictionary *dict = [MSCoreDataStore tableItemFromManagedObject:newAthlete];
I then I get the error when I try to sync: "The item provided did not have a valid id."
I feel like I am experiencing a circle.. :S
#Charley14, you can work around the bug by adding the following handler.
- (void)tableOperation:(nonnull MSTableOperation *)operation onComplete:(nonnull MSSyncItemBlock)completion
{
NSMutableDictionary *rwItem = [NSMutableDictionary dictionaryWithDictionary:operation.item];
// Temporary workaround
[rwItem removeObjectsForKeys:#[ #"relationship1", #"relationship2"]];
operation.item = rwItem;
[operation executeWithCompletion:completion];
}
The tableOperation:onComplete: handler is simply removing keys that correspond to the relationships. You will have to replace 'relationship1', 'relationship2' in the code snippet with names of actual relationships in your application.
Once the bug (https://github.com/Azure/azure-mobile-services/issues/779) is fixed, this workaround can be removed.
This appears to be a bug in the iOS SDK, as the Many to One relationship is not supposed to be exposed in the object given to the operation during a Push call.
Created the following bug with more details on GitHub: https://github.com/Azure/azure-mobile-services/issues/779
The cause of the error message is due to the fact that the relationship is a NSSet on the object, and the NSJSONSerializer throws as it does not know how to convert that to JSON.
When I upload any data onto the Parse cloud, it stores the row at the top of the table. So every time a new row is added it gets stored at the top.
However, when I retrieve all the rows, the data is retrieved bottom up approach.
So let's say initially cloud is empty.
Push a
Cloud Looks like : a
Push b
Cloud Looks like : b a
Push c
Cloud Looks like : c b a
And now when I retrieve the data, i get it like: a b c
Now what I want is when data is inserted it is put at the 2nd location and not the first location.
Example:
Initial Cloud : "X"
Push a: "X" a
Push b: "X" b a
Push c: "X" c b a
Is there any way I can push data in Parse like this?
I'm doing because when I retrieve data, I wish to execute a method after all the data is retrieved in the background thread. So this way when I reach at X, I can call my method.
Found a solution to the problem....different approach though:
I count the number of objects for that query
Keep a counter increasing every time a record is fetched
when counter reached = total number of objects , then execute method.
NSInteger count= [query countObjects];
for (PFObject *obj in objects) {
[Names addObject:LastName];
if ([Names count] == count) {
[self getResults];
} }
^^^ solution is wrong
This way apparently does block the main thread, so there's a possibility of the app being killed.
Does anyone have any other solutions?
The data stored in the Parse Cloud is in an arbitrary order. Due to the way they currently store the data you may see new data at the end but this behaviour should not be relied upon.
If you want to retrieve data in a specific order then you should add a sorting operation to your PFQuery rather than trying to store the data in a specific order.
I am not sure what you are trying to achieve with your second code block. What does [self getResults] do?
If you want to execute some code after the results have been retrieved, why not just use findObjectsInBackgroundWithBlock?
This allows you to specify code to be executed once the data is received -
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
names = [NSMutableArray arrayWithArray:objects];
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"Object Name: %#", object.objectId);
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
If you need to update any UI from the block (or a method called within the block) then you should perform that on the main thread -
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUIWithResult:objects];
});
So, putting it together you get -
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateMyUIWithResult:objects];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];