iphone development: table view returns empty table - iphone

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.)

Related

How to prevent TableViewCell from duplicating during recycle?

So my situation is pretty unique. I have a to-do list app with a bunch of tasks. Each task has a UITableViewCell. After each table view cell is tapped, it creates a view controller with the task at that row's index path's property. These view controllers are all stored in a NSDictionary. This is the code representation of what I just said:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
So this method of creating unique view controllers and storing them with a certain key almost always works. The problem arises when I delete or move the view controllers:
I was under the impression that a cell's index path gets recycled as you scroll down (dequeue). Doesn't that mean marking each cell with a number identifier would result in multiple cells for the same identifier?
Also, if you stored each view controller with a indexPath key, how do you make sure the key isn't set to two view controllers..? For example. Let's say you have 4 cells, which means 4 view controllers. You delete cell 3. Cell 4 moves down to cell 3s spot. You create a new cell which goes to spot 4. Now you have two controllers with the same indexPath key! How do you avoid this?? It's screwing up my app right now because tasks that have already been moved are loading their properties in the wrong view controller/cell!
I was suggested this to solve the problem before: "You maintain an NSMutableArray that "shadows" the contents of the table." However, I don't understand what this means/how to implement it.
You can use a technique we used to use on old databases. You store an NSInteger as a class var, and use that to assign a unique id to each of the cells as you create them. As you create each cell, you increment the unique id. Like this:
in your interface:
#property (nonatomic, assign) NSUInteger nextUniqueId;
then in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
cell.tag = self.nextUniqueId++;
and then track those tags in your viewControllers. Just give them an assignable property, or customize the init to include the id.
Better to set the tag value for each row in cell for row at index path method.Store that tag value globally,and use that tag in did select row method.

Using a Table Cell's Text to Access Corresponding Object

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];
}

self.tableView insertRowsAtIndexPaths from within tableView delegate

So I thought I'd have a go at building my own simple app. Please go easy on me I'm new to all this! The idea is this. For iPad have a single view controller with a text box and a text field. Text box takes a title, and text field takes the body of a report. There's a button on the page to submit the report, which bundles the two texts into an object and adds it to a table view within the same view controller. I have set the view controller as a delegate with <UITableViewDelegate, UITableViewDataSource> in my header file. My table view works fine for adding items in the viewDidLoad method. But adding items from the text inputs via a UIButton connected to -(IBAction) addItem falls over with: Property 'tableView' not found on object of type 'ReportsViewController'
- (IBAction)addReportItem
{
int newRowIndex = [reports count];
ReportObject *item = [[ReportObject alloc] init];
item.title = #"A new title";
item.reportText = #"A new text";
[reports addObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
}
I understand that I'm trying to call a method within my object but I have other method calls to tableView which work fine. i.e.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [reports count];
}
I thought this was the point of delegation. I know I'm missing something, but as I say I am new to all this and have looked everywhere for an answer before posting. What do I need to do to send my IBAction message to tableView?
Do you have a tableView instance variable setup in your .h file of the view controller?
The reason you are able to access it in the delegate and data source methods is because they are passed in as part if the methods.
You will need to add the IBOUTLET tableView ivar and connect it to the tableView in your .xib.
Or perhaps your ivar for the tableView is named something else?
Good luck.
I had the same problem.
What helped was to inherit the View Controller from UITableViewController, instead of UIViewController. Not using the protocol names in angled brackets.
The TableView is then linked to the dataSource and delegate via the storyboard (resp. InterfaceBuilder).
The parent class UITableViewController has an IBOutlet tableView defined.
MyViewController.h:
#interface MyViewController : UITableViewController

How can I send a message to the currently selected table cell after it has moved off-screen?

