Tuesday, February 19, 2013

how to work with UITableViewCellAccessoryDetailDisclosureButton

http://hghltd.yandex.net/yandbtm?src=F&text=how%20to%20work%20with%20UITableViewCellAccessoryDetailDisclosureButton&url=http%3A%2F%2Fchris-software.com%2Findex.php%2Ftag%2Fuitableview%2F&fmode=inject&mime=html&l10n=ru&sign=e6ad769521a97d36e9ada08c33ad50ed&keyno=0

how to work with UITableViewCellAccessoryDetailDisclosureButton

Tables

May 5th, 2009

Tables – strictly UITableView, are very often used in utilities, content managers, lists and more. There are many examples of tables in applications that came with every iPhone:

  • Phone application: Favourites, Recents, Contacts
  • SMS application: list of contacts you recently text messaged (not sure about the chat bubbles – it can be UIScrollView)
  • Calendar application: list of Calendars, List view of selected calendar
  • Photos application: list of Photo Albums
  • YouTube application: every tab contains UITableView
  • Weather application: flip-side view where you can add cities
  • Notes application: list of notes
  • Settings application: whole application
  • App Store application: every tab
  • Clock application: World Clock, Alarm, Stopwatch

tableview1 tableview2 tableview3 tableview4 tableview5

As you see UITableView is not only a white list with black text on it. Table (strictlyUITableViewCell) can be customized in many ways, it can not only have many sections, headers, footers, edit and reorder functionality but even a custom view.

Let's start

Create a UIViewController and View XIB. Name them all whatever you like (in my project I use:MyView.hMyView.mMyView.xib). In App Delegate add standard lines:

#import "MyView.h"    @class MyView;

and:

	MyView *viewController = [[MyView alloc] initWithNibName:@"MyView" bundle:[NSBundle mainBundle]];  	[window addSubview:[viewController view]];

Open MyView.xib and set that it's class is MyView, add UITableView and UIToolbar orUINavigationBar on top – might be important later. The view should look like this:

interfacetableprepare

Don't close Interface Builder and edit MyView.h in Xcode. Add two protocols to UIViewController:UITableViewDelegate (responsible for a lot of actions with tables) and UITableViewDataSource(providing data to be displayed in the table). Add also an IBOutlet of UITableView. Although almost everything will be passed to table you created in Interface Builder via protocols, but this object might be helpful in some cases I will discuss later.

MyView.h

  #import <UIKit/UIKit.h>    @interface MyView : UIViewController <UITableViewDelegate, UITableViewDataSource> {  	IBOutlet UITableView *myTable;  }    @end

Compile your project and return to Interface Builder and make connections. myTable withUITableViewview with View and click on UITableView again, and in Table View Connections, connect dataSource and delegate with File's Owner. Save MyView.xib.

Before you can debug your application you need to implement two method that are required to use UITableView:

  1. numberOfRowsInSection – how many rows each section has
  2. cellForRowAtIndexPath – creates cells – UITableViewCell

So you need to implement above methods in your class:

  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  	return 20; // every section has 5 rows  }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  	NSLog(@"cellForRowAtIndexPath called");  	static NSString *CellIdentifier = @"Cell";  	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];  	if (cell == nil) { NSLog(@"new cell");  		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];  	}  	cell.text = [NSString stringWithFormat@"Cell #%i"indexPath.row];  	return cell;  }

simplestableview

If you Build & Go your project, you should get:

As you see numberOfRowsInSection method is quite obvious. In above example. I'm telling – preparing the table to display 8 rows in each section. As long as I didn't tell the table how many sections there are in the table it assumes that I want to have 1 section.cellForRowAtIndexPath is "a little bit" more complex. Go toDebugger Console (Command+Shift+R) to open logs. Inside this method I added two functions to log whenever the method was called and whenever the cell is empty object (nil). To better understand this change the numberOfRowsInSection to return 20 for example, Build & Go your project and study logs while scrolling theUITableViewConclusion: because of memory management and overall performance cellForRowAtIndexPath is only called to show the visible cells, and when you scroll back to see the cells that have been already loaded, cellForRowAtIndexPath is called again, but it remembers their settings and customization and reuse them instead of allocating again. If you don't understand this, treat it like a template you need to use in most cellForRowAtIndexPathmethod. indexPath gives you two information, what is the section of current cell (indexPath.section) and row (indexPath.row).

