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);
}];
Related
I have a ViewController A and retrieve JSON data in ViewDidLoad by using following method:-
[self.manager GET:#"http://api.sampleWebsite.com/api/xx" parameters:nil progress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
json_product = responseObject;
self.aryProducts=[NSMutableArray array];
for (NSDictionary *subDic in json_product) {
ProductItem_Model *model=[[ProductItem_Model alloc]initWithDic:subDic];
[self.aryProducts addObject:model];
}
[self.collectionView reloadData];
}
failure:^(NSURLSessionDataTask *operation, NSError *error) {
}];
}
Model.h
#import <Foundation/Foundation.h>
#import "JSONModel.h"
#interface ProductItem_Model : JSONModel
#property (nonatomic,strong) NSString <Optional>*name;
- (instancetype)initWithDic:(NSDictionary *)dicProductItem;
#end
It will working fine and return result in Model.m
- (instancetype)initWithDic:(NSDictionary *)dicProductItem{
NSError *error = nil;
self = [self initWithDictionary:dicProductItem error:&error];
NSLog(#"CORRECT RESULT WILL RETURN HERE%#",dicProductItem);
return self;
}
In my ViewController B, how can I pass in NSDictionary list from Model as mentioned above? Here is my sample code:-
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *gridcell = nil;
ProductItem_Model *model = [[ProductItem_Model alloc]init];
NSDictionary *subDic;
NSLog(#"HOW CAN I PASS IN THE NSDICTIONARY LIST HERE - %#",[model initWithDic:[subDic objectForKey:#"name"]]);
I am fetching data from the website and loading on the tableViewController. Tableviewcontroller is inside the tabbarcontroller. Whenever I clickked on tabbar, tableview data does not populated. However once I click other viewcontrollers and then click again on tableviewcontroller, then data populated.
#import "GetBookViewController.h"
#import "AFNetworking.h"
#interface GetBookViewController ()
#end
#implementation GetBookViewController
#synthesize booksArray;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated
{
[self loadData];
}
-(void)viewDidAppear:(BOOL)animated
{
[self.tableView reloadData];
}
-(void) loadData
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:#"http://XXXXXX.com/coursera/books.php" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if ([[responseObject valueForKey:#"status"] isEqualToString:#"success"]) {
int count = [[responseObject valueForKey:#"total"] integerValue];
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:count];
for (int i = 1; i <= count; i++) {
NSString *obj = [NSString stringWithFormat:#"%i", i];
[array addObject:[responseObject objectForKey:obj]];
}
booksArray = array;
for (id obj in booksArray) {
NSLog(#"%#", [obj valueForKey:#"title"]);
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - Table view data source
- (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 [booksArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel* label = (UILabel*)[cell viewWithTag:100];
NSString *title = [[booksArray objectAtIndex:indexPath.item] valueForKey:#"title"];
label.text = title;
return cell;
}
You aren't doing anything once you receive a response from the network and populate your array?
What you need to do is notify the table view that it needs to query its data source again to refresh its values. Simply calling reloadData on your table view once you have your array would to the trick:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager POST:#"http://ilyasuyanik.com/coursera/books.php" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
if ([[responseObject valueForKey:#"status"] isEqualToString:#"success"]) {
int count = [[responseObject valueForKey:#"total"] integerValue];
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:count];
for (int i = 1; i <= count; i++) {
NSString *obj = [NSString stringWithFormat:#"%i", i];
[array addObject:[responseObject objectForKey:obj]];
}
dispatch_async(dispatch_get_main_queue,^{
booksArray = array;
for (id obj in booksArray) {
NSLog(#"%#", [obj valueForKey:#"title"]);
}
//now you can update your table view
[self.tableView reloadData];
});
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
This question already has answers here:
Return value for function inside a block
(3 answers)
Closed 8 years ago.
This is my array
#property (nonatomic, strong) NSMutableArray *searchResults;
I initialized it in viewDidLoad function. I want to remove all objects from this array when current input in search bar is changed and add populate it using new elements.
But when I do
[self.searchResults removeAllObjects];
It won't add new elements. same goes with returning an array to self.searchResults
But when I don't remove elements from an array and append elements, it adds elements with no problem. I'm really having a hard time figuring out what's wrong.
viewDidLoad func
- (void) viewDidLoad {
[super viewDidLoad];
self.searchBar.delegate = self;
self.searchBar.showsCancelButton = YES;
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.searchResults = [[NSMutableArray alloc] init];
[self searchHandler:self.searchBar];
}
here is adding new elements.
- (NSMutableArray *)getProductList: (NSString *)text withArray: (NSMutableArray *) arrayResult{
[self.searchResults removeAllObjects];
[manager POST:url parameters:parameter
success:^(AFHTTPRequestOperation *operation, id responseObject){
NSLog(#"Length: %lu", (unsigned long)[responseObject count]);
int length = [responseObject count];
NSString *key;
for (int i=0; i<length; i++) {
key = [NSString stringWithFormat:#"%d", i];
[self.searchResults addObject:responseObject[key]];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
checking the array
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
NSLog(#"changed text: %#", searchText);
//[searchResults removeAllObjects];
self.searchResults = [self getProductList:searchText withArray:self.searchResults];
NSLog(#"Length of current array: %lu", (unsigned long)[self.searchResults count]);
for (NSString *item in self.searchResults) {
NSLog(#"%#", item);
}
[self.tableView reloadData];
}
You set searchResults
self.searchResults = [self getProductList:searchText withArray:self.searchResults];
But getProductList doesn't return an array. Aren't you getting a warning? If not, I suspect you are just setting it to nil on the return
Also, getProductList is asynchronous, but you are just trying to load table data as soon as it returns.
Do something more like this instead:
- (NSMutableArray *)getProductList: (NSString *)text withArray: (NSMutableArray *) arrayResult{
[self.searchResults removeAllObjects];
[manager POST:url parameters:parameter
success:^(AFHTTPRequestOperation *operation, id responseObject){
NSLog(#"Length: %lu", (unsigned long)[responseObject count]);
int length = [responseObject count];
NSString *key;
for (int i=0; i<length; i++) {
key = [NSString stringWithFormat:#"%d", i];
[self.searchResults addObject:responseObject[key]];
}
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)searchBar:(UISearchBar *)theSearchBar textDidChange:(NSString *)searchText {
[self getProductList:searchText withArray:self.searchResults];
}
This should be cleaned up more (remove the withArray parameter -- you don't even use it).
Just make sure self.searchResults is not nil at this point.
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.
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];