I have custom contact book sorted by A-Z sections. I am trying to add to an array selected contacts
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableDictionary *contactInfo = [NSMutableDictionary new];
Cell *cell = (Cell *)[self.contTableView cellForRowAtIndexPath:indexPath];
//NSLog(#"CELL %#", cell.contact.fullname);
if (!cell.contact.contactChecked) {
cell.contactImage.image = [UIImage imageNamed:#"cell_blue_circle.png"];
cell.contact.contactChecked = YES;
//NSLog(#"DID SELECT %#", cell.contact.fullname);
NSLog(#"index checked row %d section %d", indexPath.row, indexPath.section);
[contactInfo setValue:cell.contact.fullname forKey:#"name"];
[contactInfo setValue:cell.contact.numbers.firstObject forKey:#"phone"];
[self.seletedPeople insertObject:contactInfo atIndex:indexPath.row];
} else {
NSLog(#"index unchecked row %d section %d", indexPath.row, indexPath.section);
cell.contactImage.image = [UIImage imageNamed:#"cell_gray_circle.png"];
cell.contact.contactChecked = NO;
[self.seletedPeople removeObjectAtIndex:indexPath.row];
}
NSLog(#"DICT SELECTED %#", self.seletedPeople);
}
What happens, that in some cell app crashing with error
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM insertObject:atIndex:]: index 1 beyond
bounds for empty array'
*** First throw call stack: (0x29c02fef 0x38150c8b 0x29b1cf8f 0xf7fe9 0x2d36e56b 0x2d41d43b 0x2d2d2a91 0x2d24d38f 0x29bc8fed 0x29bc66ab
0x29bc6ab3 0x29b13201 0x29b13013 0x313f2201 0x2d2b7a59 0x10c075
0x386dcaaf) libc++abi.dylib: terminating with uncaught exception of
type NSException
UPDATE:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"Cell";
Cell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
if (searchResults) {
//NSLog(#"CELL %#", cell.contact.fullname);
contact = [searchResults objectAtIndex:indexPath.row];
cell.contact = contact;
cell.firstNameLabel.text = contact.fullname;
cell.avatar.image = contact.image;
cell.avatar.layer.borderColor = [UIColor grayColor].CGColor;
cell.avatar.layer.borderWidth = 0.5;
cell.avatar.layer.cornerRadius = 25.0;
cell.avatar.layer.masksToBounds = YES;
cell.number.text = contact.numbers.firstObject;
} else {
NSString *sectionTitle = [[[namesDictionary allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]
objectAtIndex:indexPath.section];
NSArray *sectionContacts = [namesDictionary objectForKey:sectionTitle];
contact = [self getContactFromArray:[sectionContacts objectAtIndex:indexPath.row]];
cell.firstNameLabel.text = [sectionContacts objectAtIndex:indexPath.row];
cell.avatar.image = contact.image;
cell.avatar.layer.borderColor = [UIColor grayColor].CGColor;
cell.avatar.layer.borderWidth = 0.5;
cell.avatar.layer.cornerRadius = 25.0;
cell.avatar.layer.masksToBounds = YES;
cell.number.text = contact.numbers.firstObject;
cell.contact = contact;
cell.tag = indexPath.row;
}
if (contact.contactChecked) {
cell.contactImage.image = [UIImage imageNamed:#"cell_blue_circle.png"];
} else {
cell.contactImage.image = [UIImage imageNamed:#"cell_gray_circle.png"];
}
return cell;
}
The way I use in such cases. I create a model class and load the tableview with models. Now when i select a cell or deselect a cell. I just add that model in another array. After that when i de select the already selected cell, i can get the same model from the indexpath.row and then i can use NSArray method to fetch that model in that selected array and remove it from there. To fix your issue you can use indexPath.row as another key in dictionary during selection. After that when you deselect the cell use a predicate to get the added dictionary from the array that you are using to store selected ones. Once you find it delete it from the array.
the problem here is the coupling of model with view objects , you shouldn't inquire about a certain property from the view itself (in your case the Cell) however the contact checked should have a reflect on its model from the data source object (the one you used to feed the cellForRowAtIndexPath: , where it should be inquired from.
Otherwise the code is buggy and unstable due to that coupling since it might point to an empty object
The problem is here:
[self.seletedPeople insertObject:contactInfo atIndex:indexPath.row];
if selectedPeople is empty and the user clicks on row 2, then it's going to try to insert contactInfo into row 2 which is "beyond the bounds of an empty array". Simply use addObject: instead. You'll also need to change how you remove items from that array then (probably better to use a dictionary instead).
The solution was adding contact record id to my dictionary and search with predicate this contact id. then remove it. kudos to #jassi
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableDictionary *contactInfo = [NSMutableDictionary new];
Cell *cell = (Cell *)[tableView cellForRowAtIndexPath:indexPath];
if (!cell.contact.contactChecked) {
cell.contactImage.image = [UIImage imageNamed:#"cell_blue_circle.png"];
cell.contact.contactChecked = YES;
//NSLog(#"DID SELECT %#", cell.contact.fullname);
NSLog(#"index checked %# ", [indexPath description]);
[contactInfo setValue:cell.contact.fullname forKey:#"name"];
[contactInfo setValue:cell.contact.numbers.firstObject forKey:#"phone"];
[contactInfo setValue:#(cell.contact.contactId) forKey:#"contactId"];
[self.seletedPeople addObject:contactInfo];
} else {
NSLog(#"index unchecked %#", [indexPath description]);
cell.contactImage.image = [UIImage imageNamed:#"cell_gray_circle.png"];
cell.contact.contactChecked = NO;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"contactId == %d", cell.contact.contactId];
NSArray *resultTemp = [self.seletedPeople filteredArrayUsingPredicate:predicate];
if(resultTemp.count>0)
[self.seletedPeople removeObject:resultTemp[0]];
}
NSLog(#"DICT SELECTED %#", self.seletedPeople);
}
As user2320861 said, the problem is on the line where you use insertObject. I would do the following:
Change self.selectedPeople to a NSMutableDictionary using the following code:
//in your #interface
#property (nonatomic, strong) NSMutableDictionary *selectedPeople;
Change the code in didSelectCellAtIndexPath to:
//Since phone numbers are unique.
self.selectedPeople[cell.contact.numbers.firstObject] = contactInfo;
Retrieve all of the contacts later using this code:
for(id key in self.selectedPeople) {
NSDictionary contactInfo = [self.selectedPeople objectForKey:key];
//Do something with that contactInfo
}
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 have a UITableView which is being filled from Core Data by a NSFetchedResultsController. Everything works great. However, I have just implemented a UISegmentedControl at the top of the UITableView, which I would like to sort the results. There are three segments: #"All", #"Boys", #"Girls". In viewDidLoad, I instantiate a NSDictionary with three NSFetchedResultsController. Each has the exact same fetch request with a different predicate.
allFetchRequest.predicate = [NSPredicate predicateWithFormat:#"school.schoolID == %#", [NSNumber numberWithInt:[_schoolID intValue]]];
boysFetchRequest.predicate = [NSPredicate predicateWithFormat:#"school.schoolID == %# AND gender == %#", [NSNumber numberWithInt:[_schoolID intValue]], #"M"];
girlsFetchRequest.predicate = [NSPredicate predicateWithFormat:#"school.schoolID == %# AND gender == %#", [NSNumber numberWithInt:[_schoolID intValue]], #"F"];
When the UISegmentControl value is changed, I call a method which changes the view controller's "currentFetchedResultsController" instance variable to the corresponding NSFetchedResultsController for that segment, calls perform fetch, then calls reloadData on the tableView in the main thread.
- (void)showBoys
{
self.currentFetchedResultsController = self.fetchResultsControllerDictionary[#"boys"];
[self.currentFetchedResultsController performFetch:nil];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
It all seems to work great, except that the UITableView never seems to update its UI. It seems to show the correct number of sections for all, boys, and girls, but the object at each index doesn't change. For example, let's say we have 11 people in Core Data. Five guys, six girls. The view loads with the "All" segment pre-selected, so all 11 people load into the UITableView. However, when I switch the segment to "Boys", the number of rows will drop to five, but the objects in those rows never change. The UITableView will continue to show the first five objects that were already in the table, even if some are girls (gender == "F" in Core Data).
I know that the fetch is working properly because I have set up a small test:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Username = %# & Gender = %#", user.username, user.gender);
}
Now, when I select a row, it logs the correct username and gender that SHOULD be at that indexPath. However, the logged username is different than the one that appears in the UITableView at that row.
Table View Data Source Methods:
- (UserTableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Home Ranked Cell";
UserTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];
NSString *name = user.name;
if(!name || [name isEqualToString:#""])
name = user.username;
cell.name.text = name;
UserTableViewCell *previousCell = nil;
if(indexPath.row != 0)
previousCell = (UserTableViewCell *)[self tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section]];
NSInteger previousVotes = 0;
if(previousCell)
previousVotes = [previousCell.votes.text integerValue];
if(!previousCell) {
cell.rank.text = [NSString stringWithFormat:#"%i", 1];
} else if(previousVotes == [user.votes integerValue]) {
cell.rank.text = previousCell.rank.text;
} else {
cell.rank.text = [NSString stringWithFormat:#"%i", [previousCell.rank.text integerValue] + 1];
}
if(user.profilePicture && user.profilePicture.thumbnailData && ![user.profilePicture.thumbnailData isEqualToString:#""]) {
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:user.profilePicture.thumbnailData options:0];
cell.imageView.image = [UIImage imageWithData:imageData];
}
if(!cell.imageView.image)
cell.imageView.image = [UIImage imageNamed:#"xIcon.png"];
cell.votes.text = [NSString stringWithFormat:#"%i", [user.votes integerValue]];
cell.upButton.tag = indexPath.row;
cell.downButton.tag = indexPath.row;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
User *user = [self.currentFetchedResultsController objectAtIndexPath:indexPath];
NSLog(#"Username = %# & Gender = %#", user.username, user.gender);
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.currentFetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [self.currentFetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return self.segmentControl;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.currentFetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [[self.currentFetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
I have tried nearly everything. I have gone through every related thread and nothing has worked. What am I doing wrong?
** Newest Findings:
When a cell is selected, the cell does not become highlighted (or show any UI change for that matter), unless it is the correct cell for that specific index path. For example, let's say Sally is in row 0 for all Users, and Tom is in row 1. If I switch the UISegmentedControl to "Male" and tap the first cell (row 0, which currently shows Sally), there is absolutely no UI indication that the cell has been tapped, although tableView: didSelectRowAtIndexPath still gets called, logging the cell information that belongs there (Tom's User info, since he belongs in row 0 of the "Male" Users).
It seems to me that it would be easier to put the segmentation logic into the fetched results controller method. When switching the segmented control, just set your FRC to nil and account for the proper filter in the FRC creation code. You do not need 3 FRCs. Thus:
-(void)segmentedControlDidChange:(UISegmentedControl*)control {
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
and when creating the FRC:
NSPredicate *basePredicate = [NSPredicate predicateWithFormat:
#"school.schoolID = %#", _schoolID];
NSPredicate *secondPredicate = [NSPredicate predicateWithValue:YES];
NSInteger i = self.segmentedControl.selectedSegmentIndex;
if (i > 0) {
secondPredicate = [NSPredicate predicateWithFormat:
#"gender = %#", i == 1 ? #"M" : #"F"];
}
fetchRequest.predicate = [NSCompoundPredicate
andPredicateWithSubPredicates:#[basePredicate, secondPredicate]];
It turns out I made a silly mistake when initializing my UITableViewCell. Instead of calling
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
I was calling
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier **forIndexPath:indexPath**];
The latter returns the current UITableViewCell dequeued from the indexPath mentioned. For some reason, if the cell is dequeued from an existing cell, the properties on the cell are readonly, causing all of my changes to be simply ignored. I hope this helps someone in the future, as I did not see anything about this on Stack Overflow, I just happened to stumble upon my mistake after hours of analyzing each line of code.
I've a tableView with dynamic cells. Each section has 5 rows. In the first 4 rows for each row there is a text field. Instead in the fifth row there is an imageview (when I click on this row, I can choose a photo from my photo library, and this last will be put in imageView). The number of sections is decided at run-time using a stepper. The section 0 is fixed and contains a stepper. When I click on + button (on stepper) a section will be add. So everything is right but if I wrote before in the rows that contain the textfield, and then add one or more sections, the contents of these textfield are mixed with each other (and also between sections).
//In file.h I've declared #property (nonatomic) int numberOfComponents;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1 + self.numberOfComponents;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0) {
return 1;
}
else{
return 4;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0) {
NSString *CellIdentifier = #"cellStepper";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *stepperLabel = (UILabel *)[cell viewWithTag:1];
stepperLabel.text = [NSString stringWithFormat:#"%i",self.numberOfComponents];
UIStepper *stepper = (UIStepper *)[cell viewWithTag:2];
stepper.minimumValue = 0;
stepper.maximumValue = 20;
stepper.stepValue = 1;
stepper.autorepeat = YES;
stepper.continuous = YES;
return cell;
}
if (indexPath.row == 4) {
NSString *CellIdentifier = #"cellProfileSnap";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
NSString *CellIdentifier = #"cellDetail";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UITextField *cellLabelComponent = (UITextField *)[cell viewWithTag:3];
cellLabelComponent.placeholder = #"detail";
return cell;
}
- (IBAction)stepperClik:(UIStepper *)stepper{
if (stepper.value == 0 && self.numberOfComponents == 0) {
if (stepper.value > self.numberOfComponents) {
self.numberOfComponents += 1;
}
else{
return;
}
}
if (stepper.value > self.numberOfComponents) {
self.numberOfComponents += 1;
}
else{
self.numberOfComponents -= 1;
}
[self.tableView reloadData];
}
Solved by Greg, but now there's another problem: I have a save button, which should save many arrays into a dictionary (containing the details of each section 5) as the number of sections.
Here the code of save button:
- (IBAction)saveButton:(id)sender{
NSMutableArray *arrComponents = [[NSMutableArray alloc] init];
for (int i = 0; i < self.numberOfComponents; i++)
{
NSMutableArray *component = [[NSMutableArray alloc] init];
for (int j = 0; j < [self.arrDetails count]; j++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i+1];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath: indexPath];
if (j == 4){
UIImageView *imageComponent = (UIImageView *) [cell viewWithTag:4];
NSLog(#"%#",imageComponent.image);
if (imageComponent.image == Nil) {
[component addObject: nil];
}
[component addObject: imageComponent.image];
}
else{
UITextField *detailComponent = (UITextField *) [cell viewWithTag:3];
NSLog(#"%#",detailComponent.text);
if ([detailComponent.text isEqualToString:#""]) {
[component addObject:#""];
}
if (detailComponent.text != nil && i != 0)
[component addObject: detailComponent.text];
}
}
[arrComponents addObject: component];
NSLog(#"%#",arrComponents);
}
Where it is shown in the code / / ERROR HERE, at the fourth iteration of 5 iterations (number of rows in a section) of the latest iteration (last section read), the application crashes giving this message:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[__NSArrayM
insertObject:atIndex:]: object cannot be nil'
I believe your detailComponent is equal nil and you cannot save nil to the array. Before you call
[component addObject: detailComponent.text]; //ERROR HERE
do check:
if (detailComponent != nil && i != 0)
[component addObject: detailComponent.text];
It could happen because in your section 0 you haven't got any textfield.
//EDITED
The issue is happened here:
if (imageComponent.image == Nil) {
[component addObject: nil];
}
[component addObject: imageComponent.image];
Replace it with:
if (imageComponent.image == nil) {
[component addObject:[NSNull null]];
}
else {
[component addObject: imageComponent.image];
}
You cannot add nil to array if you want to add nil you should add object NSNull.
And you are missing else statement. Your code try to add nil to the array twice (if the image is nil) first time in your if statement ([component addObject: nil];) and second time just after your if statement: [component addObject: imageComponent.image];
// EDITED
Make the changes as suggested in comments in code below. I use the same dictionary, you should change the name because it suggest that it keeps just textfields values, but now it will store the images as well:
if (indexPath.row == 4) {
NSString *CellIdentifier = #"cellProfileSnap";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//I believe this is where you keep your imageView
// get your image
UIImageView *imageComponent = (UIImageView *) [cell viewWithTag:4];
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
if (self.textValueDictionary[key])
imageComponent.image = self.textValueDictionary[key];
else
imageComponent.image = nil; //Your default value (probably you want to set up your plus image)
return cell;
}
The next step you need to make is where the user presses the plus image - you have to save the image in the dictionary. Put this code to your method where you get the image from user. This is a pseudo code so you have to adjust it a little bit:
// Get reference to the cell
UITableViewCell *cell = //have a look on textFieldDidEndEditing method. You have to work out how to get the cell (I cannot do that because I haven't got access to your code);
// Get index path
NSIndexPath *indexPath = [self.myTableView indexPathForCell:cell];
// Get section as a string
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
UIImage *img = //get your image here;
//make sure it's not nil before you save it to dictionary
//save it to dictionary
[self.textValueDictionary setValue:img forKey:key];
The last bit is saveButton: method there was mistake in my advices from yesterday. You're trying to get text and image directly from cells, and it works fine if the cells are visible. If you cannot see the cell system put it to reusable pool and you get nils.
To fix it you have to get the data from the dictionary:
Don't use:
UIImageView *imageComponent = (UIImageView *) [cell viewWithTag:4];
instead do:
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
UIImage *img = self.textValueDictionary[key];
// make sure is not nil before you save it. If it's nil add [NSNull nil] to your array as I explained before.
When you read text don't do it like that:
UITextField *detailComponent = (UITextField *) [cell viewWithTag:3];
get text from the dictionary:
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
NSString *str = self.textValueDictionary[key];
If it complain about data type use cast (UIImage*).
Remember to change your first loop to: for (int i = 1; i < self.numberOfComponents; i++) and when you get NSIndexPath change it to inSection:i instead inSection:i+1.
I haven't run this in Xcode so maybe there are some mistakes but you should be able to find and fix it.
Hope it will work for you.
It happens because you don't save the cellLabelComponent.text property and when you reloadData tableView reuses cell (which cause this problem).
You should save data you entered to your cellLabelComponent (for example in array, you can use UITextFieldDelegate) and in your cellForRowAtIndexPath: method you should assign saved values to desired field.
//EXTENDED
Conform to <UITextFieldDelegate> protocol in your .h file or class extension.
Add
#property (nonatomic, strong) NSMutableDictionary *textValueDictionary;
to your class extension and allocate it and init in viewDidLoad or init method:
self.textValueDictionary = [[NSMutableDictionary alloc] init];
Add this to cellForRowArIndexPath:
cellLabelComponent.placeholder = #"detail";
// Make your class to be delegate for UITextField
cellLabelComponent.delegate = self;
// I use NSString (section) as a key in my dictionary. You can use NSNumber if you like
if (self.textValueDictionary[[NSString stringWithFormat:#"%d", indexPath.section]])
cellLabelComponent.text = self.textValueDictionary[[NSString stringWithFormat:#"%d", indexPath.section]];
else
cellLabelComponent.text = #""; //Your default value
Add your UITextFieldDelegate methods:
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// Get reference to the cell
UITableViewCell *cell = (UITableViewCell*)[[[textField superview] superview] superview];
// Get index path
NSIndexPath *indexPath = [self.myTableView indexPathForCell:cell];
// Get section as a string
NSString *section = [NSString stringWithFormat:#"%d", indexPath.section];
[self.textValueDictionary setValue:textField.text forKey:section];
}
You can use more delegate if you need.
It should be enough to make it works. It will work just when you have one UITextField in your table section if you have more you should use unique key in your dictionary (NSindexPath will work if you have more that one textfield in section but not more that one in row, just remember to convert it to NSNumber).
Let me know is it work.
//EXTENDED
If you have more that one UITextField per section you have to change the dictionary key. This solution above will work just if you have up to one row per section.
This solution (below) will work if you have many textfields per section but not more that one text field per cell (it will work for one textfield per section as well):
Change line in cellForRowAtIndexPath from:
if (self.textValueDictionary[[NSString stringWithFormat:#"%d", indexPath.section]])
cellLabelComponent.text = self.textValueDictionary[[NSString stringWithFormat:#"%d", indexPath.section]];
to:
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
if (self.textValueDictionary[key])
cellLabelComponent.text = self.textValueDictionary[key];
And change lines in textFieldDidEndEditing: method from:
NSString *section = [NSString stringWithFormat:#"%d", indexPath.section];
[self.textValueDictionary setValue:textField.text forKey:section];
to:
NSString *key = [NSString stringWithFormat:#"sec:%d,row:%d", indexPath.section, indexPath.row];
[self.textValueDictionary setValue:textField.text forKey:key];
I am recieving this error when i scroll to the bottom of my TableView, I dont think its any error with actually retrieving the pictures from the server.:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFArray objectAtIndex:]: index (15) beyond bounds (15)'
Here is my .m file I cut it to only the actually needed parts of the file:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self entries] count] + tweets.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 0) {
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *created = [tweet objectForKey:#"created_at"];
NSLog(#"%#", created);
static NSString *CellIdentifier = #"TweetCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSString *text = [tweet objectForKey:#"text"];
NSString *name = [[tweet objectForKey:#"user"] objectForKey:#"name"];
cell.textLabel.text = text;
cell.detailTextLabel.text = [NSString stringWithFormat:#"by %#", name];
return cell;
}else {
static NSString *CellIdentifier = #"InstagramCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSDictionary *entry = [self entries][indexPath.row];
NSString *imageUrlString = entry[#"images"][#"low_resolution"][#"url"];
NSURL *url = [NSURL URLWithString:imageUrlString];
[cell.imageView setImageWithURL:url];
return cell;
}
}
- (void)fetchTweets {
self.twitterClient = [[AFOAuth1Client alloc] initWithBaseURL:[NSURL URLWithString:#"https://api.twitter.com/1.1/"] key:#"TWEETER_KEY" secret:#"TWEETER_SECRET"];
[self.twitterClient authorizeUsingOAuthWithRequestTokenPath:#"/oauth/request_token" userAuthorizationPath:#"/oauth/authorize" callbackURL:[NSURL URLWithString:#"floadt://success"] accessTokenPath:#"/oauth/access_token" accessMethod:#"POST" scope:nil success:^(AFOAuth1Token *accessToken, id responseObject) {
[self.twitterClient registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self.twitterClient getPath:#"statuses/home_timeline.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *responseArray = (NSArray *)responseObject;
[responseArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(#"Success: %#", obj);
tweets = responseArray;
[self.tableView reloadData];
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
} failure:^(NSError *error) {
NSLog(#"Error: %#", error);
}];
}
There needs to be tight coordination between the return value from numberOfRowsInSection and the array access that the code does in cellForRowAtIndexPath.
Consider this, your entries array and tweets array each have 4 elements. So numberOfRowsInSection returns 8. The cellForRowAtIndexPath method gets called to configure row 6. Your code will do this: NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
But wait... that array has only 4 elements, right? Asking for something at index 6 will generate the crash you see.
It might be simpler to write a methods to interleave the arrays into a single array, then answer the count of the combined array in numberOfRowsInSection. In cellForRowAtIndexPath, the array elements themselves should be able to tell you what kind of row you have (not the index). Dereference the combined array and configure the table accordingly.
EDIT - I'll try to make my advice more explicit in code: Let's say, for simplicity, that "entries" and "tweets" are both arrays of NSDictionaries and that your app wants to organize them in the UI entries first, then tweets.
// in interface:
#property (nonatomic, strong) NSArray *myModel;
// in code:
- (NSArray *)myModel {
if (!_myModel) {
NSMutableArray *array = [NSMutableArray arrayWithArray:[self entries]];
[array addObjectsFromArray:tweets];
_myModel = [NSArray arrayWithArray:array];
}
return _myModel;
}
We call this 'myModel' for a reason. It's the datasource of the table. The datasource protocol is asking explicitly about this array (and no other).
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.myModel.count;
}
Now cellForRowAtIndexPath is going to ask you to configure that many (myModel count) rows, numbered 0..count-1. You must dereference the same array -- myModel -- for all datasource methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *myModelForThisRow = self.myModel[indexPath.row];
// get the cell = deque...
cell.textLabel.text = myModelForThisRow[#"someKey"];
return cell;
}
What if your tweets or entries array changes? No problem, just rebuild the model like this:
- (IBAction)tweetsOrEntriesDidChange:(id)sender {
self.myModel = nil; // the "lazy" getter will rebuild it
[self.tableView reloadData]; // this will call the datasource which will call the lazy getter
}
You are trying to go read into an array outside of it's bounds.
That array access look very suspicious
if (indexPath.row % 2 == 0) {
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
as well as this one
NSDictionary *entry = [self entries][indexPath.row];
From what I've seen your array tweets and [self entries] don't contain as many object each as there is row in your table section.
I take my assomption from here :
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self entries] count] + tweets.count;
}
NSRangeException is thrown because you are trying to access an index which is not within the valid range for your array. Try setting an "Exception breakpoint" in Xcode to see where it's coming from. Check here to know more about Exception breakpoints
This is typically caused by an off by one error.
When i scroll my UITableView down and then scrolling up, the app crashes with the stack below:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 2147483647 beyond bounds [0 .. 48]'
I know it's saying that i am accessing a cell index that exceed the UITableView size, but i am not able to figure out how to fix it. this may be my relevant code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"CheckedTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
NSDictionary *rowData = [self.tableData objectAtIndex:[self tableIndexFromIndexPath:indexPath]];//this line may be the source of the crash
cell.textLabel.text = [rowData objectForKey:kCellTextKey];
if ([[rowData objectForKey:kCellStateKey] boolValue]) {
UIImageView *imageView1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checked.png"]];
cell.accessoryView = imageView1;
} else {
UIImageView *imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"unchecked.png"]];
cell.accessoryView = imageView2;
}
return cell;
}
EDIT:
Here is my tableIndexFromIndexPath method implementation
- (NSUInteger)tableIndexFromIndexPath:(NSIndexPath *)indexPath {
// Get list of items at selected section
NSArray *listData = [tableContents objectForKey:[sortedKeys objectAtIndex:indexPath.section]];
// Get name of selected row within the section
NSString *textKey = [listData objectAtIndex:indexPath.row];
// Look up that name in the tableData array
for (int i=0; i < [tableData count]; i++) {
NSDictionary *dict = [tableData objectAtIndex:i];
if ([[dict objectForKey:kCellTextKey] isEqualToString:textKey]) {
return i;
}
}
//In case Name was not found
return NSNotFound;
}
Change
NSDictionary *rowData = [self.tableData objectAtIndex:[self tableIndexFromIndexPath:indexPath]];
to
NSDictionary *rowData = [self.tableData objectAtIndex:indexPath.row];
As several have noted, your tableIndexFromIndexPath: is incorrect. Specifically it's returning NSNotFound, which suggests your using something like indexOfObject: for an object that is not in the array.
At first check that are you getting all value of your NSMutableArray?
If you are getting then check your number of section of tableView is equal to number of NSMutableArray array or not?
If same then there is a reload problem then reload your table view when you are inserting data into the NSMutableArray.
I hope you will find your solution among them.