Dynamic data source

dynamicdatasimulatorIn this tutorial I will only show you simple examples of usingUITableView, but you could prepare NSMutableArray to be displayed in UITableView. To do so, first add an NSMutableArray in header of your class:

	NSMutableArray *dataForTable;

and in viewDidLoad method:

  - (void)viewDidLoad {  	dataForTable = [[NSMutableArray alloc] initWithObjects:  					@"First row",  					@"Second row",  					@"Third row",  					nil];  	[dataForTable retain];  	[super viewDidLoad];  }

Now, numberOfRowsInSection shouldn't return the static number 8, 20, or any other, but:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  	return [dataForTable count];  }

And cellForRowAtIndexPath shouldn't tell you what is the row's number, but the object (NSString) from NSMutableArray.

	cell.text = [dataForTable objectAtIndex:indexPath.row];

Please note: once you changed the objects in dataForTable you can call [myTable reloadData]; to redraw the table with new data.

Table Styles

tablestyleplaintablestylegrouped
There are two styles you can apply to UITableViewPlain (left, default) has "sticky headers",Grouped (right) has rounded edges and stripped, gray background. Altough, so far there was no headers displayed by your code you can compare these two styles in Phone and Settingsapplication. You can switch the style in Xcode, but as long as you are using Interface Builder, simply go to Table View Attributes and change the Style property.

Sections

By default there is one section in every UITableView. If you want more – seperate one group of rows from the other, you have to implement the numberOfSectionsInTableView method:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {      return 2;  }

Above method will gave us one of these two results:

sectionsplain sectionsgrouped

You should notice two things. In grouped style the space between sections is clearly visible, but not in plain style as long as you don't provide headers' names. The second: both sections are identical, that's because numbersOfRowsInSection is returning always the same value and the same is with cellForRowAtIndexPath. You can easily change it. You could do it dynamicaly, as shown before, but I will use switch instruction:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  	switch (section) {  		case 0:  			return 5;  			break;  		case 1:  			return 2;  			break;  	}  	return 0;  }

