I have a UITableView, and when didSelectRowAtIndexPath is called on a row, it switches perfectly to the next view. However, when I click the 'back' buttton and then select the same row previously selected...my app crashes. It does not crash if I select a different row. Here's the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *tempEventDictionary = [[NSDictionary alloc]initWithDictionary:[arrayWithEvents objectAtIndex:indexPath.row]];
NSLog(#"%i",tempEventDictionary);
//push to new view and set myArray in the cardPage
CardPageViewController *cardPageViewController = [[CardPageViewController alloc] init];
cardPageViewController.eventDictionary = tempEventDictionary;
[self presentModalViewController:cardPageViewController animated:YES];
[cardPageViewController release];
[tempEventDictionary release];
}
Crashes with “EXC_BAD_ACCESS” message.
As you can see, I am printing the pointer address of the NSDictionary, and it seems to be looking for the same address for each individual indexPath.row. This means that the pointer location is being released, and when I try to reasign it to a value of the same indexPath.row, the old pointer address is being searched for, yet it does not exist. Maybe I'm totally wrong here. Any help is appreciated.
There's nothing obviously wrong with the code you've posted. My guess would be that you're overreleasing one of your data objects somewhere within CardPageViewController and then when your code tries to make a temporary dictionary from the same data again, it encounters a dealloced object within the dictionary and that's when the crash happens.
I have a "hack" of an answer that keeps the object alive before the retain count goes all the way down with this elusive autorelease. I initialize the dictionary, and then set it so the retain count goes to 2, and the hidden (to my eyes) autorelease doesn't crash the program. Here is the code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//---send data to cardPage
CardPageViewController *cardPageViewController = [CardPageViewController alloc];
NSDictionary *tempEventDictionary = [[NSDictionary alloc]initWithDictionary:[arrayWithEvents objectAtIndex:indexPath.row]];
cardPageViewController.eventDictionary = [arrayWithEvents objectAtIndex:indexPath.row];
[self presentModalViewController:cardPageViewController animated:YES];
[cardPageViewController release];
}
I hope this helps someone who can't find this released dictionary.
Why not create a custom init that takes a dictionary and then copy the dictionary inside the your custom init for the new view controller?
Related
I am brand new to iOS development, and I could not find a solution on here or Google, so I'm asking out of desperation.
I have a class "ViewController" that is a subclass of UIViewController. In here, I have:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([self.bookTitle.text length] > 0)
self.entries = [self.bookLibrary searchForBook:self.bookTitle.text];
if ([segue.identifier isEqualToString: #"BookList"]) {
TableViewController *controller = (TableViewController *)segue.destinationViewController;
controller.itemCounter = [self.entries count];
controller.bookLibrary = [self.entries allValues];
}
}
The view for this on the Storyboard has a connection to a Table View Controller that I dragged and dropped onto the grid. I clicked the "Table View Controller" at the bottom, and set my custom class "TableViewController" in the custom class input box.
Now, from what I understand, the method above is passing all the data properly to the TableViewController.
Here's one of the methods I have in the TableViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"BookCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
Book* book = [self.bookLibrary objectAtIndex:indexPath.row];
cell.textLabel.text = book.title;
NSLog(#"%#", book.title);
return cell;
}
The NSLog entry is printing out all the book titles to the console, so I know for a fact the data is being passed. However, when I run the program and click the button to pull up the Table View, it's just an empty table. Any hints? I can upload my entire project. Been at this for several hours and a bit frustrated. Please help :(
EDIT: A response suggested I look at the state of my data variables in the table methods. It suggests their state is not what I think it is and that I should use NSLog to print out their values. I did just that, and I can see all the values printed out. I don't understand... they do infact have values assigned to them. The problem isn't that the data is missing.
Make sure you're either using a UITableViewController subclass as your VC (if you're using a UITableViewController ui object from the pallet), or that you're properly hooking up the UITableView's delegate and datasource properties to your VC (if you're using a plain UIViewController object and subclass).
(see comments on question).
Try this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"bookLibrary.count %d", bookLibrary.count);
return [bookLibrary count];
}
You'll find that things are not what you think they are... Add in an implementation of viewDidLoad and viewWillAppear along with that, each with their own "I'm here" NSLog statement, and trace the flow of that second view controller appearing. Again, you'll find you've got some sequencing issues where the flow isn't working quite the way you might be assuming.
Added comment:
Ultimately, the origin of your problem is this line in your "sending" controller's prepare for segue method:
controller.bookLibrary = [self.entries allValues];
What is this doing? It's calling allValues on the Dictionary object. That method generates a new array (!) containing the values. You don't store that new Array object in any permanent storage. You just use it to set:
controller.bookLibrary = ...
So, right after that statement executes, you have:
an Array object in your prepareForSegue method (where the code is executing) that you've only stored in one variable/holder, which is:
a weak pointer to that object over in your destination view controller (TableViewController)
The method ends.
The Array returned by [... allValues] is not being held on to by anything in the Source view controller, so the only thing holding it from being garbage collected is the pointer to it in the Destination view controller.
But that pointer is a weak pointer. By definition, if that's the only pointer to an object, the pointer will be set to nil and the object released for garbage collection. Poof! No more array object, and you're left holding a nil pointer.
As you discovered, setting the "receiver" to strong lets it hold on to that Array object, even after the other code exits and it's the only pointer to the Array.
(And, your code isn't being invoked twice. If you look closely at the logging -- or better yet set a breakpoint inside the table get-row-count method -- you'll see it's only being called once. The earlier logging of "I have 8 objects" is happening over in other code, not in your TableViewController.)
I have few table view controllers and I want selections from them to be shown on "ResultTableViewController". I also have array that collects the selected data and when i finally push it to the "ResultViewController" it shows only the last selection. Please help
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
firstarr = [[NSMutableArray alloc]init]; //array with questions
[self.firstarr addObject:[self.data objectAtIndex:indexPath.row]]; //collects the data properly
BodyDetailViewController* vc =[[BodyDetailViewController alloc] initWithNibName:#"BodyDetailViewController" bundle:nil];
vc.someArray = firstarr; //some array - ViewController with Results.
[self.navigationController pushViewController:vc animated:YES];
}
You have to remove this line :
firstarr = [[NSMutableArray alloc]init];
Because you're initializing your NSMutableArray each time you're selecting a cell, so all the data previously stored is erased and vc.someArray is getting an array with only your last selection, that's why ;)
Init your NSMutableArray outside the method, in your viewDidLoad for example
This...
firstarr = [[NSMutableArray alloc]init]; //array with questions
...creates a new array. Whatever was in firstarr previously is gone when you do a new selection.
Either create the array once somewhere else (when the object is initialized?) or append its content to vc.someArray instead of replacing it.
Currently, I have a savedWorkout class that is simply a Table View populated with different exercises in each cell. My goal now is for the user to be able to click on each individual exercise, which will take you to a new view filled with detailed information about it.
For this, I have created an Exercise class that will hold the detailed information about the new object. Is this possible?
Here is some pseudo-code I have written up:
if (Table View Cell's Text == ExerciseObject.exerciseName) {
Populate a view with the corresponding information;
}
Being new to iPhone programming, I'm not exactly sure what would be the best way to do this, and this is what i'm thinking would be the best way to go about it.
My Exercise class holds an NSString to keep track of the exercise name, and three NSMutableArray's to hold different information.
Please let me know if I am going in the right direction.
EDIT:
After trying to implement my pseudo-code this is what I came up with:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Exercise *exerciseView = [[Exercise alloc] initWithNibName:#"Exercise" bundle:nil]; //Makes new exercise object.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *str = cell.textLabel.text; // Retrieves the string of the selected cell.
exerciseView.exerciseName.text = str;
[self presentModalViewController:exerciseView animated:YES];
}
However, this doesn't seem to work. When the new view is presented, the label doesn't show up (I connected the UILabel exerciseName to my desired string). Am I implementing this wrong?
Yes, of course it's possible. Just use the delegate method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
and check your data source cell based on the index location.
You might need to post your cellForRowAtIndexPath method. If done traditionally, it uses the indexPath.row to access an array of exercises to get a particular exercise, then changes cell properties based on the particular exercise. Is that about right?
It so, then you're halfway home.
EDIT
1) Use the code inside your cellForRowAtIndex path to init your str, as indicated here.
2) The new view controller view hasn't been built yet. You can't initialize a subview in the view hierarchy before the VC is ready. You need to pass the string to a property in that view controller (in a custom init method if you want), then on that class's viewDidLoad, you can set the exerciseName field to the string property you saved earlier. That subview shouldn't be part of the classes public interface.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// There should be an array of exercises, the same one used in cellForRowAtIndexPath:
NSString *str = [self.myArrayOfExercises objectAtIndex:indexPath.row];
// Just made code up here, but however you get a string to place in the cell
// in cellForRowAtIndexPath do that same thing here.
Exercise *exerciseView = [[Exercise alloc] initWithNibName:#"Exercise" bundle:nil];
// Might be wise to rename this ExerciseViewController, since it's probably (hopefully) a ViewController subclass
// no need to get a table cell, you have the info you need from your exercise array
//UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//NSString *str = cell.textLabel.text; // Retrieves the string of the selected cell.
exerciseView.exerciseName.text = str;
[self presentModalViewController:exerciseView animated:YES];
}
I’m populating a UITableView based on the values of an NSMutableArray. This table view has search results. If the user clicks in one of the results, one will navigate to another screen. If the user clicks “back”, the search results are filled in again. At this point, while the table view is being repopulated, the old values still appear, just as I want. However, since I’m doing:
- (void)viewWillAppear:(BOOL)animated
{
NSMutableArray *m = [[NSMutableArray alloc] init];
self.searchResultsArray = m;
[m release];
}
The old cells information is no longer available. Thus, the app crashes if the user clicks in one of the old cells or scrolls the UITableView because I’m accessing the mutable array which was reinitialized above.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *cellArray = [searchResultsArray objectAtIndex:indexPath.row];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
appDelegate.selectedCell = [searchResultsArray objectAtIndex:indexPath.row];
}
Do you have any suggestions concerning how should I do this properly?
Thanks.
this is because you are re-initalizing the array each time the view comes back in focus, the array will then have 0 objects in, causing the issue when you select a row and reference an index in the array that simply was wiped when the viewWillAppear is called.
why not init ' self.searchResultsArray ' in viewDidLoad (remembering to undo this with release when the device receives memory warning)
let me know how you get on.
You should reset the array only when the view loads:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *m = [[[NSMutableArray alloc] init] mutableCopy];
self.searchResultsArray = m;
[m release];
}
You should always call the reloadData: method when changing the data in the UITableViewDataSourceDelegate instance.
Let me know if this helps
Please try below code:
- (void)viewWillAppear:(BOOL)animated
{
// Your search result array with your old values
// Now add or appened new data to your old search results.
[self.searchResultsArray addObject:#"Your Value 1"];
[self.searchResultsArray addObject:#"Your Value 2"];
[self.searchResultsArray addObject:#"Your Value 3"];
[self.tableView reloadData];
// if you need to add new values from a array put this code in a loop.
}
It should helps you...
Thx
It seems to me that you are not initialising the NSArray in the right method (viewDidLoad:).
Using XLData you don't have to care about where you set up the storage, reload the UITableView or add items to the NSArray (searchResultsArray) since it keeps track of the data (NSArray) and updates the UITableView accordingly and on the fly.
I have two different table views in which I use the exact same code in tableView:MoveRowAtIndexPath:ToIndexPath: to support user reordering of the rows. One of these tableViews works perfectly. The other one, however, gets confused and starts displaying the same subview no matter which row is selected - i.e. its row indexing seems to have got messed up.
I've temporarily fixed this by adding a [tableView reloadData] at the end of theMoveRowAtIndexPath method, but I don't understand why it wasn't working in the first place - especially since another view with the exact same code works perfectly. Obviously, there must be another method in this view controller which is messing it up, but I don't know where to look.
Here is the code that is the same in both:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
userDrivenDataModelChange = YES;
NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy];
NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:fromIndexPath];
[things removeObject:thing];
[things insertObject:thing atIndex:[toIndexPath row]];
int i = 0;
for (NSManagedObject *mo in things)
{
[mo setValue:[NSNumber numberWithInt:i++] forKey:#"displayOrder"];
}
[things release], things = nil;
[managedObjectContext save:nil];
userDrivenDataModelChange = NO;
}
(For what it's worth, the one that works is the child view of the one that doesn't, and they are in a to-many Core Data relationship).
Use a different identifier for each tableviews in
(UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
If you use the same identifier string, they will pickup each other's cached cell objects and show inconsistent data.
I forgot that I was using a custom cell on the parent view, and that the user presses a UIButton within the cell, rather than the cell itself. The custom cell has its index path set as a property by tableView:cellForRowAtIndexPath:, which of course never gets called again after the table cells are moved. Hence, I had to add a [tableView reloadData] to make sure the cells get updated.