Here’s my scenario:
I’m showing a UITableViewController in a UINavigationController, and I’m drawing the cells myself in a subclass. In order to keep the cells looking as close to possible like native cells, I have a flag that indicates whether it is in a transitional state or not, in order to prevent the text color from visibly flashing when the user moves back up the stack from a detail view to the table view.
Currently, I set my transitioning flag in -tableView:didSelectRowAtIndexPath:, like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// (stuff for pushing the detail view on to the navigation stack)
((MyCustomTableViewCell *) [self.tableView cellForRowAtIndexPath: indexPath]).transitioning = YES;
}
This works rather well, with one caveat: Immediately before the list animates off-screen, the transition is clearly visible to anyone looking for it, as the cell text changes to black (on blue) from white (on blue.)
My question: Is there any way to get the currently selected cell from the table view, after it has transitioned off-screen, and send it a message? (assuming it isn’t being deallocated, simply unloaded)
Or am I simply going about this whole thing the wrong way?
(For anyone considering saying that nobody will notice it, keep in mind that it’s acceptable to me the way that it is, I’m simply wondering if there’s a way for me to make it better. Good iOS applications are all about the little things.)
What do you mean by "prevent the text color from visibly flashing"? By default iOS table cells don't appear to do that, at least in an unpleasant way. Perhaps you can revisit your UITableViewCell implementation and determine if you are incorrectly handling -setSelected:animated: and -setHighlighted:animated
UITableView does not keep a publicly-accessible list of all the cells in the table.
In order to access all the cells (including ones out of the screen) you need to maintain a separate array of the cells you generate.
#interface MyViewController : UIViewController
{
NSMutableArray* tableCells;
}
#implementation MyViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableCells == nil)
tableCells = [[NSMutableArray alloc] init];
UITableViewCell* cell;
if (indexPath.row < [tableCells count])
{
// Return a cell from the cached list
cell = (UITableViewCell*)[tableCells objectAtIndex:indexPath.row];
}
else
{
// Create a new cell
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"identifier"];
// Customize and fill the cell with content anyway you wish
// ...
}
return cell;
}
Now that you have a list of all the cells in the table, you can send them any message, anytime you want.

How to empty out the whole tableview on a button click in iPhone SDK?

In my iPhone app, I am using a single tableview to display different sets of data based on the button clicked.
Now as I am using the same tableView I need to blank out the tableView contents everytime a new button is selected.
And this is quite normal requirement rite? As such it is inefficient to take 7 tables to show 7 different data sets.
Problem:
I have seen that table clears out but when we display some other data in the table then the previous data appears in background as in Screenshot AFTER.
I have tried setting the array as nil and reloading the tableView but then it doesnt seem to work.
What can be a fix for this issue?
I have checked the code and it seems proper to me.
You can refer to the Screen shot to get a better idea of what actually is happening.
BEFORE ( i.e. the first time Event is clicked)
AFTER (i.e. once the Event category button is clicked after some other category button)
You can clearly see a different image in background where as it should be same as image in above screenshot. This is not a button, I am adding a UIImageView to tableViewCell.
NSArray is not mutable, that is, you cannot modify it.
Instead of using NSArray use NSMutableArray and use
[mutArr removeAllObjects];
and then reload the tableView. It worked for me.
- (IBAction)yourAction {
tableView.delegate = nil;
tableView.dataSource = nil;
[tableView reloadData];
}
it will empty your table view... ur need is not clear
In button action method, call the tableView reload and assign null to object from which you are initializing the cells previously,
[tableView reloadData];
You can return zero for numberOfRowsInSection, and add BOOL variable for isEmpty.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(isEmpty){
return 0;
}else{
// your current logic
}
}
//action when button clicked
-(IBAction)myAction{
isEmpty = TRUE;
[self reloadData];
}
I don't think it will work if your array is nil. Try initializing it as a empty array with:
myArr = [NSArray array];
And then reload the tableView data. Otherwise I think we need to see your code
EDIT
It is still a bit unclear(still no code in your question), but I think your problem is really related to your cell construction. Are you adding UIImageView on every cellForRowAtIndexPath message?
I guess you have something similar to:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"cellName";
myCell *cell = (myCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
//CONSTRUCT CELL.
//THIS IS THE ONLY PLACE WHERE YOU SHOULD ADD SUBVIEWS TO YOUR CELL
}
//Are you adding subviews here? -you shouldn't
//configure data in cell
return cell;