customsectionsAnd (instead of cell.text = [NSString ...);

	switch (indexPath.section) {  		case 0:  			cell.text = [NSString stringWithFormat:@"Happy Cell #%i",indexPath.row];;  			break;  		case 1:  			cell.text = [NSString stringWithFormat:@"Sad Cell #%i",indexPath.row];;  			break;  	}

This code should be easily understandable for you. I've not done anything else but used switch instruction to return / assign different values for given cases. Since now, I'm going to skip switchinstructions in sample codes, you should implement them on your own easily.

Header and footer

headerfootergrouped headerfooterplain

As you see the grouped UITableView looks amazing with headers and footers, but for plain style, you should use only headers because it doesn't look good - header and footer look exactly the same. To achieve something like this you need to implement these two methods:

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {  	return @"Section title";  }
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {  	return @"Footer title";  }

Please note: if you return nil header / footer won't be displayed

Section index

tableindexWhen you scroll the contact list, the small letters A-Z on the right are very helpful, aren't thay? It's very easy to implement something like this.

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {  	NSMutableArray *index = [[NSMutableArray alloc] initWithObjects:@"A",@"B",@"C",@"D",nil];  	return index;  }

In index array you have to provide that titles that will be displayed on the whole height of UITableView on the right. The titles don't have to be the same as the headers' titles (but their should, or be similar). If you provide more index titles than there are sections inUITableView, most of them will navigate to the bottom section.

Please note: sectionIndexTitlesForTableView should return aNSArray object, but you are returning NSMutableArray, don't worry,NSMutableArray is just a subclass of NSArray so this is acceptable and doesnn't cause any logical errors.

Please note: section index works only in plain style of UITableView.

Pressing the cell

cellpressedThe cell usually links to some actions. Here is the sample action to displays an alert with information what cell was selected.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Cell pressed" message:[NSString stringWithFormat:@"Cell #%i in section #%i",indexPath.row,indexPath.section] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];  	[alert show];  	[alert release];  }

And there is one "visual thing" you could add to maintain the nice aesthetics. Once cell (UITableViewCell) was pressed it stays selected - highlighted until you press any other cell or scroll theUITableView. Just add this line of code to above method to fix it:

	[tableView deselectRowAtIndexPath:indexPath animated:YES];

Accessory Buttons

accessorybuttonsAccessory Buttons are this nice tiny usually arrow-images on the right side of UITableViewCell with custom actions once tapped (only #2). There are 3 types of accessory buttons:

1. UITableViewCellAccessoryCheckmark
2. UITableViewCellAccessoryDetailDisclosureButton
3. UITableViewCellAccessoryDisclosureIndicator

- (UITableViewCellAccessoryType)tableView:(UITableView *)  tableView  accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath {  	return UITableViewCellAccessoryCheckmark;    }

If you don't want to display any accessory button for some cells simply return UITableViewCellAccessoryNone for them. If you used DetailDisclosureButton (nice blue button with arrow) you can perform some tasks, once it was tapped:

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {  	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Accessory Button pressed" message:[NSString stringWithFormat:@"Accessory Button for Cell #%i in section #%i",indexPath.row,indexPath.section] delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];  	[alert show];  	[alert release];  }

Please note: above method works only with UITableViewCellAccessoryDetailDisclosureButton and it doesn't call any method connected with selecting a row.

Height and indent

sizeplain sizegrouped

If needed you can change the height of any cell or it's indend as shown screenshots above. By default height=45 and indent=0.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {  	if (indexPath.row == 2) return 90;  	return 45;  }  - (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath {  	if (indexPath.row == 3) return 2;  	return 0;  }

Custom Cell

customcellibAs you see there are many possibilities of customizing the cell, but it's not enough. You can create a new .xib file and aUITableViewCell controller and design the cell like any viewso far.

In Xcode add new class of type UITableViewCell and View XIB (name them CustomCell.*).

In CustomCell.h any any IBOutlets you need, labels, slider, switches... whatever.

Open CustomCell.xib and delete the View and add addUITableViewCell to CustomCell.xib. In Table View Cell Identity set class = CustomCell (not File's Owner this time). If you want you can adjust the size in Custom Cell Size...

customcellsize

or by dragging the bottom right triangle. Set it's features in Custom Cell Attributes, add some objects - treat it like a typical UIView.

Please note: this time, UITableViewCustomCell is a class of CustomCell, not File's Owner as usuall. So when you want to make connections go to Custom Cell Connections, forget about File's Owner this time.

OK, your custom cell is prepared right now, now it's time to show it in the UITableView. First you need to import CustomCell.h to your class that manages table (#import "CustomCell.h" and@class CustomCell).

Now you have to reorganize the table. Let's assume that your custom cell will be displayed only once in seperated section - in numberOfSectionsInTableView tell that there will be one more section and in numbersOfRowsInSection return 1 for a given section (add next case for existingswitch).

Hope above wasn't difficult because this will be: now it's time to extend cellForRowAtIndexPathmethod. I'm like many times before using switch and I assume that custom cell will be displayed in 4th section:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  	// custom cell only  	if (indexPath.section == 3) {  		static NSString *CellIdentifier = @"CustomCell";  		CustomCell *cell = (CustomCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];  		if (cell == nil) {  			NSArray *outlets = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];  			for (id currentObject in outlets) {  				if ([currentObject isKindOfClass:[UITableViewCell class]]){  					cell =  (CustomCell *) currentObject;  					break;  				}  			}  		}  		cell.myLabel.text = @"Some text";  		return cell;  	}    	// other cells:  	// ...

So, when it comes to 4th section (indexPath.section == 3), the CustomCell nib (.xib) file is loaded, and in for loop, all it's objects are assigned as properties to cell. Although you set the size ofcustom cell in Interface Builder, but you must also tell UITableView to expect this by extendingheightForRowAtIndexPath method:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {  	if (indexPath.row == 2) return 90;  	else if (indexPath.section == 3) return 95;  	return 45;  }

customcellsimulator

Please note:

you can read more about UITableView in documentation: check descriptions ofUITableViewDelegate and UITableViewDataSource protocols. You will find there more options and methods allowing you to modify the UITableView - delete, add, edit, reorder rows and more.

Conclusion:

That was pretty much for the one tutorial. Hope you understand everything. Don't worry, tables are something that young developers try to avoid, but they are not so difficult at all. I provided for you a sample project. Remember to change occasionally in Interface Builder the style of UITableView(plain / grouped). Some methods are disabled by comments, you can enable them by yourself.

xcodeproj

Download the project

No comments:

Post a Comment