I want to remove all rows in my table view before reloading the data, but can't seem to get it to work.
In my viewcontroller I get my array from this AFNetworking request.
- (void)viewDidLoad
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
self.location_results = location_results;
[self.tableView reloadData];
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
And I want to remove data then reload it with this button
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self viewDidLoad];
NSMutableArray *location_results = [NSMutableArray array];
[location_results removeAllObjects];
[self.tableView reloadData];
}
But it's not removing the rows. I see the reload happening over the top of the rows that are already there. Any suggestions?
Please DON'T EVER call viewDidLoad manually.
Create a method like
- (void)reloadDataWithCompletion:(void(^)(NSArray *locations))completion
failure:(void(^)(NSError *error))failure {
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
if(completion)
completion(location_results);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure)
failure(error);
}];
}
And call it whenever you need to reload the data
- (void)viewDidLoad {
[super viewDidLoad]; // Don't forget the call to super!
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
In order to achieve a graphical effect for reloading the table you can do (assuming that you have only one section), substitute
[self.tableView reloadData];
with
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
Related
I'm using MagicalRecord and I can not understand with and retrieve the id of the record you just saved
Items *item = [Items MR_createEntity];
item.ref_user = ref_user;
[self saveContext];
- (void)saveContext {
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
DDLogInfo(#"MR_saveContext success");
[self loadView];
[self viewDidLoad];
} else if (error) {
DDLogError(#"Error saving context: %#", error.description);
}
}];
}
Why not just create it out of block?
item = [ITEM MR_createEntityInContext:defaultContext];
[defaultContext MR_saveToPersistentStoreAndWait];
// you can retrieve the id of above item here
I have three API's I pull data from, and put into a UITableView inside of my ViewController.m.
Is there a way to still let the UITableView load if one of the websites isn't loading?
Right now, the ViewController.m just doesn't load if all 3 sources aren't loading per my method.
Here's the method I use:
- (void)loadOneWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:#"tNE%#", bn]];
NSString *path = [NSString stringWithFormat:#"xx/%#/", tNE];
[self.eObjectManager getObjectsAtPath:path parameters:nil success:success failure:failure];
}
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
// Here's the #1
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API2 here. Doing so will serialize these two requests
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
// Here's the trick. call API3 here. Doing so will serialize these two requests
[self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
[self sortCombinedModel];
[self.tableView reloadData];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
}];
}
So if API1 doesn't load, API2 and API3 will still load and show in the UITableView in ViewController.m.
Maybe you can try something like this, first define tree bool variables: finish1, finish2 and finish3
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
[self loadOneWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish1 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish1 = true;
[self reloadTableData]
}];
[self loadTwoWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish2 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish2 = true;
[self reloadTableData]
}];
[self loadThreeWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
finish2 = true;
[self reloadTableData]
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"No?: %#", error);
finish3 = true;
[self reloadTableData]
}];
}
- (void) reloadTableData {
if (finish1 && finish2 && finish3) {
[self sortCombinedModel];
[self.tableView reloadData];
}
}
The loadOne, loadTwo ... functions have a disadvantage which is that they take two block parameters, one for success and one for fail. If you change those to take a single block that handles success or failure, it will be much easier to carry on after errors occur.
EDIT Change how you call your eObjectManager by not directly passing on the completion and failure blocks. Instead, implement those blocks and rearrange the params to match the single block interface...
- (void)betterLoadOneWithCompletion:(void (^)(RKObjectRequestOperation*, RKMappingResult*, NSError *))completion {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *tNE = [defaults objectForKey:[NSString stringWithFormat:#"tNE%#", bn]];
NSString *path = [NSString stringWithFormat:#"xx/%#/", tNE];
[self.eObjectManager getObjectsAtPath:path parameters:nil success:^(RKObjectRequestOperation *op, RKMappingResult *map) {
// success! pass the operation, map result and no error
completion(op, map, nil);
} failure:^(RKObjectRequestOperation *op, NSError *error) {
// fail. pass the operation, no result and the error
completion(op, nil, error);
}];
}
It can still call your old function or some external library with two blocks, but it combines the result into a single block. The caller of this expects that they will either get a good RKMappingResult and a nil NSError, or a nil for the result parameter and an instance of an error. With this api, we can easily fix your method to just log errors as they occur and carry on, error or not...
- (void)loadMedia {
self.combinedModel = [NSMutableArray array];
// changed the loadOneWithCompletion signature to take just a single block, calling it on success or fail
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// if it worked, handle the results
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
// if it didn't work, log the error, but execution continues
NSLog(#"No?: %#", error);
}
// even if it didn't work, we can keep going...
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// same - handle results
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
// same - log the error if there is one
NSLog(#"No?: %#", error);
}
// same - log the error and keep going
[self betterLoadOneWithCompletion:^(RKObjectRequestOperation *op, RKMappingResult *mappingResult, NSError *error) {
// same...
if (!error) {
[self.combinedModel addObjectsFromArray:mappingResult.array];
} else {
NSLog(#"No?: %#", error);
}
[self sortCombinedModel];
[self.tableView reloadData];
}];
}];
}];
}
I'm trying to create a simple weather app which gets data from OpenweatherMap using JSON and print them out in a UITableView. However, when I executed this code below and set a breakpoint at numberOfRowsInSection method, it returns 0 row. Somehow the viewDidLoad method was called after the numberOfRowsInSection method, that's why the threeHoursForecast array is empty. Can anyone help me on this please?
static NSString * const BaseURLString = #"http://api.openweathermap.org/data/2.5/forecast?q=Houston";
#interface HPViewController ()
#end
#implementation HPViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.threeHoursForecast = [[NSMutableArray alloc] init];
self.tableView.dataSource = self;
self.tableView.delegate = self;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:BaseURLString parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *data = [responseObject objectForKey:#"list"];
for (NSDictionary *forecastPerThreeHours in data)
[self.threeHoursForecast addObject:[[forecastPerThreeHours valueForKey:#"main"] valueForKey:#"temp"]];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.threeHoursForecast count];
}
You can reload data after the completion handler is called, which will solve the problem.
[manager POST:BaseURLString parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *data = [responseObject objectForKey:#"list"];
for (NSDictionary *forecastPerThreeHours in data)
[self.threeHoursForecast addObject:[[forecastPerThreeHours valueForKey:#"main"] valueForKey:#"temp"]];
//Add this line
[self.TableView reloadData];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
The post method is async. You need to add [self.tableView reloadData]; in succes block of request.
manager POST: is asynchronous call, so you need to reload data after fetching JSON.
[manager POST:BaseURLString parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *data = [responseObject objectForKey:#"list"];
for (NSDictionary *forecastPerThreeHours in data)
[self.threeHoursForecast addObject:[[forecastPerThreeHours valueForKey:#"main"] valueForKey:#"temp"]];
// [NOTE]
[self.tableView reloadData];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
I'm having trouble understanding how to use subclassed objects with blocks.
Here is an example of what I'm trying. PFItem is a subclass of PFObject.
- (void) handleItem:(PFItem *)item{
[item fetchIfNeededInBackgroundWithBlock:^(PFItem *item, NSError *error) {
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];
}
However, xcode flags this as a semantic error:
Incompatible block pointer types sending 'void (^)(PFItem *__strong, NSError *__strong)' to parameter of type 'PFObjectResultBlock' (aka 'void (^)(PFObject *__strong, NSError *__strong)')
If I change from PFItem to PFObject in fetchIfNeededInBackgroundWithBlock, it works, but then I can no longer access the properties of item. Instead of item.name I need to do item[#"name"].
If the method specifies you must use a block that takes a PFObject argument rather than a PFItem argument, then you must use a block that matches that for the method.
If you know the object being sent is actually a PFItem, you can always cast it within the block:
[item fetchIfNeededInBackgroundWithBlock:^(PFObject *obj, NSError *error) {
PFItem *item;
if ([obj isKindOfClass:[PFItem class]]) {
item = (PFItem *)obj;
} else {
return;
}
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];
- (void) handleItem:(PFItem *)item{
[item fetchIfNeededInBackgroundWithBlock:^(PFObject *pfObj, NSError *error) {
PFItem *item = (PFItem *)pfObj;
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];
}
Cast the PFObject to a PFItem and you're done. This is assuming that the PFObject is actually a PFItem.
When should I init my NSFetchedResultsController with AFNetworking? Currently I am doing with this:
[[AFHttpClient sharedClient] GET:#"/admin/stockCategories" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if (response.statusCode == 200) {
NSArray *results = (NSArray *)responseObject;
if ([results count] > 0) {
for (NSDictionary *obj in results) {
StockCategory *category = [StockCategory MR_createEntity];
category.name = obj[#"name"];
category.categoryId = obj[#"_id"];
category.createdDate = [NSDate dateForRFC3339DateTimeString:obj[#"createdDate"]];
[context MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
[self.tableView reloadData];
});
}];
}
} else {
}
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
I know normally I put execute fetch request in success block after I saved object to core data. Should I put do this:
[context MR_saveWithOptions:MRSaveParentContexts completion:^(BOOL success, NSError *error) {
// add NSFetchedResultsController here ?
self.fetchedResultsController = [StockCategory MR_fetchAllSortedBy:#"createdDate" ascending:NO withPredicate:nil groupBy:nil delegate:self];
dispatch_async(dispatch_get_main_queue(), ^{
[SVProgressHUD dismiss];
[self.tableView reloadData];
});
}];
if so, self.fetchedResultsController will be init multiple times. I don't think so. What I am currently do is this:
- (NSFetchedResultsController *)fetchedResultsController{
if (!_fetchedResultsController) {
_fetchedResultsController = [StockCategory MR_fetchAllSortedBy:#"createdDate" ascending:NO withPredicate:nil groupBy:nil delegate:self];
}
return _fetchedResultsController;
}
But in my UITableViewDatasource methods, only numberOfRows method works, but the cell.textLabel.text is empty. But if I relaunch the simulator, and comment the afnetworking get method. everything works, objects have been saved the core data, and NSFetchedResultsController works too.