My app is only crashing in a specific way. Here's a break down of what's going on.
I can type in text in a UITextView and tap a button that saves the text and adds a row to a UITableView in another UIViewController. I can then tap on the desired cell from the UITableView and that UIViewController will dismiss and the text will appear again on the main UIViewController.
I have another button that simply clears out the UITextView so I can type in new text.
If I view the text from an added row and then tap the "Add" button to input new text and then tap the "Save" button my app crashes.
Here's some of the code:
didSelectRow Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
[self dismissViewControllerAnimated:YES completion:nil];
}
save button code:
- (IBAction)saveNote
{
if (_noteView.aTextView.text == nil)
{
[_noteArray addObject:#""];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow: [_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
else
{
[_noteArray addObject:self.noteView.aTextView.text];
Note * tempNote = [[Note alloc] init];
_note = tempNote;
[_savedNotesViewController.savedNoteArray addObject:tempNote];
NSIndexPath * tempNotePath = [NSIndexPath indexPathForRow:[_savedNotesViewController.savedNoteArray count]-1 inSection:0];
NSArray * tempNotePaths = [NSArray arrayWithObject:tempNotePath];
//**** This is where the app is crashing *****
[_savedNotesViewController.noteTableView insertRowsAtIndexPaths:tempNotePaths withRowAnimation:NO];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
Note * myNote = [Note sharedNote];
myNote.noteOutputArray = _noteArray;
}
add butt code (makes a new UITextView):
- (IBAction)addButtonTapped
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"AddNote" object:nil];
}
in my viewWillAppear to show the selected row text I do this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.noteView.aTextView.text = _savedNotesViewController.displayString;
}
note code (singleton class):
static Note * sharedNote = nil;
- (id)initWithNote:(NSString *)newNote
{
self = [super init];
if (nil != self)
{
self.note = newNote;
}
return self;
}
+ (Note *) sharedNote
{
#synchronized(self)
{
if (sharedNote == nil)
{
sharedNote = [[self alloc] init];
}
}
return sharedNote;
}
When the app crashes I get this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Note isEqualToString:]: unrecognized selector sent to instance 0x8875f20'
Stepping through my code, the text is being added to the array, but when it comes time to insertRowsAtIndexPaths the app blows up.
Any advice is much appreciated!
* EDIT // TableView Code **
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_savedNoteArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
//I have a feeling this could be where an issue is.
NSString * cellString = [_savedNoteArray objectAtIndex:indexPath.row];
cell.textLabel.text = cellString;
return cell;
}
One potential issue (which may not be your crash, but will cause issues regardless) is that you are storing Note objects in the savedNoteArray BUT you are trying to use them as strings (your code below):
//Setting the text stored in an array into a NSString here
_displayString = [_savedNoteArray objectAtIndex:indexPath.row];
Then you assign that displayString to a UITextView's text property (which is supposed to be an NSString*):
self.noteView.aTextView.text = _savedNotesViewController.displayString;
The short form of this issue can be summarized as...
Note *note = [[NSNote alloc] init];
[array addObject:note];
textView.text = array[0];
This will clearly cause issues. You're basically assigning a 'Note' object to something that is supposed to be a string.
This probably leads into the crash that you're experiencing in the cellForRowAtIndexPath: method of your table view data source. Are you using Note objects there as well, or are you properly assigning NSStrings to views?
Related
I have a tableview in TrendingEventsTableViewController class and a selectRadiusViewController that implements a picker view. Upon selction of a value from the pickerview the data on TrendingEventsTableViewController should reload with the new data from ParseModelClass. When NSlogged to check, none of the tableview delegate methods are called after tableview reloaddata.
//pickerview method of selectRadiusViewController
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSString *result = [NSString stringWithFormat:#"Radius Chosen is %#", Radii[row]];
self.RadiusChosen.text=result;
ParseDataModel *obj = [[ParseDataModel alloc]init];
TrendingListViewController *tobj = [[TrendingListViewController alloc]init];
tobj.RadiusInput = true ;// ** true indicates table view should be modified
[obj CalRadius:6000]; // call this method to populate modified array
NSLog(#"Contrl returned after calRad func call with count: %lu", obj.modEventNames.count);
// dispatch_async(dispatch_get_main_queue(), ^{
[tobj.TrendingTableView reloadData];
//});
}
//callRadius method in ParseDataModelClass to get the new data to refresh the tableview
-(void) CalRadius:(int) rad
{
CLLocation *Userlocation = [[CLLocation alloc] initWithLatitude:57.052443 longitude:9.910623];
PFGeoPoint *ULocation = [[PFGeoPoint alloc]init];
ULocation.latitude=57.052443;
ULocation.longitude=9.910623;
// PFGeoPoint *ULocation =[PFGeoPoint geoPointWithLatitude:40.75060000 longitude:73.99360000];
self.GeoPointsRadii = [[NSMutableArray alloc] init];
self.modEventNames = [[NSMutableArray alloc] init];
// --------<filtering radius array >---------
PFQuery *query = [PFQuery queryWithClassName:#"EventInfo"];
[query whereKey:#"EventLocation" nearGeoPoint:ULocation withinKilometers:6000];
self.GeoPointsRadii = [query findObjects];
for(int i=0; i< [ self.GeoPointsRadii count] ; i++)
{
PFObject *tempObj = self.GeoPointsRadii [i];
self.modEventNames[i]=tempObj[#"EventName"];
}
}
//TrendingEventsTableViewController
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"No of sections called");
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"numberOfRowsInSection called");
// Return the number of rows in the section.
if(self.RadiusInput==false)
return self.myGuys.count;
else{
ParseDataModel *obj=[[ParseDataModel alloc]init];
return obj.modEventNames.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"TABLE VIEW method called");
static NSString *CellIdentifier = #"Cell"; // reuse identifier
// check if we can reuse a cell from row that just went off screen
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// create new cell, if needed
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if(self.RadiusInput==false)
{
NSLog(#"TABLE VIEW FILLING NORMALLY WITH myGuys");
UIImage *image= [UIImage imageNamed:#"garfield(1).jpg"];
cell.imageView.image= image;
// set text attibute of cell
cell.textLabel.text = [self.myGuys objectAtIndex:indexPath.row];
}
else{
NSLog(#"TABLE VIEW RELOADING");
UIImage *image= [UIImage imageNamed:#"garfield(1).jpg"];
cell.imageView.image= image;
// set text attibute of cell
ParseDataModel *obj=[[ParseDataModel alloc]init];
cell.textLabel.text = [obj.modEventNames objectAtIndex:indexPath.row];
}
// set accessory type to standard detail disclosure indicator
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
In oder to reload the table from a different class you may use NSNotification to let the view controller of the table know when to reloadData.
In the view controller of the table, under viewDidLoad, add this:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"reloadData" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(reloadTable:) name:#"reloadData" object:nil];
Add this method as well in the table view controller:
- (void)reloadTable:(NSNotification *)notification {
[self.TrendingTableView reloadData];
}
Lastly, in the picker view controller fire the notification (instead of [tobj.TrendingTableView reloadData];):
[[NSNotificationCenter defaultCenter] postNotificationName:#"reloadData" object:self];
I'm experiencing an issue with my UITableView which fetches data from a data table on parse.com. The issue is that every time I scroll down, hiding the first cell completely and then scroll back up, the text on the first cell's titleL is that of another cell. Kindly look at my code and let me know what I'm doing wrong. Also are there any better practices for my code when working with UITableViews in the future?
Code
- (void)viewDidLoad {
[super viewDidLoad];
[self someMethod];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifer = [NSString stringWithFormat:#"CellIdentifier%i",num];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
UILabel *titleL = [[UILabel alloc] initWithFrame:CGRectMake(10,10,300,20)];
titleL.text = myTitle;
[cell addSubview:titleL];
return cell;
}
-(void) someMethod {
for (int i = 0; i < arr.count; i++) {
PFQuery *query = [PFQuery queryWithClassName:#"SomeClass"];
[query whereKey:#"objectId" equalTo:[arr objectAtIndex:i]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!object) {
} else {
myTitle = [object objectForKey:#"title"];
num = i;
[feed beginUpdates];
[feed reloadRowsAtIndexPaths:myArr withRowAnimation:UITableViewRowAnimationAutomatic];
[feed endUpdates];
}
}];
}
}
You need to write your your tableView:cellForRowAtIndexPath: in such a way that it doesn't matter in which order it is called.
That method is called whenever the UITableView needs to get a cell (sometimes this doesn't mean that it's displayed). It will get called multiple times and you cannot rely on a specific order (for obvious reasons: you cannot predict how the user will scroll).
Now, your problem is that your implementation uses myTitle to assign a title. But that value is not calculated inside tableView:cellForRowAtIndexPath:. You need to change your code in such a way that you always can access the required value for your index path, no matter in which order or how often that method is called.
For example, in someMethod you can store your values from [object objectForKey:#"title"] in an NSMutableArray or in a NSMutableDictionary (with #(i) as key). Then you can query the title for each index path in tableView:cellForRowAtIndexPath:.
I have made a table where depending on which cell you click on you will be sent into a new scene (detailviewcontroller). For example if you click on the cell with the text Thailand you will be sent to ThailandDetailViewController (scene). Everything works until you use the searchbar (look under - (void)tableView).
-When some countries get outfiltered (because of the searchfunction) the reaming countries will go higher in the table and acquire a lower count. Which leads to that they will lead to the wrong detailviewcontroller (scene).
A friend of mine said to me that I should use objectAtIndex within my array, didnt really catch what he meant with that.. And make a switch on the cell.textLabel.text (didnt really follow him)
Here is my .m file:
- (void)viewDidLoad
{
[super viewDidLoad];
self.mySearchBar.delegate = self;
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
totalStrings = [[NSMutableArray alloc] initWithObjects:#"America",#"Austria",#"Canada",#"France",#"Germany",#"Greece",#"Malaysia",#"Mexico",#"Netherlands",#"Poland",#"Russia",#"Singapore",#"Thailand",#"Ukraine", nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0: [self performSegueWithIdentifier:#"Segue0" sender:self];
break;
case 1: [self performSegueWithIdentifier:#"Segue1" sender:self];
break;
//and so on
default: break;
}
}
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if(searchText.length == 0){
isFiltered = NO;
}
else
{
isFiltered = YES;
filteredStrings = [[NSMutableArray alloc]init];
for (NSString *str in totalStrings){
NSRange stringRange = [str rangeOfString:searchText options:NSCaseInsensitiveSearch];
if(stringRange.location !=NSNotFound) {
[filteredStrings addObject:str];
}
}
}
[self.myTableView reloadData];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Cellidentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Cellidentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Cellidentifier];
}
if (!isFiltered) {
cell.textLabel.text = [totalStrings objectAtIndex:indexPath.row];
}
else //if it's filtered
{
cell.textLabel.text = [filteredStrings objectAtIndex:indexPath.row];
}
return cell;
}
Big thank you in beforehand!!
Well, you can have a custom class to store the area and the segue index like this:
#interface SegueVO : NSObject
#property (nonatomic, strong) NSString *area;
#property int segueIndex;
-(id)initWithArea:(NSString *)area andSegueIndex:(int)index;
#end
#implementation SegueVO
-(id)initWithArea:(NSString *)area andSegueIndex:(int)index
{
self = [super init];
if (self)
{
self.area = area;
self.segueIndex = index;
}
return self;
}
#end
You will then store your ares in the totalStrings array like this:
[[NSMutableArray alloc] initWithObjects:[[SegueVO alloc] initWithArea:#"America" andIndex:0],....
Of course you can create a factory method to cut down on initialisation code.
Now you can work out what segue to activate like this:
NSArray *arrayToUse = totalStrings;
if (isFiltered)
arrayToUse = filteredStrings;
[self performSegueWithIdentifier:[#"Segue"
stringByAppendingString:[NSString stringWithFormat:#"%i",
[arrayToUse[indexPath.row].segueIndex]] sender:self];
Hope this helps.
You could easily solve this problem by storing a custom object in your table's data model instead of an NSString. That object would contain the label to display plus the name of the segue to activate once selected.
It's another question why you'd want a totally different view controller for different data. I suppose these are different kinds of data that need different ways to deal with them.
I'm going to become a little crazy for this solution..
I have a TableView with his list of item (an Array from csv Parsing), I need to pass some data from this Array to the list of a Detail TableView when I select a cell..
I reed a bit of solutions and I tried them, but this should be the best solution and the code is conform to the guides.. But when I select a cell I have an "EXC_BAD_ACCESS" but I can't understand where is the zombie object, so I post all class:
#import "inRaggioViewController.h"
#import "iR-DetailViewController.h"
#implementation inRaggioViewController
#synthesize lista, record;
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *listaNonOrdinata = [[NSMutableArray alloc]init];
self.navigationItem.title = #"Tipologia";
NSString *fileString = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Lista1" ofType:#"csv"] encoding:NSUTF8StringEncoding error:nil];
record = [fileString csvRows];
dettaglio = [[NSMutableArray alloc]init];
id doppio = nil;
for (int i=1; i < record.count; i++) {
for (int j=0; j < listaNonOrdinata.count; j++) {
doppio = [[record objectAtIndex:i] firstObjectCommonWithArray:listaNonOrdinata];
if (doppio == nil) {
// [dettaglio addObject:[NSNumber numberWithBool:NO]];
} else {
// [dettaglio addObject:[NSNumber numberWithBool:YES]];
}
}
if (doppio == nil) {
[listaNonOrdinata addObject:[[record objectAtIndex:i]objectAtIndex:0]];
}
}
//Ordino array in ordine alfabetico
lista = [[NSArray alloc]init];
lista = [listaNonOrdinata sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
[listaNonOrdinata release];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (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 lista.count;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
NSString *cellValue = [lista objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
// NSLog(#"dettaglio bool Value: %s",[[dettaglio objectAtIndex:indexPath.row]boolValue] ? #"YES" : #"NO");
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
NSLog(#"Selezionata riga %i",indexPath.row+1);
iR_DetailViewController *detailViewController = [[iR_DetailViewController alloc]init];
// ...
// Pass the selected object to the new view controller.
detailViewController.navigationItem.title = [self.lista objectAtIndex:indexPath.row];
[detailViewController.lista addObject:[[self.record objectAtIndex:indexPath.row+1]objectAtIndex:1]];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)dealloc {
[dettaglio release];
[record release];
[lista release];
[super dealloc];
}
#end
The strange thing is that list object work fine in the other methods, only in the selection method it gives problem...
Sorry for my bad english, I don't speak english well. Thanks at all for advice!
I'VE RESOLVED!
Thanks to all, the problem was that I must retain list and record objects at the end of viewDidLoad!
If the list you're trying to add to is an NSArray it will not work. You can only add to it if it is an NSMutableArray. Try that?
My application terminates in the simulator when it successfully switches to a view. I'm sure its something easy, here is the .m file from the view that terminates the app. Maybe something isnt releasing. It does not throw an error in the console, the page loads, sits for a few seconds, then terminates and throws the mach_msg_trap in the debugger. It will continue if I hit the play button.
#implementation ProspectViewController
#synthesize jsonArray;
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *jsonURL = [NSURLURLWithString:#"https://www.mysite.php"];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
self.jsonArray = [jsonData JSONValue];
[jsonURL release];
[jsonData release];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [jsonArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *infoDictionary = [self.jsonArray objectAtIndex:indexPath.row];
static NSString *Prospects = #"agencyname";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Prospects];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:Prospects] autorelease];
}
// setting the text
cell.text = [infoDictionary objectForKey:#"agencyname"];
self.navigationItem.title = #"Prospects";
// Set up the cell
return cell;
}
Do not release jsonurl in viewDidLoad because it is not retained before. Only init-like methods retains the instance, not the static constructors.