Many different UITableViewCells in one TableView - iphone

I have an NSDictionary that we will say is like this:
key: value:
name Bookshelf
movies Array containing: (Movie 1, Movie 2, Movie 3)
books Array containing: (Book 1, Book 2, Book 3)
music Array containing: (Music 1, Music 2, Music 3)
And so on. Now what I'm trying to do is create a tableView that displays all this information, but with different types of cells based on what the dictionary key is. For example, movies use cellForMovies books use cellForBooks music use cellForMusic, each has it's own layout.
Obviously, I am oversimplifying, but hopefully you understand what I'm trying to do.
I am able to accomplish this if I do sections and a different cell type for each section, but I don't want to do that. I want to be able to have a table like this, for example:
cellForBooks
cellForMovies
cellForMovies
cellForMusic
cellForBooks
and so on...Any ideas or resources you can point me to? I only care about iOS 5 support (storyboards).
Edit with solution:
A big thank you to everyone who helped, especially atticus who put the final piece together.
What ended up working was to change the structure of the data to be an array of dictionaries and then add a Key: type Value: book/movie/music to each entry. The data structure now looks like:
Array[0]: Dictionary: [Key: type Value: book], [Key: name Value: Physics];
Array[1]: Dictionary: [Key: type Value: music], [Key: name Value: Rock];
Then to configure the cells I did this:
NSString *CellIdentifier = #"Cell";
NSDictionary *object = [self.listOfStuff objectAtIndex:indexPath.row];
if ([[object objectForKey:#"type"] isEqualToString:#"book"]){
CellIdentifier = #"BookCell";
}else if ([[object objectForKey:#"type"] isEqualToString:#"music"]){
CellIdentifier = #"MusicCell";
}else if ([[object objectForKey:#"type"] isEqualToString:#"movie"]){
CellIdentifier = #"MovieCell";
}
each of these had a different tableviewcell in the storyboard. We figured out if you want to use any of the built-in cell features, like cell.textLabel.text you needed to do this:
cell.textLabel.text = [object objectForKey:#"name"];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.opaque = NO;
Hope this helps someone else in the future.

In your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method you can return many types of cells according to indexPath.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row > 5) { // Your conditions here to choose between Books and Movies
BookCell *cell = [tableView dequeueReusableCellWithIdentifier:#"bookCell"];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"bookCell"];
}
return cell;
} else {
MovieCell *cell = [tableView dequeueReusableCellWithIdentifier:#"movieCell"];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"movieCell"];
}
return cell;
}
return nil;
}

A big thank you to everyone who helped, especially atticus who put the final piece together.
What ended up working was to change the structure of the data to be an array of dictionaries and then add a Key: type Value: book/movie/music to each entry. The data structure now looks like:
Array[0]: Dictionary: [Key: type Value: book], [Key: name Value: Physics];
Array[1]: Dictionary: [Key: type Value: music], [Key: name Value: Rock];
Then to configure the cells I did this:
NSString *CellIdentifier = #"Cell";
NSDictionary *object = [self.listOfStuff objectAtIndex:indexPath.row];
if ([[object objectForKey:#"type"] isEqualToString:#"book"]){
CellIdentifier = #"BookCell";
}else if ([[object objectForKey:#"type"] isEqualToString:#"music"]){
CellIdentifier = #"MusicCell";
}else if ([[object objectForKey:#"type"] isEqualToString:#"movie"]){
CellIdentifier = #"MovieCell";
}
each of these had a different tableviewcell in the storyboard. We figured out if you want to use any of the built-in cell features, like cell.textLabel.text you needed to do this:
cell.textLabel.text = [object objectForKey:#"name"];
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.opaque = NO;
Hope this helps someone else in the future.

In your storyboard, set your tableView to use Dynamic Prototypes. Then, create a cell for each kind of cell you're going to use. Customize those appropriately, and then give each one a unique Reuse Identifier in the inspector (e.g. MovieCell).
Then, in your tableview:cellForRowAtIndexPath: method, determine the appropriate kind of cell for the content at that index path. If the cell at indexPath should be a movie cell, you would create the cell using:
static NSString * MovieCellIdentifier = #"MovieCell"; // This must match the storyboard
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MovieCellIdentifier];
That will give you the cell you specified as "MovieCell" in your storyboard. If you need to customize it further, create a UITableViewCell subclass and specify that for the cell in Storyboard. Then, you can refer to the properties etc. here for customization by casting the returned cell:
MovieTableViewCell *movieCell = (MovieTableViewCell *)cell;
// customize

My opinion is that you should implement all your cell types in one cell. Which means you should have one UITableViewCell class and .xib for all of them. Then in cellForRowAtIndexPath, create the cell, then depending on the actual requirement, show/hide the subviews that makes up a book or a movie.
In such design, your cell will be somewhat heavyweight. However, it's still better than using several cell classes because this way cell reusing is more efficient ([UITableView dequeueReusableCellWithIdentifier]).
I believe UITableView is designed towards one type of cell. So if your layout varies, you can still put them in one place and use frame or show/hide to control cell behavior.

i think u should use the following as your cell identifier.
- (UITableViewCell *)tableView:(UITableView *)tmpTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdentifier = [NSString stringWithFormat:#"Cell%d%d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tmpTableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (nil == cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
// do ur stuff here
}
return cell;
}

Related

Store boolean value in UITableviewcell

How can I store a boolean value in each row of a UITableview. I need to retrieve the boolean value stored in the cell, when that particular cell is selected.
There are so many ways:
1. You can use UITableViewCell.tag property
2. You can create your own cell class inherited from UITableViewCell and add there normal property for you BOOL value
3. You can use array associated with your tableview and when you get cell selected, just use indexPath to find associated value in your array
etc.
I recommend to use tag property in UITableViewCell.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
// returns nil if cell is not visible or index path is out of range
{
static NSString *identifier = #"MyIndetifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
//make sure tu put here, or before return cell.
cell.tag = 0; //0 =NO, 1=YES;
return cell;
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
BOOL boolean = cell.tag; // return 0 or 1. based on what boolean you set on this particular row.
}
You probably have some other storage, where you keep things like the table cell title or subtitle. Store the boolean there. Use this to convert the bool to something you can put in an array or dictionary.
[NSNumber numberWithBool:YES]
For example, if you're using an NSArray of strings to store the titles, instead use an array of dictionaries. Each dictionary would have a "title" and (for example) "isActive" boolean (stored as an NSNumber).
NSMutableArray *tableData;
every table cell associate with a NSMutableDictionary store in tableData,
you can set a NSNumber(store bool) to the Dictionary.
You need to implement method of UITableView
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath;
// returns nil if cell is not visible or index path is out of range
{
//create cell here
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
return cell;
}
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//by using indexpath.row you can access the cell on which user clicked.
}

Using a dynamic custom UITableViewCell in XCode 4.2, with Storyboards and UISeachDisplayController

I've used Xcode 4.2 to create a storyboard-based iOS application.
One of my screens contains a UITableViewController, using dynamic custom cells.
So far - so good.
Now, I wanted to add a UISearchDisplayController to allow filtering my list.
For some reason, the UISearchDisplayController won't display my custom cells, and I can't find a way to force it...
This is what my cellForRowAtIndexPath method looks:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"QueueListCell";
QueueListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[QueueListTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
assert(cell);
if ([tableView isEqual:self.searchDisplayController.searchResultsTableView]) {
indexPath = [_indexPathsForSearchResults objectAtIndex:indexPath.row];
}
// Set up the cell...
NSDictionary* itemDict = [_ListItems objectAtIndex:indexPath.row];
cell.labelQueueName.text = [itemDict objectForKey:kQueueName];
cell.labelQueueNumItems.text = [[itemDict objectForKey:kQueueNumItems] stringValue];
return cell;
}
Any thoughts on how to get this working? I mean, my UISearchDisplayController table DOES show the correct number of results (I know that since I can click on them, and I added an NSLog to let me know what I'm clicking on...)
This is my table view
This is how the search display table looks like...
My problem/question is how to make the UISearchDisplayController table view show my custom cells?
Any help appreciated...
Reuven
Answer specific to query
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
For Complete example, have the sample code from apple's site.
Step by step illustration

how to show two different values in one row of UITable?

I have one NSMutableArray having two strings in it
how I want my result in a row of table with some space as like below
Hotel Royal ___ First
Hotel Taj _____ Second
here _ ----> spaces
if you can't understand my question, you can ask me again regarding it
Thanks in Advance, I do upvote and appreciate proper answer...
either you use a custom cell or you use plain UITableViewCells.
This is the code you could use for a standard UITableViewCell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
//cell.textLabel.text = [array1 objectAtIndex:indexpath.row]; // Hotel Royal
//cell.detailTextLabel.text = [array2 objectAtIndex:indexpath.row]; // First
cell.textLabel.text = [array objectAtIndex:0]; // Hotel Royal
cell.detailTextLabel.text = [array objectAtIndex:1]; // First
return cell;
}
instead of UITableViewCellStyleValue1 you can test if one of the other styles suits your needs. UITableViewCellStyleSubtitle or UITableViewCellStyleValue2
You have to add two different UILables to the contentview of the cell if you want the text to appear on same line.
Check this great tutorial for code samples: Easy custom UITableView drawing
Chapter Layout within the contentView answers your question.

UITableView not scrolling smoothly...(iPhone SDK) ..!

UITableView not scrolling smoothly...(iPhone SDK) ..!!
I have implemented UITableView DataSource and Delegate methods in an individual separate classes.(one for delegate and one for datasource) in main program i write only:
//assume that all objects are allocated
ObjTableView.dataSource=ObjDataSource;
ObjTableView.delegate = ObjDelegate;
[self.view addSubView: ObjTableView];
when i run this code , UITable view appears but when i try to scroll it, it doesn't scroll smoothly.
I have also checked that UITableViewCell doesn't redraw once the cell is initialized.
can any one tell me why this happens ? How can i solve this problem ??
From comments:
ListDataSource *ObjListDataSource = [[ListDataSource alloc]initWithArray:[[sender object] valueForKey:#"List"]];
ListDelegate *ObjListDelegate = [[ListDelegate alloc]initWithArray:[[sender object] valueForKey:#"List"]];
tblList = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460)];
tblList.dataSource = ObjListDataSource; tblList.delegate = ObjListDelegate;
[self.view addSubview:tblList]; [tblShopList release];
More from comments:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
UITableViewCell *cell = (UITableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom cell code
}
return cell;
}
More Information:
I have used NSNotification which notifies to current class when parsing is complete, after receiving notification , current class method calls DataSource, Delegate methods (which is defined in a separate class file).
So UItableViewCell customization (which is in ListDataSource) and table view(in current class) both are in different classes.
A problem is
NSString *CellIdentifier = [NSString stringWithFormat:#"%i",indexPath.row];
The id needs to be the same for all cells of the same class, otherwise you never reuse them. As you can see in most examples, it is indeed a constant in most (all?) cases.
Little explaination on the reuseIdentifier: every time a cell gets out of screen, you can reuse it instead of creating a new one. To reuse it, you need a cell in queue with the same identifier as the one you pass to dequeueReusableCellWithIdentifier. The way you did, the cells are never reused, because each id is unique (they may or may not be reused in case a row reappears on screen, depending on queue size, which is not configurable AFAIK). This is why personalization of the cell should happen OUTSIDE the "cell == nil" block. Long story short, you are using the reuseIdentifier not as intendend.
I think Michele is correct, but I would also add that it looks like you are doing your cell customization where the cell gets created. What you should be doing is something more like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = (UITableViewCell)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0,0,320,100) reuseIdentifier:CellIdentifier] autorelease];
//custom REUSABLE cell code here, e.g. text color, etc.
}
NSString *cellText = [dataArray objectAtIndex:indexPath.row]; //assuming you have a simple array for your data
cell.textLabel.text = cellText;
return cell;
}
I would also add that I'm not sure why you are able to run the app with the code you have here, since UITableViewCell cell = ... is an invalid initializer. It should be UITableViewCell *cell = ....
It would be helpful to see how you are customizing your cell, since without that it's hard to see what's happening.

How to populate a label field from a selected row in a uitableview

I have a uitableview that is populated from a sqlite query.
I want to select or click on a row and then display that row's value in a uilabel field. To show the user that the row was selected.
I also want to pass that value on to different controllers that will be called later.
Here is a copy of my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"psystem";
PSystem *psystem = [self.ppdm_systems objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell...
// self.accessoryType = UITableViewCellAccessoryCheckmark;
cell.textLabel.text = psystem.system_id;
return cell;
}
I took out the _label.text .... in my various experiments.
Now what is not working is the passing of the value to different controllers.
Using the example listed here, the source controller is TableViewController and is where the value is set. The target controller is DetailViewController.
I can pass the title of the tab bar in, but that's from TableView --> DetailView.
I am not sure how to pull from tableview; ie: Tableview <-- DetailView when I am in DetailView.
thx
In your UIViewController, implement:
- (MyObject *)valueForSelectedRow {
MyCell *cell = (MyCell *)[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.myObject;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
MyObject *object = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = object.myValue;
}
When you want to push a new view controller, you just call -valueForSelectedRow and then use that value to push the controller.
This is assumed that you have a UITableViewCell subclass, with a property set to some model object. When you don't have that and just set the text property, that NSString object will be your 'model' object, although it would be easier when your cells handle custom model objects.
EDIT: Thanks for editing your answer. I now have the information I need. In this line: cell.textLabel.text = psystem.system_id, you setup the cell by simply setting the textLabel's text property. This is what I described in the paragraph above. I always create a UITableViewCell subclass, with a property set the the complete PSystem object. When you assign a PSystem object to the cell, it will handle it's contents, so you can easily manage your view in the, well, view. That's a very compelled approach since you never have to look at the controller again to alter the view's contents.
However, it can be done the way you currently have it. It would look something like:
- (NSString *)valueForSelectedRow {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
return cell.textLabel.text;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Get value
NSString *value = [self valueForSelectedRow];
// Update the label, assumed that _label is a pointer to a UILabel view object.
_label.text = value;
}
In this case, your PSystem model has been replaced with an NSString object. For this, it's enough, but it could be so much easier to have the object itself. Okay, that can also be done by selecting the PSystem object again from the p_system array by the NSIndexPath, but things will become harder once you come up with more complex tableviews.