Obj-c - NSArray property appears NULL in numberOfRowsInSection, but not in viewDidLoad? - ios

I'm trying to create a new array by filtering self.neighbourData into self.closeByNeighbours (a new array), and everything works great - the data appears as it should in viewDidLoad. However, when trying to return the number of cells for self.closeByNeighbours, the NSArray count returns NULL? Any idea as to why this is?
ViewController.h
#property (strong, nonatomic) NSArray *closeByNeighbours;
#property (strong, nonatomic) NSMutableArray *neighbourData;
ViewController.m
-(void)viewDidLoad {
NSMutableDictionary *viewParams = [NSMutableDictionary new];
[viewParams setValue:#"u000" forKey:#"view_name"];
[DIOSView viewGet:viewParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.neighbourData = [responseObject mutableCopy];
[self.neighboursView reloadData];
NSDictionary *userDictInfo = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"diosSession"]];
DIOSSession *session = [DIOSSession sharedSession];
[session setUser:userDictInfo];
[session user];
NSString *myData = [session user][#"user"][#"field_province"][#"und"][0][#"safe_value"];
self.closeByNeighbours = [self.neighbourData filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.neighboursView) {
return [self.closeByNeighbours count];
}
}

The framework reloads the table view implicitly after the view did load.
At that moment closeByNeighbours is declared but not initialized therefore it's nil.
The completion block in viewGet is called much later.
A solution to avoid nil is to initialize the array at the beginning of viedDidLoad
-(void)viewDidLoad {
[super viewDidLoad];
self.closeByNeighbours = [NSArray array];
...
And you have to reload the table view on the main thread in the completion handler
...
self.closeByNeighbours = [self.neighbourData filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});

I think you are forgetting to init closeByNeighbours. Try adding.
NSString *myData = [session user][#"user"][#"field_province"][#"und"][0][#"safe_value"];
self.closeByNeighbours = [NSArray array: filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
Or however you want, you just need to initialize the closeByNeighbours property.

Related

Obj-C - Execute animation if filtered result contains > x?

I'm filtering an NSDictionary, and the filtered items are displayed in a tableView. I want to execute an animation (self.tableView.frame...) ONLY if the filtered count is more than - however, I can't get my filtered count to write an if statement inside of animateTextView. See code below - what is the best way for me to do this?
.m
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewParams = [NSMutableDictionary new];
[viewParams setValue:#"chat" forKey:#"view_name"];
[DIOSView viewGet:viewParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
self.messages = (NSMutableArray *)responseObject;
[self.tableView reloadData];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (void) animateTextView:(BOOL) up
{
const int movementDistance = self.keyboardHeight;
const float movementDuration = 0.2f;
int movement= movement = (up ? -movementDistance : movementDistance);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.upView.frame = CGRectOffset(self.upView.frame, 0, movement);
[UIView setAnimationDidStopSelector:#selector(afterAnimationStops)];
[UIView commitAnimations];
self.tableView.frame = CGRectOffset(self.tableView.frame, 0, movement);
[UIView setAnimationDidStopSelector:#selector(afterAnimationStops)];
[UIView commitAnimations];
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self.messageDataFriends count] > 0) {
NSDictionary *userDictInfo = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"diosSession"]];
DIOSSession *session = [DIOSSession sharedSession];
[session setUser:userDictInfo];
[session user];
NSString *targetedUser = [self.messageDataFriends objectForKey:#"uid2"];
NSString *myID = [session user][#"user"][#"uid"];
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"uid contains[cd] %#", targetedUser];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"targetuser contains[cd] %#", myID];
NSPredicate *p3 = [NSPredicate predicateWithFormat:#"uid contains[cd] %#", myID];
NSPredicate *p4 = [NSPredicate predicateWithFormat:#"targetuser contains[cd] %#", targetedUser];
NSCompoundPredicate *pIntermediary1 = [NSCompoundPredicate andPredicateWithSubpredicates:#[p1, p2,]];
NSCompoundPredicate * pIntermediary2 = [NSCompoundPredicate andPredicateWithSubpredicates:#[p3, p4,]];
NSCompoundPredicate *pFinal = [NSCompoundPredicate orPredicateWithSubpredicates:#[pIntermediary1, pIntermediary2]];
NSArray *filtered = [self.messages filteredArrayUsingPredicate:pFinal];
return [filtered count];
}
First, it's probably a good idea to separate the filtering process (a function of the Model part of MVC) from the tableView:numberOfRowsInSection: method (the Controller sending data to the view):
-(NSArray *)filterMessages {
//returns the filtered array
}
You can then save the filtered array in another instance variable, and use that:
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.filteredMessages.count;
}
Finally, to animate when needed:
dispatch_async(dispatch_get_main_queue(), ^{
self.messages = (NSMutableArray *)responseObject;
self.filteredMessages = [self filterMessages];
[self.tableView reloadData];
if(self.filteredMessages.count > ANIMATE_THRESHHOLD){
[self animateTextView:YES];
}
});
By the way, unless you are making UI calls to do it, the filtering can be pulled off the main thread. Simply move the call to filterMessages and the cast before it outside the dispatch block.

JSON parsing with AFNetworking and model Class

I am bit confused in fetching data and displaying data from json into my App using Model.
I am having this kind of json data :
{
result
[
{
key : value
key : value
key : value
}
{
key : value
key : value
key : value
}
]
}
I am trying this kind of code:
json = [[NSMutableArray alloc]init];
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseObject options:NSJSONWritingPrettyPrinted error:&writeError];
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&writeError];
json = dic[#"Result"];
int i;
for (i = 0; i <= json.count; i++)
{
NSMutableArray *array = json[i];
[json enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop)
{
// Dim = [[DimEntityFull alloc]initWithJSONDictionary:obj];
saveSearch = [[SaveSearchMaster alloc]initWithJSONDictionary:obj];
} ];
}
I am using "AFNetworking" and I am trying to fetch data and store into model class and then display to custom cell labels.
How can I get it.
Thank You.
In your view controller
- (void)viewDidLoad
{
[super viewDidLoad];
[self getUsersList];
}
-(void)getUsersList
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[manager POST:[NSString stringWithFormat:#"http://www.yourdomainname.com/getUserList"] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
{
//we will add Modal class objects of users in this array
usersArray=[[NSMutableArray alloc]init];
//getting result dictionary from response
NSDictionary *resultDictinary = [responseObject objectForKey:#"result"];
for (NSDictionary *userDictionary in resultDictinary)
{
//allocating new user from the dictionary
User *newUSer=[[User alloc]initWithDictionary:userDictionary];
[usersArray addObject:newUSer];
}
//in users array, you have objects of User class
//reload your tableview data
[self.TableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Now Create New file Called 'User'
in User.h
#interface User : NSObject
{
NSString *userID;
NSString *firstName;
NSString *lastName;
}
#property (nonatomic, retain)NSString *userID;
#property (nonatomic, retain)NSString *firstName;
#property (nonatomic, retain)NSString *lastName;
-(id)initWithDictionary:(NSDictionary *)sourceDictionary;
#end
in User.m
#import "User.h"
#implementation User
#synthesize userID,firstName,lastName;
-(id)initWithDictionary:(NSDictionary *)sourceDictionary
{
self = [super init];
if (self != nil)
{
self.firstName=[sourceDictionary valueForKey:#"firstName"];
self.lastName=[sourceDictionary valueForKey:#"lastName"];
self.userID=[sourceDictionary valueForKey:#"userID"];
}
return self;
}
#end
in your numberOfRowsInSectionmethod
return usersArray.count;
in your cellForRowAtIndexPath method
User *user=(User *)[usersArray objectAtIndex:indexPath.row];
yourLabel.text=user.firstName;

Won't add new objects to an array after removing all objects [duplicate]

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.

Why is there a long delay between json data being retrieved and viewcontroller being displayed

I have a problem with a long delay between a JSON data retrieval and the starting of a UITableViewController.
The method below uses a hardcoded query that is called from the UITableViewControllers initializer, and retrieves and displays the data within 2 seconds.
- (void)productsQuery
{
NSString *requestString = #"http://192.168.2.10/testQueries.php?Product_Description=tea";
NSURL *url = [NSURL URLWithString:requestString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
NSArray *returnedItems =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
for (int i = 0; i < [returnedItems count]; i++) {
NSDictionary *item = [returnedItems objectAtIndex:i];
NSNumber *nBay = [item objectForKey:#"Bay_Number"];
NSNumber *nShelf = [item objectForKey:#"Shelf_Number"];
NSNumber *coordX = [item objectForKey:#"CoordinateX"];
NSNumber *coordY = [item objectForKey:#"CoordinateY"];
TNWProduct *product =[[TNWProduct alloc]
initWithProductDescription:[item objectForKey:#"Product_Description"]
aisleNumber:[item objectForKey:#"Aisle_Number"]
bay:[nBay intValue]
shelf:[nShelf intValue]
nonAisleLocation:[item objectForKey:#"Location_Description"]
coordinateX:[coordX intValue]
coordinateY:[coordY intValue]];
[self.productList addObject:product];
}
NSLog(#"%#", self.productList);
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
[dataTask resume];
}
The method was then adapted and moved to a UIView controller so that the query could be input by the user.
The JSON data is still retrieved and added to the NSMutableArray _productList in 2 seconds, as it shows in the console from the NSLog call, but then appears to do nothing for 5-15 seconds until starting the ProductListViewController.
#interface TNWSearchViewController () <UITextFieldDelegate>
#property (weak, nonatomic) NSString *userQuery;
#property (weak, nonatomic) IBOutlet UIToolbar *toolbar;
#property (weak, nonatomic) IBOutlet UITextView *informationMessages;
#property (nonatomic) NSURLSession *session;
#property (nonatomic, strong) NSMutableArray *productList;
#property (nonatomic, strong) NSArray *returnedItems;
#end
#implementation TNWSearchViewController
.
.
.
.
- (void)productQuery:(NSString *)query
{
if ([_productList count] > 0 ) {
[_productList removeAllObjects];
}
NSMutableString *requestString = [#"http://192.168.2.10/testQueries.php?Product_Description=" mutableCopy];
[requestString appendString:query];
NSString *escapedRequestString = [requestString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#", escapedRequestString);
NSURL *url = [NSURL URLWithString:escapedRequestString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
_returnedItems =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&error];
//NSLog(#"Returned items = %#", _returnedItems);
for (int i = 0; i < [_returnedItems count]; i++) {
NSDictionary *item = [_returnedItems objectAtIndex:i];
//NSLog(#"Item = %#", item);
NSNumber *nBay = [item objectForKey:#"Bay_Number"];
NSNumber *nShelf = [item objectForKey:#"Shelf_Number"];
NSNumber *coordX = [item objectForKey:#"CoordinateX"];
NSNumber *coordY = [item objectForKey:#"CoordinateY"];
TNWProduct *product =[[TNWProduct alloc]
initWithProductDescription:[item objectForKey:#"Product_Description"]
aisleNumber:[item objectForKey:#"Aisle_Number"]
bay:[nBay intValue]
shelf:[nShelf intValue]
nonAisleLocation:[item objectForKey:#"Location_Description"]
coordinateX:[coordX intValue]
coordinateY:[coordY intValue]];
NSLog(#"%#", product);
[_productList addObject:product];
}
NSLog(#"Product list = %#", self.productList);
if ( [_productList count] > 0 ) {
TNWProductListViewController *plvc =
[[TNWProductListViewController alloc] initWithStyle:UITableViewStylePlain];
plvc.productList = [self.productList mutableCopy];
[self.navigationController pushViewController:plvc animated:YES];
} else {
_informationMessages.text = #"No matches found";
}
}];
[dataTask resume];
}
Moving the code blocks from the for loop and the if/else statement below [dataTask resume] result in the app loading the UITableView as expected, but the data from _returnedItems is no longer accessible.
Assistance appreciated.
for (int i = 0; i < [_returnedItems count]; i++) {
.
.
.
} else {
_informationMessages.text = #"No matches found";
}
Moving the creation and call of the ViewController to the dispatch_async block as below fixed the issue.
Thanks Fonix.
dispatch_async(dispatch_get_main_queue(), ^{
if ( [[[TNWProductList productsStore] allProducts] count] > 0 ) {
TNWProductListViewController *plvc =
[[TNWProductListViewController alloc] initWithStyle:UITableViewStylePlain];
[self.navigationController pushViewController:plvc animated:YES];
} else {
_informationMessages.text = #"No matches found";
}

Trouble using subclasses of PFObject in blocks such as fetchIfNeededInBackgroundWithBlock

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.

Resources