Friday, August 23, 2013

Increasing the target area of a table's DetailDisclose button with Xamarin.iOS

Can you make that button bigger? I went slightly under the hood in Xamarin.iOS to make an accessory button target area larger.

I'm finishing up an iPad app for our fall release schedule. One of the comments that came up in testing was that it was too hard for some people to hit the detail disclosure button in the tables. I was asked to make the button larger.

What we are talking about is the standard blue circle with a white ">" in the center. Something like this:

The target area that responds to finger touches is about 44x44 pixels, centered around the button. We wanted to increase that target area to make it easier to reach. My first pass was to create new button that was a larger size. That worked, but it didn't look like the standard detail disclosure button (actually, it looked hideous, but that's another story). I prefer to use the standard iOS imagery unless I have a good reason not to. If you have spent any amount of time with an iPhone or an iPad, you have seen that button before and you know that touching it will provide you with more details. The more familiar a UI is, the less training the user will need.
On a side note: If you want to create a custom button and want to use imagery that will scale between Retina and non-Retina displays, take a look at PaintCode. I was able to take my vector based image and convert it to code and created a button that drew the imagery at runtime at the device resolution. I don't often need that tool, but I'm glad I have it.

I decided to take another approach. Instead of changing the image, I would make the target area around the button wider. The button will look the same, but clicking more of the white area around that button would trigger the button. And it wasn't hard to do.

We are going to looking at some Xamarin.iOS C# code. This should also work with Objective-C, just with different syntax.

I started out with a custom table cell

public class MyCustomCell : UITableViewCell
 public UIButton detailDisclosureButton;

 public MyCustomCell (string Action, string Status, NSString identKey) : base (UITableViewCellStyle.Subtitle, identKey)
  detailDisclosureButton = UIButton.FromType (UIButtonType.DetailDisclosure);
  detailDisclosureButton.Frame = new RectangleF (0f, 0f, 120f, 44f);
  detailDisclosureButton.ImageEdgeInsets = new UIEdgeInsets (0f, 120f - 44f, 0f, 0f);

  AccessoryView = detailDisclosureButton;

  UpdateCell (Action, Status);

 public void UpdateCell(string Action, string Status)
  TextLabel.Text = Action;

  DetailTextLabel.Text = Status;

The constructor creates a new SubTitle cell. I then add a UIButton of type DetailDisclosure. The button then gets a new Frame that is much wider, 120px, than the default size. That will give the user a wider target to hit. Since the default is to left align the image, we need to shift the image inside the frame. For that we add a new UIImageEdgeSets, to set the margin on the drawing rectangle for the image. By setting the left inset to the new width minus the old width, we align the image to the right side of the frame.

The last thing to do is to set the AccessoryView property of the cell to the new button, that will let the default code draw the button in the right place and we do not need to know how wide the cell is at runtime. What you lose is the AccessoryButtonTapped event is no longer called, we have to handle that functionality.

The next step would be define the table. I'm using a UITableViewSource and I override the GetCell method to create the custom cell. In addition to creating the custom cell, I needed to assign a TouchUpInside event to the new button as the default accessory behavior will no longer handle that button.

public class MyCustomTableSource : UITableViewSource
 protected List tableItems = new List();
 protected string cellIdentifier = "myTableCell";

 public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
  MyCustomCell cell = (MyCustomCell)(tableView.DequeueReusableCell (cellIdentifier));

  var Action = SomeMethodToReturnTitle(tableItems [indexPath.Row]);
  var Status = SomeMethodToReturnSubTitle(tableItems [indexPath.Row]);

  if (cell == null) {
   // Create our custom cell
   cell = new MyCustomCell(Action, Status, new NSString (cellIdentifier));

   // Set the tag property of the button to the current row.
   // If you are doing sections and rows, then I would subclass the UIButton and
   // add Section and Row properties to make the code easier to work with
   cell.detailDisclosureButton.Tag = indexPath.Row;

   // Set the TouchUpInside event to check the button's tag property to figure out
   // which row triggered the event.  And then do something
   cell.detailDisclosureButton.TouchUpInside += delegate(object sender, EventArgs e) {
    EditAction (tableItems[(sender as UIButton).Tag]);
  else {
   cell.UpdateCell(Action, Status);

  return cell;

 public void EditAction(MyCustomItem thisAction)
  // Do something here

With GetCell, we create or update an instance of our custom cell. When the cell gets created, I assign the table row to the Tag property of the button. Since we are not using the default code for adding a DetailDisclosure button, we have to handle the touch event. Right after the cell is created, the TouchUpInside event of the custom button gets a new delegate assign to it. I cast the sender as the UIButton and access the Tag property to get the selected row.

My code only had a single section in the table, so I only needed to track the table row. If I had needed to know the current section, I would have created a UIButton descendant and added Section and Row properties. That would make the code a little cleaner. The end result is the user gets the a button with the default look and feel, but with a larger target area.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.