Wednesday, January 30, 2013

Customize that UIViewCell – Part 1: Using Interface Builder http://www.iphonedevcentral.com/customize-that-uiviewcell-part-1-using-interface-builder/

http://www.iphonedevcentral.com/customize-that-uiviewcell-part-1-using-interface-builder/

Customize that UIViewCell – Part 1: Using Interface Builder

If you followed my first tutorial on UITableView (link | source code), you now have a very simple app that shows a list of DVD titles and clicking any of the titles shows you a detail view page with more information about the DVD.  That's all nice but we really want to make it a little bit prettier. We could display the length of each movie right on the listing page. Also, we have this coverImage field in our data set, let's use it.

What we want is for our home screen to look like this:

tutorial006

We can accomplish that by customizing UITableViewCell. There are 2 ways of going about it:

  1. Using Interface Builder
  2. Programmatically

In reality, you can also choose a hybrid approach where you create some UI elements in the Interface Builder and some programmatically.

Which option to choose?

The two approaches both bring some advantages and disadvantages with them.

Interface Builder

If you decide to go the Interface Builder route, you'll find it very easy to create and customize your cells. Any subsequent edits can also be done quite easily since you're simply rearranging elements visually. The downside is speed and performance since the system needs to render each view in a cell individually. If your table view has thousands of rows in it, this may/may not affect the performance of your app, depending how complicated your cell is.

Programmatically

This one involves a lot more work. You are responsible for creating each UI element by hand in the code. That can be very tedious and any edits you need to make in the future require code changes. Also, you'll need to set up all the autosizing masks yourself. The upside is performance. Since the system will draw each cell as one view, the performance gain can be very significant.

Ok, let's get to it…

1. Subclass UITableViewCell

Create a Cocoa Touch Class file and make it a subclass of UITableViewCell. Let's name it DVDListingViewCell.m. We'll use this class as a placeholder for our labels and the cover image. In DVDListingViewCell.h add the following code:

@interface DVDListingViewCell : UITableViewCell {      IBOutlet UILabel *titleLabel;      IBOutlet UIImageView *coverImageView;      IBOutlet UILabel *featureLengthLabel;  }     @property (nonatomic, retain) UILabel *titleLabel;  @property (nonatomic, retain) UILabel *featureLengthLabel;  @property (nonatomic, retain) UIImageView *coverImageView;     @end

We use the IBOutlet annotation here to later let Interface Builder know we want to connect these properties with the actual UI elements we'll lay out.

We also need synthesize the properties we've just defined. This will create all setters and getters for us. Add this toDVDListingViewCell.m right below the @implementation directive.

@synthesize titleLabel, featureLengthLabel, coverImageView;

2. Design the cell in Interface Builder

In Xcode, double-click on any of the nib files (they should all be listed under NIB Files magic folder). Once in Interface Builder, chooseNew… from the File menu. You should be presented a dialog with several templates in it. From the Cocoa Touch category on the left, choose Empty.

tutorial007

You should now see an "Untitled" window with File's Owner and First Responder in it. One thing missing right now is the actual cell view, let's add it. From the Library window (Tools -> Library) locate Table View Cell. Drag it to the "Untitled" window. You should now see something like this:

tutorial008

Double clicking on the Table View Cell object will open up the actual cell view where we're going to add our UI elements. Make the cell 120 pixels in height (you change the dimensions under the size tab in the properties window or by pressing Command  + 3). Drag a couple Lables and an Image View to the cell to lay it out as shown below. Once done, save the file as DVDListingView. Make sure to save the file in your project directory and check the checkbox when prompted if you want to add the file into your project.

tutorial009

3. Prepare the RootViewController

Return back to Xcode and open up your RootViewController.h header file. We will add a cell instance variable of type DVDListingViewCell that we've created earlier. We will use this cell to actually draw on the screen. Modify your header file to make it look like this:

@interface RootViewController : UITableViewController {      DvdLibraryDao *dao;         IBOutlet DVDListingViewCell *_cell;  }

You may notice the IBOutlet annotation again. That is because we will be connecting this variable to the actual cell we created in Interface Builder.

Let's switch to the implementation file of our RootViewController (RootViewController.m). You may remember the methodcellForRowAtIndexPath from the first tutorial. That's the method that controls what cell is drawn at which row. We'll want our cell to be a DVDListingViewCell so let's modify the current version of it to this:

  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {         static NSString *CellIdentifier = @"LibraryListingCell";         DVDListingViewCell *cell = (DVDListingViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];      if (cell == nil) {          [[NSBundle mainBundle] loadNibNamed:@"DVDListingView" owner:self options:nil];          cell = [_cell autorelease];          _cell = nil;      }         cell.titleLabel.text = [[dao libraryItemAtIndex:indexPath.row] valueForKey:@"title"];      cell.featureLengthLabel.text = [NSString stringWithFormat:@"%@ minutes",                                      [[dao libraryItemAtIndex:indexPath.row] valueForKey:@"featureLength"]];      cell.coverImageView.image = [UIImage                                   imageNamed:[[dao libraryItemAtIndex:indexPath.row] valueForKey:@"coverImage"]];         return cell;  }

