I'm trying to convert my NSDictionary (storageData) to an NSArray, as I need to pass the data (title) contained in the dictionary through a segue. I've implemented the below (see last method, the tableview is simply to provide some context), but a crash occurs with the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString objectForKey:]:
unrecognized selector sent to instance
Any idea how to fix?
Viewcontrolller.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *DoctorsTableIdentifier = #"StorageItemTableViewCell";
StorageItemTableViewCell *cell = (StorageItemTableViewCell *)[tableView dequeueReusableCellWithIdentifier:DoctorsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"StorageItemTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
NSDictionary *tmp = [self.storageData objectForKey:[NSString stringWithFormat:#"%d",(long)indexPath.row]];
[[cell itemName] setText:(NSString *)[tmp objectForKey:#"title"]];
NSString *title = [tmp objectForKey:#"title"];
[[cell itemName] setText:title];
NSDictionary *node = [self.descripData objectAtIndex:indexPath.row];
[[cell itemDescrip] setText:[node objectForKey:#"body"]];
NSString *secondLink = [[self.descripData objectAtIndex:indexPath.row] objectForKey:#"photo"];
[cell.itemPhoto sd_setImageWithURL:[NSURL URLWithString:secondLink]];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *values = [self.storageData allKeys];
ItemDetailViewController *detailViewController = [[ItemDetailViewController alloc]
initWithNibName:#"ItemDetailViewController" bundle:nil];
detailViewController.title = [[values objectAtIndex:indexPath.row] objectForKey:#"title"];
detailViewController.itemDetail = [self.descripData objectAtIndex:indexPath.row];
detailViewController.secondLink = self.descripData[indexPath.row][#"photo"];
[self.navigationController pushViewController:detailViewController animated:YES];
}
The error states that you are calling objectForKey in an object of type NSString, which doesn't have that method declared.
So put a breakpoint in the beginning of the method, step over each line one by one until crashes and then check what is the variable that you are thinking is a Dictionary when it is a String.
I believe if you would extract allValues instead of allKeys then you could only:
[values objectAtIndex:indexPath.row]
Since you are using a index as indexPath.row. Let me know if this helps.
Related
The top 3 cells in my tableview have a label that says the word 'Squirrels'. Is there a way to make it so that if a UILabel says 'Squirrels' in more than one cell in my table, to only show the first cell of the three?
E.g. if UILabel userName in tableviewCell is equal to #"Squirrels", only show one table
view cell in the table that contains Squirrels in the UILabel
Hope this makes sense. Thanks in advance!
EDIT: I've successfully retrieved the first array containing more than one common 'name' value (see edit to code below). That said, when I try and display these values (firstFoundObject) in my tableview I get the following crash error:
-[__NSDictionaryI objectAtIndex:]: unrecognized selector sent to instance 0x1c01a5a20 2017-10-03 23:01:51.728128-0700
pawswap[623:85420] *** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSDictionaryI
objectAtIndex:]: unrecognized selector sent to instance 0x1c01a5a20'
ViewController.m
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *nodeTitle = self.messages[0][#"name"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", nodeTitle];
NSArray *filteredArray = [self.messages filteredArrayUsingPredicate:predicate];
id firstFoundObject = nil;
firstFoundObject = filteredArray.count > 0 ? filteredArray.firstObject : nil;
NSMutableArray *firstObjects = firstFoundObject;
return [firstObjects count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *nodeTitle = self.messages[0][#"name"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", nodeTitle];
NSArray *filteredArray = [self.messages filteredArrayUsingPredicate:predicate];
id firstFoundObject = nil;
firstFoundObject = filteredArray.count > 0 ? filteredArray.firstObject : nil;
NSMutableArray *firstObjects = firstFoundObject;
static NSString *PointsTableIdentifier = #"MyMessagesCell";
MyMessagesCell *cell = (MyMessagesCell *)[tableView dequeueReusableCellWithIdentifier:PointsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyMessagesCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *receivedSubjectLine = [firstObjects objectAtIndex:indexPath.row];
NSString *messageSubject = [receivedSubjectLine objectForKey:#"node_title"];
[cell.subjectLine setText:messageSubject];
NSDictionary *fromUser = [firstObjects objectAtIndex:indexPath.row];
NSString *userName = [fromUser objectForKey:#"name"];
[cell.senderName setText:userName];
NSDictionary *receivedBody = [firstObjects objectAtIndex:indexPath.row];
NSString *messageBody = [receivedBody objectForKey:#"body"];
[cell.fullMessage setText:messageBody];
NSDictionary *messageReceived = [firstObjects objectAtIndex:indexPath.row];
NSString *timeReceived = [messageReceived objectForKey:#"published at"];
NSLog(#"Message Received at %#", timeReceived);
[cell.receivedStamp setText:timeReceived];
return cell;
}
PREVIOUS
ViewController.m
#pragma mark - Table view data source
- (int)numberOfSectionsInTableView: (UITableView *)tableview
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.messages count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PointsTableIdentifier = #"MyMessagesCell";
MyMessagesCell *cell = (MyMessagesCell *)[tableView dequeueReusableCellWithIdentifier:PointsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyMessagesCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *receivedSubjectLine = [self.messages objectAtIndex:indexPath.row];
NSString *messageSubject = [receivedSubjectLine objectForKey:#"node_title"];
[cell.subjectLine setText:messageSubject];
NSDictionary *fromUser = [self.messages objectAtIndex:indexPath.row];
NSString *userName = [fromUser objectForKey:#"name"];
[cell.senderName setText:userName];
NSDictionary *receivedBody = [self.messages objectAtIndex:indexPath.row];
NSString *messageBody = [receivedBody objectForKey:#"body"];
[cell.fullMessage setText:messageBody];
NSDictionary *messageReceived = [self.messages objectAtIndex:indexPath.row];
NSString *timeReceived = [messageReceived objectForKey:#"published at"];
[cell.receivedStamp setText:timeReceived];
return cell;
}
Basically the problem you are getting is due to firstObject is of type Dictionary and you are type casting it to NSMutableArray. Please check below line:
id firstFoundObject = nil; firstFoundObject = filteredArray.count > 0
? filteredArray.firstObject : nil;
If you see you have filteredArray.firstObject as Dictionary in your application which you capture in firstFoundObject but later you are making it NSMutableArray type here:
NSMutableArray *firstObjects = firstFoundObject;
And later when you try to get here, it crashes
NSDictionary *receivedSubjectLine = [firstObjects
objectAtIndex:indexPath.row];
The correct - basic - version of your code should look like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PointsTableIdentifier = #"MyMessagesCell";
MyMessagesCell *cell = (MyMessagesCell *)[tableView dequeueReusableCellWithIdentifier:PointsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyMessagesCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
[cell.subjectLine setText:[self.recvMessage objectForKey:#"node_title"]];
[cell.senderName setText:[self.recvMessage objectForKey:#"name"]];
[cell.fullMessage setText:[self.recvMessage objectForKey:#"body"]];
[cell.receivedStamp setText:[self.recvMessage objectForKey:#"published at"]];
return cell;
}
Though it is not optimised but still it can do work for you.
COUNT ISSUE:
NSMutableDictionary *firstObjects = firstFoundObject;
return [firstObjects count];
In your code above you have inside the numberOfRowsInSection since you have firstFoundObject as dictionary so when you call [firstObjects count] which is a valid call and it returns the number of key in the dictionary.
You have modify it like :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
NSInteger rowCount = filteredArray.count;
self.recvMessage = rowCount? filteredArray.firstObject: nil;
return rowCount? 1: 0;
}
and you have new data which actually stores the filtered object.
#property (nonatomic) NSDictionary *recvMessage;
Hope this helps.
Yes you can do it easily,
You will do it before run tableView,
for example :
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableDictionary* NameDictionary=[[NSMutableDictionary alloc] init];
NSString* PreviousName;
int oneTime=0;
for(int i=0;i<[self.messages count];i++){
NSDictionary *fromUser = [self.messages objectAtIndex: i];
NSString *userName = [fromUser objectForKey:#"name"];
if(oneTime==0)
{
PreviousName=userName;
[NameDictionary addObject:[self.messages objectAtIndex: i]];
oneTime=1;
}
if(oneTime!=0)
{
if([PreviousName isEqualToString: userName])
{
}
else{
[NameDictionary addObject:[self.messages objectAtIndex: i]];
}
PreviousName=userName;
}
}
}
When you ask to filter out cells with a senderName property equal to #"Squirrels", you're effectively asking to change your datasource after it has been set. This will cause problems in your numberOfRowsInSection method, which will return more rows than you need if any filtering takes place after the datasource is set.
As one of the comments to your answer suggests, "make a secondary array which contains unique elements of self.messages and work with this array." The array that makes up the datasource of the tableview should require no filtering. The tableview should just be able to present it.
If I had enough reputation to comment on the above answer, I would say that you're right that it doesn't work because self.messages doesn't change. Instead of collecting the "non-duplicate" objects in NameDictionary, consider collecting them in an NSMutableArray. This new, filtered array should be the datasource for your array. You may want to filter this array outside of this ViewController so that once the array arrives at this view controller it can just be assigned to self.messages.
If you're looking to exclude all duplicates, as opposed to just duplicates that appear next to each other, consider this answer.
I am getting the above mentioned error and do not know why because I implemented the same code in another example. "services" is my NSMutableArray and "service name" is the label on table view with Tag 2. In viewdidload I fill the service array with objects.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"servicesCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
UILabel *servicesName;
UILabel *nrofService;
if(tableView == self.servicetable)
{
servicesName = (UILabel*) [cell viewWithTag:2];
servicesName.text = [[services objectAtIndex:0] objectAtIndex:indexPath.row];
}
return cell;
}
The problem is on the below line:
servicesName.text = [[services objectAtIndex:0] objectAtIndex:indexPath.row];
Check [services objectAtIndex:0] is returning NSString instead of NSArray or NSMutableArray. So you are calling objectAtIndex:indexPath.row on NSString which causes the exception:
NSCFConstantString objectAtIndex unrecognized selector sent to
instance
So set your UILabel text as below:
servicesName.text = [services objectAtIndex:indexPath.row];
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return dict.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"RestaurantResultsCell";
RestaurantDetailsViewCell *cell = (RestaurantDetailsViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle]loadNibNamed:#"restaurantDetailsCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
arrResponse =[NSArray arrayWithObject:dict];
arr = [arrResponse objectAtIndex:0];
dictio = [arr objectAtIndex:0];
cell.cusineRestLabel.text = [[dictio valueForKey:#"restaurant_name"]objectAtIndex:indexPath.row];
NSLog(#"cell is %#",cell.cusineRestLabel.text);
// cell.cusineRestLabel.text = [dic objectForKey:#"restaurant_name"];
cell.cusineRestAddress.text = [dictio valueForKey:#"restaurant_streetaddress"];
NSLog(#"cell is %#",cell.cusineRestAddress.text);
cell.cusineDeliveryTime.text = [dictio valueForKey:#"restaurant_delivery"];
NSLog(#"cell is %#",cell.cusineDeliveryTime.text);
return cell;
}
after executing for the first time [__NSArrayI length]: unrecognized selector sent to instance
This code
arrResponse =[NSArray arrayWithObject:dict];
arr = [arrResponse objectAtIndex:0];
is a very strange way to write
arr = (id)dict;
which looks not like a bug, but intentional obfuscation. So what is it: An array or a dictionary?
I'm attempting to allow my user to be able to delete/remove a row from a tableView (remove an object from an existing NSMutableArray), however when I try and delete the row, I get the following crash error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '-[__NSCFArray
removeObjectAtIndex:]: mutating method sent to immutable object'
Does anyone know why this might be? See code below. Apologies for the lengthy code.
.h
#property (strong, nonatomic) NSMutableArray *descripData;
.m
- (void)viewDidLoad {
[super viewDidLoad];
self.descripData = [[NSMutableArray alloc] init];
refreshControl = [[UIRefreshControl alloc]init];
[self.tableView addSubview:refreshControl];
[refreshControl addTarget:self action:#selector(refreshTable) forControlEvents:UIControlEventValueChanged];
NSMutableDictionary *viewParams = [NSMutableDictionary new];
[viewParams setValue:#"storeditems" forKey:#"view_name"];
[DIOSView viewGet:viewParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.descripData = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self.storageData count] > 0 && self.descripData.count > 0)
{
return [self.descripData count];
}
else
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *DoctorsTableIdentifier = #"StorageItemTableViewCell";
StorageItemTableViewCell *cell = (StorageItemTableViewCell *)[tableView dequeueReusableCellWithIdentifier:DoctorsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"StorageItemTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if (self.storageData.count > 0 && self.descripData.count > 0) {
noitemsView.hidden = YES;
NSDictionary *title = [self.descripData objectAtIndex:indexPath.row];
[[cell itemName] setText:[title objectForKey:#"node_title"]];
NSDictionary *node = [self.descripData objectAtIndex:indexPath.row];
[[cell itemDescrip] setText:[node objectForKey:#"body"]];
NSDictionary *value = [self.descripData objectAtIndex:indexPath.row];
[[cell valueLabel] setText:[value objectForKey:#"dollarvalue"]];
NSLog(#"%#", self.descripData);
NSDictionary *quantity = [self.descripData objectAtIndex:indexPath.row];
[[cell quantityLabel] setText:[quantity objectForKey:#"quantity"]];
NSLog(#"%#", self.descripData);
NSString *secondLink = [[self.descripData objectAtIndex:indexPath.row] objectForKey:#"photo"];
[cell.itemPhoto sd_setImageWithURL:[NSURL URLWithString:secondLink]];
}
else {
noitemsView.hidden = NO;
}
return cell;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 152;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[self.descripData removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView reloadData];
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ItemDetailViewController *detailViewController = [[ItemDetailViewController alloc] initWithNibName:#"ItemDetailViewController" bundle:nil];
detailViewController.itemDetail = [[values objectAtIndex:indexPath.row] objectForKey:#"node_title"];
detailViewController.itemDetail = [self.descripData objectAtIndex:indexPath.row];
detailViewController.secondLink = self.descripData[indexPath.row][#"photo"];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Check the line self.descripData = responseObject;. It looks like you are probably setting your descripData property to point to a NSArray, not an NSMutableArray. If you really need the mutability, try self.descripData = [responseObject mutableCopy]; instead.
I suspect that the following assignment causes the problem:
self.descripData = responseObject;
The response object itself is immutable, assigning it to a NSMutableArray does not change that fact, you need to create a NSMutableArray with the contents of the responseObject:
self.descripData = [NSMutableArray arrayWithArray:responseObject];
Most likely happening on the following line:
self.descripData = responseObject;
You're replacing your previous mutable array for descripData with a new one, which apparently is not mutable. You can either create a new mutable array from this new array using [NSMutableArray arrayWithArray:responseObject], or you can add the contents of responseObject to your current mutable array.
It's important to pay attention when you assign something of type id to a variable, because there's no compile-time type checking that can occur. This sometimes results in errors at runtime.
Face this issue because NSUserDefaults always returns an immutable object.
Please while retrieving Mutable object from NSUserDefaults please refer below code.
NSMutableArray *storeArray = [[defaultDefects objectForKey:#"defaultStoreDefects"]mutableCopy];
I'm trying to fill a table view with some data from an array . The array is populated but when i run the app i get this error : reason: '-[__NSCFArray length]: unrecognized selector sent to instance
I searched for similar questions , but no one was the right for me. I don't use the lenght function and my app is crashing on : cell.textLabel.text = [self.currentDate objectAtIndex:indexPath.row];
quizViewController.m
- (void)passDataForward
{
ScoreViewController *secondViewController = [[UIStoryboard storyboardWithName:#"Storyboard" bundle:[NSBundle mainBundle]]instantiateViewControllerWithIdentifier:#"score"];
secondViewController.data =self.scoreLabel.text;
secondViewController.delegate = self;
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[highScores insertObject:self.scoreLabel.text atIndex:highScores.count];
NSData *currentDate =[NSDate date];
NSDateFormatter *dateFormatter=[[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"dd.MM.YYYY HH:mm:ss"];
NSString *dateString =[dateFormatter stringFromDate:currentDate];
[data insertObject:dateString atIndex:data.count];
NSMutableArray *scorearray = [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] arrayForKey:#"high_scoresarray"]];
NSMutableArray *dataArray = [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] arrayForKey:#"data_score"]];
[scorearray addObject:highScores];
[dataArray addObject:data];
[[NSUserDefaults standardUserDefaults] setObject:scorearray forKey:#"high_scoresarray"];
[[NSUserDefaults standardUserDefaults] setObject:dataArray forKey:#"data_score"];
secondViewController.test=[NSMutableArray arrayWithArray: scorearray];
secondViewController.currentDate=[NSMutableArray arrayWithArray: dataArray];
[self presentViewController:secondViewController animated:YES completion:^(void)
{
}];
}
ScoreViewController.m
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell==nil) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = [self.currentDate objectAtIndex:indexPath.row];
cell.detailTextLabel.text=[self.test objectAtIndex:indexPath.row];
return cell;
}
The error means that this line:
[self.currentDate objectAtIndex:indexPath.row]
returns an Array instead of an NSString. Which means that
self.currentDate
contains NSArray objects and not NSString objects.
Aris is right.
self.currentDate returns an NSArray. And that is caused by:
[scorearray addObject:highScores];
[dataArray addObject:data];
Where you are adding one array as the only item to the array scorearray and dataArray respectively. You do not add a bunch of strings, which I assume are stored within highScores and data.
Replace it with
[scorearray addObjectsFromArray:highScores];
[dataArray addObjectsFromArray:data];
That should add the contents of highScores and data to scorearray and dataarray.