There are few things going on so let's analyze them line by line.

Line #5 was changed to initialize the cell local variable to the DVDListingViewCell instead of the generic UITableViewCell.

Lines #6-#10 contain the meat of the cell creation. It first loads the nib file we created. You can see we're setting the owner to self. That is because the _cell instance variable will get initialized via the nib file, which I'm going to cover a little later.

And finally, Lines #12-#15 assign proper values to the labels and image view in our custom cell.

4. Connect everything in Interface Builder

Return back to Interface Builder and click on the File's Owner icon. Open up the Identity Inspector (Tools -> Identity Inspector) and look at the Class drop-down menu. Locate RootViewController and select it. In the same window, you should now see the _cell instance variable that belongs to RootViewController. When the nib file is loaded, we'll want the cell we designed in Interface Builder to be assigned to our _cell. Let's do that next.

Open up Connections Inspector (Tools -> Connections Inspector) with File's Owner icon still selected. You should see our _cell in theOutlets section. Next to it is a little hollow circle that turns into a plus (+) sign when you hover it. Click on the circle and drag over to the Listing View Cell window. When you drag over it, the cell should highlight with a label that says "Listing View Cell." Release the mouse to complete the connection (the cell will blink).

tutorial010

Note: There is an alternate way of creating the connection; Click on the File Owner's icon while pressing down the Control key and Ctrl-drag to the Listing View Cell icon. A little black pop-up window "Outlets" will appear. Select _cell and let go of the mouse.

tutorial011

Now let's connect our two labels and the image view. Highlight the Listing View Icon and open up Identity Inspector (Command+4). Under the Class drop-down find and select our DVDListingViewCell. Once done, you should see all the outlets we defined earlier in theDVDListingViewCell class: coverImageViewfeatureLengthLabel and titleLabel. Open up Connections Inspector (Command+2) and check out the Outlets section. Just as before, drag a connection from the hollow circle next to titleLabel to the "My DVD" label,featureLengthLabel to the feature length label and finally coverImageView to the UIImageView object in our Listing View Cell.

tutorial012

Alternatively, you can Control-drag from the Listing View Cell icon to the Listing View Cell window and connect the three objects that way.

tutorial013Save all your changes and return back to Xcode.

5. Set the cell height in RootViewController

After you've saved your changes in Interface Builder and return back to Xcode, go ahead and run the project. You'll see that everything works well except one thing – the cells are all crammed together.

tutorial014

This is because we need to explicitly set the height of cells in each row when it differs from the default. We can correct that by implementing the heightForRowAtIndexPath delegate method in the RootViewController.m file.

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {      return 120.0;  }

Rerun your project and you should now see the cells correctly sized and looking good.

tutorial006

Conclusion

Remember that this is just one of a couple ways to customize your cells. You can do all of this by hand, programmatically which is more work but may result in a better performance. We will cover that in the second part of this tutorial.

You can download the complete source code to this tutorial here: My DVD Library Xcode Project 02.



Custom UITableViewCell with UIButton: which button has been clicked? http://stackoverflow.com/questions/9579605/custom-uitableviewcell-with-uibutton-which-button-has-been-clicked

http://stackoverflow.com/questions/9579605/custom-uitableviewcell-with-uibutton-which-button-has-been-clicked

Custom UITableViewCell with UIButton: which button has been clicked?


I'm developing an iOS 4 application with latest SDK and XCode 4.2.

I have a UITableView with sections and with custom UITableViewCell. Every cell has a UIButton and all of these buttons has the same target for UIControlEventTouchUpInside.

This is my code:

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  {      static NSString* cellIdentifier = @"CalendarCell";        CalendarEventCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];        if (cell == nil)      {          NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"CalendarEventCell" owner:nil options:nil];            for(id currentObject in topLevelObjects)          {              if ([currentObject isKindOfClass:[CalendarEventCell class]])              {                  cell = (CalendarEventCell *)currentObject;                  [cell.addToCalendarButton addTarget:self action:@selector(addEventToiCal) forControlEvents:UIControlEventTouchUpInside];                  break;              }          }      }  ...  }

When user touch inside that button, how can I know on which section and row was the cell that has been clicked?

share|improve this question
You can able to set tag for that Button. By using that tag number you can able to find out which button is clicked. – Ganesh Mar 6 '12 at 7:42
feedback

Place a tag on the button. For example:

  CalendarEventCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];    if (cell == nil)  {      NSArray* topLevelObjects =  [[NSBundle mainBundle] loadNibNamed:@"CalendarEventCell" owner:nil options:nil];        for(id currentObject in topLevelObjects)      {          if ([currentObject isKindOfClass:[CalendarEventCell class]])          {              cell = (CalendarEventCell *)currentObject;              [cell.addToCalendarButton addTarget:self action:@selector(addEventToiCal) forControlEvents:UIControlEventTouchUpInside];              break;          }      }  }  cell.addToCalendarButton.tag = ((indexPath.section & 0xFFFF) << 16) |                                 (indexPath.row & 0xFFFF);

You will need to change your selector to @selector(addEventToiCal:) and update the method to -(void) addEventToiCal:(UIButton *)sender.

You can then add something like the following to -addEventToiCal:

  if (!([sender isKindOfClass:[UIButton class]]))      return;  NSUInteger section = ((sender.tag >> 16) & 0xFFFF);  NSUInteger row     = (sender.tag & 0xFFFF);  NSLog(@"Button in section %i on row %i was pressed.", section, row);
share|improve this answer
You could also declare the method as - (void) addEventToiCal:(UIButton *)sender and then remove the first line. – Alessandro Vendruscolo Mar 6 '12 at 7:48
@MisterJack I debated writing it that way. Since you seemed to second that idea, I updated the answer to reflect your suggestion. – David M. Syzdek Mar 6 '12 at 7:52
Thanks for your answer. I will need indexPath.section and indexPath.row. – VansFannel Mar 6 '12 at 7:58
Tagging method is easy, but doesn't scale very well, especially when one adds more controls to the cell that have to be identified in the controller and also you need to update the tags once the rows are added and removed. I've seen it cause bugs/problems too many times. – macbirdie Mar 6 '12 at 7:59
@VansFannel You can encode the section and row using something like: tag = ((indexPath.section & 0xFFFF) << 16) | (indexPath.row & 0xFFFF); and then decode them using something like:section = ((tag >> 16) & 0xFFFF); row = (tag & 0xFFFF); – David M. Syzdek Mar 6 '12 at 8:03
show 1 more comment
feedback

Set button's target to a method in the cell instead of setting the target to the controller itself, create a delegate protocol for the cell with a method like tappedButton:(UIButton *)button inCell:(UITableViewCell *)cell and set the controller as cell's delegate. In the target method call that delegate method.

Then in controller's delegate method implementation you can find out cell's NSIndexPath by callingUITableView's tableView:indexPathForCell:.

share|improve this answer
It could be easier to pass section and row to tappedButton: method instead of callingtableView:indexPathForCell:, isn't it? – VansFannel Mar 6 '12 at 8:34
Then you have to hold cell's NSIndexPath as its ivar/property - that's also possible. But you're unnecessarily duplicating data you have to maintain additionally - release it when cell dies, update it when e.g. cells above are added or removed and so on. However if you run into performance problems by runningtableView:indexPathForCell:, which is unlikely, you can use that "shortcut" of course. – macbirdieMar 6 '12 at 8:48
feedback
      Assign Tag value to button like that in cellForRowAtIndexPath method        1-cell.addToCalendarButton.tag=indexPath.row        2-When you add method to button also send the sender so assign method to button like that          [cell.addToCalendarButton addTarget:self action:@selector(addEventToiCal:)forControlEvents:UIControlEventTouchUpInside];          3-In your method read the relevant row like that         -(IBAction)addEventToiCal:(id)sender{        NSLOG("current row is %d",[sender tag]);          }      If you want to now about the section then indexPath do such thing then    - (void)addEventToiCal:(id)sender event:(id)event  {      NSSet *touches = [event allTouches];      UITouch *touch = [touches anyObject];      CGPoint currentTouchPosition = [touch locationInView:self.tableView];      NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];       NsLog("value of indePath.section %d ,indexPath.row %d",indexPath.section,indexPath.row);    }    Assign your method at cellforRowAtIndexPath Like that    [cell.addToCalendarButton addTarget:self action:@selector(addEventToiCal:event:)forControlEvents:UIControlEventTouchUpInside];
share|improve this answer
Thanks for your answer. I will need indexPath.section and indexPath.row. – VansFannel Mar 6 '12 at 7:58
feedback

BNRXIBCell is an excellent solution, for iOS 5 and above. It's a UITableViewCell subclass, intended to be subclassed, to forward action messages from cell subviews to a controller.

share|improve this answer