为什么要定制表格?

  表格在很多iPhone应用程序中都是必需的UI元素。虽然对于应用程序开发而言,这并非是一项新发明,鉴于设备尺寸等方面的限制,表格在iPhone中的功能是非常固定的。

  苹果在其SDK中,直接内置了很多风格来让你定制表格。不过,在你最初创建表格的时候,它看起来非常简单。在没有进行任何定制的时候,你可以为表格选择两种基本风格,默认风格和分组风格:

  在对表格中的单元格进行一点调整后,你就可以添加图标和说明文字:

  你甚至能改变单元格的字体和颜色,然而,有时候这样还是不足够。如果你真的想完全改变基本的风格,创建一个复杂的UI,那么你必须创建自己的自定义单元格控件。下面的截图是定制后的效果,这个应用程序使用完全定制的单元格来显示内容:

  幸好,苹果让我们有便捷的方法来完成这样的定制,就是允许我们创建自己的定制UITableViewCell 控件,并把其用于UITableView 控件中。下面,让我们来逐步创建一个应用程序,学习如何利用UITableView 和UITableViewCell 控件来创建上图所示效果的例子。

  示例应用程序

  那么接下来,我们就来创建应用程序。首先,打开MonoDevelop,如下图所示:

  接着,新建一个iPhone MonoTouch项目。从;File菜单中,选择;New Soluton:

  从C# iPhone 模板中选择;iPhone Window-based Project,输入解决方案的名称;Example_CustomUITableViewCells:

  在项目特性对话框上点击;OK,这些内容和我们要做的应用程序无关:

  我们的项目现在创建好了,如下图所示:

  通过在Solution Explorer窗口里双击MainWindow.xib,以便在Interface Builder中打开它,之后你会看到如下内容:

  我们首先拖一个UITableView控件到我们的MainWindow中,效果如下:

  我们需要在AppDelegate类中添加一个outlet,以便我们能编程访问这个Table View。

  在Library窗口中,选择顶部的;Classes标签页,接着在下拉列表中选择;Other Classes。确保AppDelegate已经选中,在Library窗口的下半部点击;Outlets标签页:

  让我们来创建一个名为;tblMain的outlet,通过点击;+按钮,在名称列输入;tblMain,你的outlet应该类似下图的:

  接着,把outlet关联到我们之前添加的UITableView上。在Document窗口中选择AppDelegate,在Connections Inspector窗口中,就会看到新创建的tblMain  outlet:

  从连接管理器中把右侧的outlet的圆点拖动到Document窗口或Window Designer的表格上,就可以把它们关联到一起:

  现在,我们得到了一个关联过的表格,让我们回到MonoDevelop,编写一些代码。

  在项目上按右键,在上下文菜单中选择;Add,接着选择;New Folder,把文件夹命名为;Code;。

接着创建一个命名为;BasicTableViewItem.cs的类。即,在项目上再次按右键,在上下文菜单中选择;Add,接着选择;New File。命名类,并点击;New:

  在这个代码文件中,输入如下代码:

namespace 
    Example_CustomUITableViewCells
{
 
   // 
   ================================== 
   
 
   /// 
     
   <summary> 
   
 
   /// 
    Represents our item in the table
 
   /// 
     
   </summary> 
   
 
   
 
   public 
     
   class 
    BasicTableViewItem
{
 
   public 
     
   string 
    Name {  
   get 
   ;  
   set 
   ; }

 
   public 
     
   string 
    SubHeading {  
   get 
   ;  
   set 
   ; }

 
   public 
     
   string 
    ImageName {  
   get 
   ;  
   set 
   ; }

 
   public 
    BasicTableViewItem ()
{
}
}
 
   // 
   ==================================== 
   
 
   
} 
 
  这个类相当简单,它表示了一个在单元格中的项目,并具有如下属性:
• Name 这个项目所显示的文本
• SubHeading 显示在项目名称下面的文本
• ImageName 这个项目所显示的图片名称
  我们将在后面会看到如何使用这个类。
  接着,在;Code文件夹中,创建另外一个类,命名为;BasicTableViewItemGroup,并输入如下代码:
 
  
  
using 
    System;
 
   using 
    System.Collections.Generic;

 
   namespace 
    Example_CustomUITableViewCells
{
 
   // 
   ======================================================================== 
   
 
   /// 
     
   <summary> 
   

 
   /// 
    A group that contains table items
 
   /// 
     
   </summary> 
   
 
   public 
     
   class 
    BasicTableViewItemGroup

{
 
   public 
     
   string 
    Name {  
   get 
   ;  
   set 
   ; }

 
   public 
     
   string 
    Footer {  
   get 
   ;  
   set 
   ; }

 
   public 
    List 
   < 
   BasicTableViewItem 
   > 
    Items
{
 
   get 
    {  
   return 
     
   this 
   ._items; }
 
   set 
    {  
   this 
   ._items  
   = 
    value; }
}
 
   protected 
    List 
   < 
   BasicTableViewItem 
   > 
    _items  
   = 
     
   new 
    List 
   < 
   BasicTableViewItem 
   > 
   ();

 
   public 
    BasicTableViewItemGroup ()
{
}
}
 
   // 
   ======================================================================== 
   
 
   
} 
 
  这也是一个相当简单的类。它用于项目分组的容器。具有如下属性:
• Name 分组的名称。通常显示在分组的顶部。
• Footer 显示在当前组中所有项目的底部文字
• Items 分组所包含的BasicTableViewItem的List<>集合。我们在其构造的时候实例化这个集合,以便在添加项目的时候无需手动去实例化它。
  接着,再在同一个文件夹中创建另外一个类,命名为;BasicTableViewSource,并输入如下代码:
 
  
  
using 
    System;
 
   using 
    System.Collections.Generic;
 
   using 
    MonoTouch.UIKit;

 
   namespace 
    Example_CustomUITableViewCells
{
 
   // 
   ======================================================================== 
   
 
   /// 
     
   <summary> 
   

 
   /// 
    Combined DataSource and Delegate for our UITableView
 
   /// 
     
   </summary> 
   
 
   public 
     
   class 
    BasicTableViewSource : UITableViewSource

{
 
   // 
   ---- declare vars 
   
 
   protected 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    _tableItems;
 
   string 
    _cellIdentifier  
   = 
     
   " 
   BasicTableViewCell 
   " 
   ;

 
   public 
    BasicTableViewSource (List 
   < 
   BasicTableViewItemGroup 
   > 
    items)
{
 
   this 
   ._tableItems  
   = 
    items;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to determine how many sections(groups) there are.
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   int 
    NumberOfSections (UITableView tableView)
{
 
   return 
     
   this 
   ._tableItems.Count;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to determine how many cells to create for that particular section.
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   int 
    RowsInSection (UITableView tableview,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Items.Count;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to retrieve the header text for the particular section(group)
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   string 
    TitleForHeader (UITableView tableView,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Name;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to retrieve the footer text for the particular section(group)
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   string 
    TitleForFooter (UITableView tableView,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Footer;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to get the actual UITableViewCell to render for the particular section and row
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
    UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
 
   // 
   ---- declare vars 
   
 
   
UITableViewCell cell  
   = 
    tableView.DequeueReusableCell ( 
   this 
   ._cellIdentifier);

 
   // 
   ---- if there are no cells to reuse, create a new one 
   
 
   if 
    (cell  
   == 
     
   null 
   )
{
cell  
   = 
     
   new 
    UITableViewCell (UITableViewCellStyle.Default,  
   this 
   ._cellIdentifier);
}

 
   // 
   ---- create a shortcut to our item 
   
 
   
BasicTableViewItem item  
   = 
     
   this 
   ._tableItems[indexPath.Section].Items[indexPath.Row];

cell.TextLabel.Text  
   = 
    item.Name;

 
   return 
    cell;
}
}
 
   // 
   ======================================================================== 
   
 
   
} 
 
  UITableViewSource 类负责处理我们数据的绑定,也处理用户和表格的交互。在我们的例子中,处理用户交互超出本篇文章的范围,不过我们依然需要完成数据绑定的功能。
  这个类比其他两个稍微有点复杂,它的成员逐一说明如下:
• 变量声明我们声明了两个变量,_tableItems,用于获取表格对象的本地变量,_cellIdentifier,在后面在重用表格单元格的时候提供一个关键字。
• 构造器构造器接受一个名为items 的参数,其类型是List<BasicTableViewItemGroup>。在我们的例子中,我们强制类和项目分组一起实例化,以便我们确信他们都是有效的。
• NumberOfSections 这个方法用于获取在表格中创建的分组数目。在我们的例子中,就返回_tableItems的分组数目。
• RowsInSection 这个方法用于获取表格中特定分组的项目数目。根据特定的分组索引,返回其包含项目的数量。
• TitleForHeader 这个方法返回某个分组标头的文本。就是返回当前分组对象BasicTableViewItemGroup 的Name 属性。
• TitleForFooter 这个方法返回某个分组标脚的文本。类似TitleForHeader,不过这次是返回Footer属性。
• GetCell 这个方法根据特定分组和行返回实际的UITableCell 控件,这也是数据绑定大量工作发生的地方。首先,我们在相关的UITableView上调用DequeueReusableCell 。这样做主要是因为性能原因。因为iPhone限制了处理器的耗能,如果每次显示都必须创建新的UITableViewCell 的话,在遇到很长列表的时候可能会变得非常慢。为了解决这个问题,UITableView 控件中后台维护着一个UITableViewCell 控件缓存池,在单元格滚动出可视范围的时候,它会放入到这个缓存池中以备重用。这也就是_cellIdentifier变量的用武之处。
  好,现在我们编写好了相关的类,就要开始添加一些图片,以便可以在项目上显示图标。
  首先,在项目中类似之前创建;Code文件夹那样,创建一个名为;Images的文件夹。把如下的图片按照对应的名称逐一保存到你的硬盘上:
 图片
名称
PawIcon.png
LightBulbIcon.png
PushPinIcon.png
TargetIcon.png
  一旦保存好,就要把他们添加到项目中。在;Images文件夹上点右键,选择;Add Files。选择你保存好的图片,勾中;Override default build action,在下拉列表中选择;Content。这很重要。Content是让你在运行时可以加载并使用它们的唯一构建动作。
  你的解决方案现在应该如下所示:
  为了确保你之前的步骤都是正确的,可以通过编译项目来检验。要进行编译,按住Apple Key(苹果键),再按;B键,或者从;Build菜单中选择Build All;。
  一切正常的话,我们就准备好了相关的类和图片。让我们来编辑应用程序代码以使用它们!
  在Main.cs上双击,显示出代码编辑器。在AppDelegate类中,添加如下代码行:
 
  
  
BasicTableViewSource _tableViewSource; 
 
 
  This is a class variable to hold our BasicTableViewSource that we created earlier.
  这是一个类变量,用来保存我们早期创建的BasicTableViewSource 实例。
  接着,把如下方法复制到AppDelegate类中:
 
  
  
/// 
     
   <summary> 
   
 
   /// 
    Creates a set of table items.
 
   /// 
     
   </summary> 
   
 
   protected 
     
   void 
    CreateTableItems ()
{
List 
   < 
   BasicTableViewItemGroup 
   > 
    tableItems  
   = 
     
   new 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    ();

 
   // 
   ---- declare vars 
   
 
   
BasicTableViewItemGroup tGroup;

 
   // 
   ---- birds 
   
 
   tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Birds 
   " 
   , Footer  
   = 
     
   " 
   Birds have wings, and sometimes use them. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Crow 
   " 
   , SubHeading  
   = 
     
   " 
   AKA, Raven. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Chicken 
   " 
   , SubHeading  
   = 
     
   " 
   Males are called roosters. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Turkey 
   " 
   , SubHeading  
   = 
     
   " 
   Eaten at thanksgiving. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- fish 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Fish 
   " 
   , Footer  
   = 
     
   " 
   Fish live in water. Mostly. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Trout 
   " 
   , SubHeading  
   = 
     
   " 
   Rainbow is a popular kind. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Salmon 
   " 
   , SubHeading  
   = 
     
   " 
   Good sushi. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Cod 
   " 
   , SubHeading  
   = 
     
   " 
   Flat fish. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- mammals 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Mammals 
   " 
   , Footer  
   = 
     
   " 
   Mammals nurse their young. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Deer 
   " 
   , SubHeading  
   = 
     
   " 
   Bambi. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Bats 
   " 
   , SubHeading  
   = 
     
   " 
   Fly at night. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   this 
   ._tableViewSource  
   = 
     
   new 
    BasicTableViewSource(tableItems);
} 
 
  我们将调用这个方法来创建数据源,完成在表格中显示数据的过程。
  最终,在AppDelegate 类中的FinishedLaunching 方法中,在调用window.MakeKeyAndVisible 之前加入如下代码。
 
  
  
this 
   .CreateTableItems (); 
 
   this 
   .tblMain.Source  
   = 
     
   this 
   ._tableViewSource;  
 
  通过调用这个方法,我们就创建了一个表格数据源,并把数据源赋值给表格。
  最终的Main.cs应该像这样:
 
  
  
using 
    System;
 
   using 
    System.Collections.Generic;
 
   using 
    System.Linq;
 
   using 
    MonoTouch.Foundation;
 
   using 
    MonoTouch.UIKit;

 
   namespace 
    Example_CustomUITableViewCells
{
 
   public 
     
   class 
    Application

{
 
   static 
     
   void 
    Main ( 
   string 
   [] args)
{
UIApplication.Main (args);
}
}

 
   // 
    The name AppDelegate is referenced in the MainWindow.xib file. 
   
 
   public 
     
   partial 
     
   class 
    AppDelegate : UIApplicationDelegate

{
BasicTableViewSource _tableViewSource;

 
   // 
    This method is invoked when the application has loaded its UI and its ready to run 
   
 
   public 
     
   override 
     
   bool 
    FinishedLaunching (UIApplication app, NSDictionary options)
{
 
   this 
   .CreateTableItems ();
 
   this 
   .tblMain.Source  
   = 
     
   this 
   ._tableViewSource;

 
   // 
    If you have defined a view, add it here:

 
   // 
    window.AddSubview (navigationController.View); 
   
 
   window.MakeKeyAndVisible ();

 
   return 
     
   true 
   ;
}

 
   // 
    This method is required in iPhoneOS 3.0 
   
 
   public 
     
   override 
     
   void 
    OnActivated (UIApplication application)
{
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Creates a set of table items.
 
   /// 
     
   </summary> 
   
 
   protected 
     
   void 
    CreateTableItems ()
{
List 
   < 
   BasicTableViewItemGroup 
   > 
    tableItems  
   = 
     
   new 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    ();

 
   // 
   ---- declare vars 
   
 
   
BasicTableViewItemGroup tGroup;

 
   // 
   ---- birds 
   
 
   tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Birds 
   " 
   , Footer  
   = 
     
   " 
   Birds have wings, and sometimes use them. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Crow 
   " 
   , SubHeading  
   = 
     
   " 
   AKA, Raven. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Chicken 
   " 
   , SubHeading  
   = 
     
   " 
   Males are called roosters. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Turkey 
   " 
   , SubHeading  
   = 
     
   " 
   Eaten at thanksgiving. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- fish 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Fish 
   " 
   , Footer  
   = 
     
   " 
   Fish live in water. Mostly. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Trout 
   " 
   , SubHeading  
   = 
     
   " 
   Rainbow is a popular kind. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Salmon 
   " 
   , SubHeading  
   = 
     
   " 
   Good sushi. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Cod 
   " 
   , SubHeading  
   = 
     
   " 
   Flat fish. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- mammals 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Mammals 
   " 
   , Footer  
   = 
     
   " 
   Mammals nurse their young. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Deer 
   " 
   , SubHeading  
   = 
     
   " 
   Bambi. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Bats 
   " 
   , SubHeading  
   = 
     
   " 
   Fly at night. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   this 
   ._tableViewSource  
   = 
     
   new 
    BasicTableViewSource(tableItems);
}

}
} 
 
  让我们来运行下应用程序,看看什么样子。要运行应用程序,按住苹果键,并按Enter按钮,或者从Run菜单中选择Debug。
  当我们运行它之后,就可以看到下图所示效果:
  脚标看上去稍显怪异。让我们来把表格的类型改为Grouped 后重新运行一下。要这样做,需要在Interface Builder中打开MainWindow.xib ,然后在Document窗口上点击Table View来编辑表格风格,接着在Inspector窗口,通过风格下拉列表来改变为;Grouped:
  保存文件,切换回MonoDevelop,再次运行应用程序,你就可以看到如下效果了:
  现在脚标就看着顺眼多了。
  让我们再来处理其他地方。在项目中要显示一些图片和子标题,那么下面就要着手编写。
  停止应用程序,接着双击BasicTableViewSource.cs 类来编辑它。在GetCell method方法中把如下代码行:
 
  
  
cell  
   = 
     
   new 
    UITableViewCell (UITableViewCellStyle.Default,  
   this 
   ._cellIdentifier); 
 
 
  改为:
 
  
  
cell  
   = 
     
   new 
    UITableViewCell (UITableViewCellStyle.Subtitle,  
   this 
   ._cellIdentifier); 
 
 
  这样就把UITableViewCell 的风格改变为Subtitle ,其会在单元格中显示文本的第二行。
  你能选用的只有很少的风格。然而要注意,只有在Default 和Subtitle 风格中才支持图片。
  现在,在这行之后:
 
  
  
cell.TextLabel.Text  
   = 
    item.Name; 
 
 
  增加:
 
  
  
cell.Accessory  
   = 
    UITableViewCellAccessory.Checkmark;
cell.DetailTextLabel.Text  
   = 
    item.SubHeading;
 
   if 
   ( 
   ! 
   string 
   .IsNullOrEmpty(item.ImageName))
{
cell.ImageView.Image  
   = 
    UIImage.FromFile( 
   " 
   Images/ 
   " 
     
   + 
    item.ImageName );
} 
 
  UITableViewCells 的原来样子就会被改变为如下所示:
  UITableViewCell 控件原本带有两个主要部分,单元格主体区,其是单元格显示内容的全部区域;单元格内容区,其通常用来放置内容在里面。如果附加区未使用的话,单元格内容区就会扩展占满整个单元格主体区。类似地,如果图片区不使用的话,标签区会覆盖图片的地方。
  让我们来看 一下如何在代码中使用这些区域。第一行代码告诉单元格,右边会有一个Checkmark 。接着我们添加SubHeading 文本到DetailTextLabel 上。最后,我们在单元格上设置一个图片。
  很容易就可以通过文件来创建图片对象。简单地使用UIImage.FromFile构造函数,把图片文件的路径传递就去。由于之前把图片文件的编译类型设为Content ,只要项目中的文件夹结构就是文件系统的,那么就可以容易地访问它们。 
  现在运行应用程序,就得到下面的效果:
  非常好。如果在Interface Builder 中把表格风格改回Plain(双击打开MainWindow.xib ,选择表格,在下拉列表中选择风格为;Plain,并保存),那么再次运行,就得到:
  还是很漂亮的。
  在iPhone OS 3.0之后,我们也能定义一些其他属性,包括TextColor、SelectedTextColor、SelectedImage、BackgroundView、SelectedBackgroundView等等。这虽然让你有了更多的个性化设置,不过有时候,我们需要完全不同的显示效果。
  要完成这个工作,必须创建自己的表格单元格。苹果允许我们通过继承UITableViewCell ,并创建其中我们所想的任何内容。
  那么,让我们来创建自定义的表格样式。首先,在项目中创建一个名为;Controls的新文件夹。在其中,我们打算用View Controller来创建新视图。在Controls文件夹上点右键,选择;Add,接着;New File,从左栏选择;iPhone并在右边选择;View Interface Definition with Controller,并命名为;CustomTableViewCell:
  在Interface Builder打开CustomTableViewCell.xib 。就会显示空白视图:
  我们不想使用标准视图,而要创建一个UITableViewCell,所以我们会删除这个视图并用一个单元格对象来替换它。
  按住苹果键(Apple),按退格键,或者从编辑窗口中选择;Delete就可以从Document窗口中删除这个视图。
  接着,从Library窗口中拖一个Table View Cell到你的Document窗口中:
  如果你在新添加到表格单元格上双击,就会打开设计器,你会看到一个设计界面来让你添加控件:
  那么,现在让我们来使用Interface Builder来设计自定义单元格。拖动右下角的让单元格变高点。接着,从Library窗口,拖一个UIImageView 控件和几个UILabels控件:
  你可以使用Attributes Inspector 窗口来改变UILabel 控件的大小,以及颜色等等属性。不用看上去完全和上图一样,只需相关的元素都在对应位置就行。
  现在,我们完成了自定义的设计,就来为自定义单元格添加outlet,和File’s Owner的内容,以便我们能访问它们。之前,我们把outlet直接添加到AppDelegate中,不过这里,要把他们添加到File’s Owner中,以便可以在所具有的视图上创建类。
  在Document窗口中选择;File’s Owner,接着在Library窗口中在顶部选择;Classes,在下拉列表中选择;Other Classes。选择我们的CustomTableViewCell 类,接着在窗口的下半部选择;Outlets标签页。添加名为;cellMain、;lblHeading、;lblSubHeading和;imgMain:的Outlets:
  把Outlets和我们之前添加的控件关联起来,就像对表格控件所做的那样。
  确保Document窗口的;File’s Owner;是被选中的,从Connection Inspector中把圆点拖到Document窗口的控件上:
  最后一件我们必须在Interface Builder中完成的事情,就是提供一个单元格标识。在Document窗口上点击Table View Cell;,接着在Attributes Inspector上把Identifier 属性设置为MyCustomTableCellView;:
  这让我们的自定义单元格模板可以被重用,正如之前我们在代码中实现的那样。
  我们已经完成了Interface Builder中事情了,那么保存文件,返回到MonoDevelop中。双击CustomTableCellView.xib.cs 打开这个类文件。
  我们要对这个类进行一点编辑。首先,我们来添加一些属性到类里面:
 
  
  
public 
    UITableViewCell Cell
{
 
   get 
    {  
   return 
     
   this 
   .cellMain; }
}

 
   public 
     
   string 
    Heading
{
 
   get 
    {  
   return 
     
   this 
   .lblHeading.Text; }
 
   set 
    {  
   this 
   .lblHeading.Text  
   = 
    value; }
}


 
   public 
     
   string 
    SubHeading
{
 
   get 
    {  
   return 
     
   this 
   .lblSubHeading.Text; }
 
   set 
    {  
   this 
   .lblSubHeading.Text  
   = 
    value; }
}


 
   public 
    UIImage Image
{
 
   get 
    {  
   return 
     
   this 
   .imgMain.Image; }
 
   set 
    {  
   this 
   .imgMain.Image  
   = 
    value; }
} 
 
  我们来看一下这些属性:
• Cell 这个属性让这个自定义单元格的调用者可以直接访问单元格本身,以便在UITableViewSource的GetCell 调用中,我们能直接从一个属性简单地返回,过一会我们会看到怎么弄。
• Heading 和SubHeading  这个属性暴露了可以直接访问表格单元格中UILabel 控件的文本值。
• Image  这个属性直接暴露表格单元格中的UIImage 控件。
  接下来,我们需要编辑其中一个构造器。文件在被MonoDevelop创建好后,文件中的最后一个构造器看起来如下:
 
  
  
public 
    CustomTableViewCell () :  
   base 
   ( 
   " 
   CustomTableViewCell 
   " 
   ,  
   null 
   )
{
Initialize ();
} 
 
  这是一个非常简单的构造器,如果你读过Mono文档关于基类构造说明的话,就会明白这里它会初始化这个类,并用我们传递进去的名称来加载Nib(编译好的.xib文件)文件。然而,文档中没有提到的是,基类构造器是异步的。如果你使用这种方式来调用,那么Nib好像未被立刻加载,在我们给自定义单元格设置诸如Heading属性的时候,我们会得到讨厌的空引用错误。
  为了修正这个问题,我们必须确保Nib文件同步地加载,即直到加载完成,方法是不会返回。
  那么,就需要编辑构造器,以便看起来如下:
 
  
  
public 
    CustomTableViewCell () 
   // 
    : base("CustomTableViewCell", null) 
   
 
   {
 
   // 
   ---- this next line forces the loading of the xib file to be synchronous 
   
 
   MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ( 
   " 
   CustomTableViewCell 
   " 
   ,  
   this 
   ,  
   null 
   );
Initialize ();
} 
 
  在这里,我们首先要做到就是,把调用基类构造器的代码注释掉。我们会手动地进行来调用那个代码,所以在这里不用再调一次。接下来,我们添加一个对LoadNib方法的调用。它实际上和基类构造器所做的事情是一样的,除了不会强制进行异步处理。现在,我们确保了Nib以及任何东西都能初始化,以便我们能访问它。
  完整的类看上去如下所示:
 
  
  
using 
    System;
 
   using 
    MonoTouch.Foundation;
 
   using 
    MonoTouch.UIKit;
 
   using 
    System;

 
   namespace 
    Example_CustomUITableViewCells
{
 
   // 
   ======================================================================== 
   
 
   
 
   public 
     
   partial 
     
   class 
    CustomTableViewCell : UIViewController
{
 
   #region 
    Constructors 
   

 
   // 
    The IntPtr and initWithCoder constructors are required for controllers that need

 
   // 
    to be able to be created from a xib rather than from managed code 
   
 
   
 
   public 
    CustomTableViewCell (IntPtr handle) :  
   base 
   (handle)
{
Initialize ();
}

[Export( 
   " 
   initWithCoder: 
   " 
   )]
 
   public 
    CustomTableViewCell (NSCoder coder) :  
   base 
   (coder)
{
Initialize ();
}

 
   public 
    CustomTableViewCell () 
   // 
    : base("CustomTableViewCell", null) 
   
 
   
{
 
   // 
   ---- this next line forces the loading of the xib file to be synchronous 
   
 
   MonoTouch.Foundation.NSBundle.MainBundle.LoadNib ( 
   " 
   CustomTableViewCell 
   " 
   ,  
   this 
   ,  
   null 
   );
Initialize ();
}

 
   void 
    Initialize ()
{
}

 
   #endregion 
   

 
   public 
    UITableViewCell Cell
{
 
   get 
    {  
   return 
     
   this 
   .cellMain; }
}

 
   public 
     
   string 
    Heading
{
 
   get 
    {  
   return 
     
   this 
   .lblHeading.Text; }
 
   set 
    {  
   this 
   .lblHeading.Text  
   = 
    value; }
}

 
   public 
     
   string 
    SubHeading
{
 
   get 
    {  
   return 
     
   this 
   .lblSubHeading.Text; }
 
   set 
    {  
   this 
   .lblSubHeading.Text  
   = 
    value; }
}

 
   public 
    UIImage Image
{
 
   get 
    {  
   return 
     
   this 
   .imgMain.Image; }
 
   set 
    {  
   this 
   .imgMain.Image  
   = 
    value; }
}

}
 
   // 
   ======================================================================== 
   
 
   
} 
 
  好,现在构建好自定义单元格类了,接着往下走,来创建自定义UITableViewSource 类。
  在Code;文件夹中,创建一个新的类,复制如下代码进去:
 
  
  
using 
    System;
 
   using 
    System.Collections.Generic;
 
   using 
    MonoTouch.UIKit;

 
   namespace 
    Example_CustomUITableViewCells
{
 
   // 
   ======================================================================== 
   
 
   /// 
     
   <summary> 
   

 
   /// 
    Combined DataSource and Delegate for our UITableView with custom cells
 
   /// 
     
   </summary> 
   
 
   public 
     
   class 
    CustomTableViewSource : UITableViewSource

{
 
   // 
   ---- declare vars 
   
 
   protected 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    _tableItems;
 
   protected 
     
   string 
    _customCellIdentifier  
   = 
     
   " 
   MyCustomTableCellView 
   " 
   ;
 
   protected 
    Dictionary 
   < 
   int 
   , CustomTableViewCell 
   > 
    _cellControllers  
   = 
   
 
   new 
    Dictionary 
   < 
   int 
   , CustomTableViewCell 
   > 
   ();

 
   public 
    CustomTableViewSource (List 
   < 
   BasicTableViewItemGroup 
   > 
    items)
{
 
   this 
   ._tableItems  
   = 
    items;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to determine how many sections(groups) there are.
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   int 
    NumberOfSections (UITableView tableView)
{
 
   return 
     
   this 
   ._tableItems.Count;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to determine how many cells to create for that particular section.
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   int 
    RowsInSection (UITableView tableview,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Items.Count;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to retrieve the header text for the particular section(group)
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   string 
    TitleForHeader (UITableView tableView,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Name;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to retrieve the footer text for the particular section(group)
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   string 
    TitleForFooter (UITableView tableView,  
   int 
    section)
{
 
   return 
     
   this 
   ._tableItems[section].Footer;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to retreive the height of the row for the particular section and row
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   float 
    GetHeightForRow (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
 
   return 
    109f;
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Called by the TableView to get the actual UITableViewCell to render for the particular section and row
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
    UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
 
   // 
   ---- declare vars 
   
 
   
UITableViewCell cell  
   = 
    tableView.DequeueReusableCell ( 
   this 
   ._customCellIdentifier);
CustomTableViewCell customCellController  
   = 
     
   null 
   ;

 
   // 
   ---- if there are no cells to reuse, create a new one 
   
 
   if 
    (cell  
   == 
     
   null 
   )
{
customCellController  
   = 
     
   new 
    CustomTableViewCell ();
 
   // 
    retreive the cell from our custom cell controller 
   
 
   
cell  
   = 
    customCellController.Cell;
 
   // 
    give the cell a unique ID, so we can match it up to the controller 
   
 
   cell.Tag  
   = 
    Environment.TickCount;
 
   // 
    store our controller with the unique ID we gave our cell 
   
 
   this 
   ._cellControllers.Add (cell.Tag, customCellController);
}
 
   else 
   
{
 
   // 
    retreive our controller via it's unique ID 
   
 
   
customCellController  
   = 
     
   this 
   ._cellControllers[cell.Tag];
}

 
   // 
   ---- create a shortcut to our item 
   
 
   BasicTableViewItem item  
   = 
     
   this 
   ._tableItems[indexPath.Section].Items[indexPath.Row];

 
   // 
   ---- set our cell properties 
   
 
   
customCellController.Heading  
   = 
    item.Name;
customCellController.SubHeading  
   = 
    item.SubHeading;
 
   if 
    ( 
   ! 
   string 
   .IsNullOrEmpty (item.ImageName))
{
customCellController.Image  
   = 
    UIImage.FromFile ( 
   " 
   Images/ 
   " 
     
   + 
    item.ImageName);
}

 
   // 
   ---- return the custom cell 
   
 
   return 
    cell;
}

}
 
   // 
   ========================================================================} 
   
 
   
} 
 
  如果你浏览一遍这个代码,就看到它几乎和BasicTableViewSource 一样,只有少许改变。
  要注意到第一件事,是声明部分:
 
  
  
// 
   ---- declare vars 
   
 
   protected 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    _tableItems;
 
   protected 
     
   string 
    _customCellIdentifier  
   = 
     
   " 
   MyCustomTableCellView 
   " 
   ;
 
   protected 
    Dictionary 
   < 
   int 
   , CustomTableViewCell 
   > 
    _cellControllers  
   = 
    
 
   new 
    Dictionary 
   < 
   int 
   , CustomTableViewCell 
   > 
   (); 
 
  我们添加了一个名为_cellControllers新变量。我们将在后面看到如何使用它,它会保存着自定义单元格的集合。
  下一个增加的东西是GetHeightForRow 方法:
 
  
  
/// 
     
   <summary> 
   
 
   /// 
    Called by the TableView to retreive the height of the row for the particular section and row
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
     
   float 
    GetHeightForRow (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
 
   return 
    109f;
} 
 
  当我们创建自定义单元格控制器之后,我们让它比通常的单元格要高。在数据绑定的过程中,CocoaTouch 需要知道要给这个单元格分配多少空间。这就是要调用GetHeightForRow 方法的缘由。如果我们不告知新的大小,那么系统会按照标准尺寸去调整布局,我们就会看到怪异的结果。
  如果我们在Interface Builder中打开我们的单元格,并通过Size Inspector查看,会知道单元格的高度上多少。在我们的例子中,它是109像素,不过你的可能会有所不同。
  最后改变的一块地方是,GetCell方法:
 
  
  
/// 
     
   <summary> 
   
 
   /// 
    Called by the TableView to get the actual UITableViewCell to render for the particular section and row
 
   /// 
     
   </summary> 
   
 
   public 
     
   override 
    UITableViewCell GetCell (UITableView tableView, MonoTouch.FoundationNSIndexPath indexPath)
{
 
   // 
   ---- declare vars 
   
 
   
UITableViewCell cell  
   = 
    tableView.DequeueReusableCell ( 
   this 
   ._customCellIdentifier);
CustomTableViewCell customCellController  
   = 
     
   null 
   ;

 
   // 
   ---- if there are no cells to reuse, create a new one 
   
 
   if 
    (cell  
   == 
     
   null 
   )
{
customCellController  
   = 
     
   new 
    CustomTableViewCell ();
 
   // 
    retreive the cell from our custom cell controller 
   
 
   
cell  
   = 
    customCellController.Cell;
 
   // 
    give the cell a unique ID, so we can match it up to the controller 
   
 
   cell.Tag  
   = 
    Environment.TickCount;
 
   // 
    store our controller with the unique ID we gave our cell 
   
 
   this 
   ._cellControllers.Add (cell.Tag, customCellController);
}
 
   else 
   
{
 
   // 
    retreive our controller via it's unique ID 
   
 
   
customCellController  
   = 
     
   this 
   ._cellControllers[cell.Tag];
}

 
   // 
   ---- create a shortcut to our item 
   
 
   BasicTableViewItem item  
   = 
     
   this 
   ._tableItems[indexPath.Section].Items[indexPath.Row];

 
   // 
   ---- set our cell properties 
   
 
   
customCellController.Heading  
   = 
    item.Name;
customCellController.SubHeading  
   = 
    item.SubHeading;
 
   if 
    ( 
   ! 
   string 
   .IsNullOrEmpty (item.ImageName))
{
customCellController.Image  
   = 
    UIImage.FromFile ( 
   " 
   Images/ 
   " 
     
   + 
    item.ImageName);
}

 
   // 
   ---- return the custom cell 
   
 
   return 
    cell;
} 
 
  这个方法开始非常类似于BasicTableViewSource 类,我们尝试通过DequeueReusableCell 的调用来从缓存池中重用的一个单元格。从这之后,就变得复杂了。如果我们不能在重用的时候得到单元格,那么我们必须去创建一个新的。它就是由我们的CustomTableViewCell 类所创建的,它实际上是一个包含着自定义单元格的控制器。
  一旦我们创建了控制器,我们就把cell变量赋值控制器的Cell属性。
  我们接着为单元格添加了一个唯一标识符,以便我们在后面能把它和正确的CustomTableViewCell 控制器关联在一起。我们使用Environment.TickCount ,因为它提供了比较稳妥的唯一性值。如果我们想,也可以使用Guid.NewGuid,不过调用它稍微有点昂贵。
  接下来,我们使用相同的tag标识,来创建存储在CustomTableViewCell 的字典对象。
  这也就是我们下面这行代码显而易见的原因:
 
  
  
else 
   
{
 
   // 
    retreive our controller via it's unique ID 
   
 
   customCellController  
   = 
     
   this 
   ._cellControllers[cell.Tag];
} 
 
  如果我们找到一个可重用的单元格,那么就需要通过控制器来设置相关属性。为了这样,必须使用之前我们使用过的标识符从Dictionary中提取出来。
  到这里,已经涉及了对这个类的大部分改变。接下来的几行,为了改变控制器类中的属性,进行了相对于单元格本身较简单的修改。
  好了!我们快要完成了。打开Main.cs文件,我们对AppDelegate类进行两个地方的改变,让其使用我们的CustomTableViewSource 而非BasicTableViewSource。
  首先,注释掉这行:
 
  
  
BasicTableViewSource _tableViewSource; 
 
 
  并紧接着后添加这行:
 
  
  
CustomTableViewSource _tableViewSource; 
 
 
  接着,在CreateTableItems 方法中,注释掉这行:
 
  
  
this 
   ._tableViewSource  
   = 
     
   new 
    BasicTableViewSource(tableItems); 
 
 
  并紧接着后面添加这行:
 
  
  
this 
   ._tableViewSource  
   = 
     
   new 
    CustomTableViewSource(tableItems); 
 
 
  我们修改后的AppDelegate 类看起来如下:
 
  
  
// 
    The name AppDelegate is referenced in the MainWindow.xib file. 
   
 
   public 
     
   partial 
     
   class 
    AppDelegate : UIApplicationDelegate
{
 
   // 
   BasicTableViewSource _tableViewSource; 
   
 
   
CustomTableViewSource _tableViewSource;

 
   // 
    This method is invoked when the application has loaded its UI and its ready to run 
   
 
   public 
     
   override 
     
   bool 
    FinishedLaunching (UIApplication app, NSDictionary options)
{
 
   this 
   .CreateTableItems ();
 
   this 
   .tblMain.Source  
   = 
     
   this 
   ._tableViewSource;

 
   // 
    If you have defined a view, add it here:

 
   // 
    window.AddSubview (navigationController.View); 
   
 
   window.MakeKeyAndVisible ();

 
   return 
     
   true 
   ;
}

 
   // 
    This method is required in iPhoneOS 3.0 
   
 
   public 
     
   override 
     
   void 
    OnActivated (UIApplication application)
{
}

 
   /// 
     
   <summary> 
   

 
   /// 
    Creates a set of table items.
 
   /// 
     
   </summary> 
   
 
   protected 
     
   void 
    CreateTableItems ()
{
List 
   < 
   BasicTableViewItemGroup 
   > 
    tableItems  
   = 
     
   new 
    List 
   < 
   BasicTableViewItemGroup 
   > 
    ();

 
   // 
   ---- declare vars 
   
 
   
BasicTableViewItemGroup tGroup;

 
   // 
   ---- birds 
   
 
   tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Birds 
   " 
   , Footer  
   = 
     
   " 
   Birds have wings, and sometimes use them. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Crow 
   " 
   , SubHeading  
   = 
     
   " 
   AKA, Raven. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Chicken 
   " 
   , SubHeading  
   = 
     
   " 
   Males are called roosters. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Turkey 
   " 
   , SubHeading  
   = 
     
   " 
   Eaten at thanksgiving. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- fish 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Fish 
   " 
   , Footer  
   = 
     
   " 
   Fish live in water. Mostly. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Trout 
   " 
   , SubHeading  
   = 
     
   " 
   Rainbow is a popular kind. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Salmon 
   " 
   , SubHeading  
   = 
     
   " 
   Good sushi. 
   " 
   , ImageName  
   = 
     
   " 
   PawIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Cod 
   " 
   , SubHeading  
   = 
     
   " 
   Flat fish. 
   " 
   , ImageName  
   = 
     
   " 
   LightBulbIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   ---- mammals 
   
 
   
tGroup  
   = 
     
   new 
    BasicTableViewItemGroup() { Name  
   = 
     
   " 
   Mammals 
   " 
   , Footer  
   = 
     
   " 
   Mammals nurse their young. 
   " 
    };
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Deer 
   " 
   , SubHeading  
   = 
     
   " 
   Bambi. 
   " 
   , ImageName  
   = 
     
   " 
   PushPinIcon.png 
   " 
    });
tGroup.Items.Add ( 
   new 
    BasicTableViewItem() { Name  
   = 
     
   " 
   Bats 
   " 
   , SubHeading  
   = 
     
   " 
   Fly at night. 
   " 
   , ImageName  
   = 
     
   " 
   TargetIcon.png 
   " 
    });
tableItems.Add (tGroup);

 
   // 
   this._tableViewSource = new BasicTableViewSource(tableItems); 
   
 
   
 
   this 
   ._tableViewSource  
   = 
     
   new 
    CustomTableViewSource(tableItems);
}
}

  运行这个应用程序,就能看到下面的效果:

  如果你在Interface Builder中把表格风格改为Grouped,保存改变,再次运行程序,你就会看到下图:

  恭喜!做了这么多的工作,终于完成了,不过你也学到了如何在MonoTouch中自定义表格。现在,我们已经让其运行起来了,来看一下在进行自定义单元格的时候,要考虑到一些事项。

  性能考虑

  如果没有正确地处理自定义单元格的开发,在使用UITableView 的时候会造成严重的性能问题。特别在你有很多行的时候更是如此。iPhone,虽然令人印象深刻,不过却只有性能有限的处理器,而为了获得用户所期望的较好响应能力,你应该考虑几个性能优化方式。

  另外,尽早部署到设备上经常性地评估自定义表格的性能,是一种可取的做法。模拟器就是一个模拟器而已,并非真正的设备。它比设备运行的要快得多,如果你依赖于模拟器来测量应用程序的性能,那么你很可能会在部署到设备上的时候大失所望。

  而且,在你测试自定义表格的时候,如果用户能添加自己的行,那么你应该测试应用程序有很多行的性能。如果它依然;活蹦乱跳,具有很好的响应,那么你就能确信你已经开发正确,而你的用户应该不会对性能失望。

  • 单元格重用 - 单元格重用是获得具有良好响应表格的第一个技术。特别在具有大量的行的时候,更是需要。如果你不重用单元格,那么iPhone OS不得不在每个单元格显示的时候去创建一个新的实例。这马上就会变成一个严重的问题。为了确定你的单元格是否正被重用,建议在单元格重用代码中使用Console.WriteLine 把重用的过程打印到应用程序控制台。确保在第一次显示界面的时候,添加了足够多的行,以便在滚动屏幕的时候,OS有机会重用老的已经被滚动出屏幕的单元格。本文章的例子就展示了如何正确地重用单元格,如果你的单元格没有重用,研究一下代码示例,确保所有实现都是正确的。
  • 缓存行高 - 表格会经常性地获取行的高度。实际上,它会在每次单元格创建的时候都获取一遍,在某些时候甚至会更频繁。如果你基于单元格内容来计算行高,那么确保要缓存这个值,以便你不必总是计算它。实际上,你可能会在单元格控制器中创建一个属性,计算,然后缓存之。
  • 缓存图片 - 如果你正使用图片,考虑缓存它们,以便不必每次显示的时候都从文件中载入。如果单元格之间会共享图片,可以考虑把他们放到一个字典对象中,并从里面来获取图片实例。要小心不要加载太多图片。用到可供你支配的iPhone内存的一半是不太正常的,因此如果你有大量的图片,你就要做出选择,是从文件中加载它们,还是在缓存字典中只保留一定数量的图片,没有的时候再从文件中加载。
  • 远离透明效果 - 在iPhone上执行的一个非常昂贵的操作就是渲染透明效果。在iPhone上有两个绘图系统,CoreGraphics和CoreAnimation。CoreGraphics 利用GPU,而CoreAnimation根据要处理内容的快慢也利用主处理器,不过通常主要使用GPU。iPhone的GPU的问题在于它没有针对颜色混合进行优化。因此,你应该尽可能尝试避免透明效果。如果你确实不能避免,那么你应该通过重写DrawRect 方法来自己完成混合过程,并直接处理绘图计算,关于这点我们将在下一条中详细讲到。
  • 手动在DrawRect中绘制单元格 - 要解决上面提到的性能问题的最后努力,就是重写DrawRect 方法,自己执行绘图。然而这也有缺点。首先,在技术上相对复杂,第二,它会占用大量内存。就这点而言,它更适合那些没有大量行的表格,不过这样也会占用大量的处理器资源。对这个技术更多的信息,可以参考苹果示例程序的第15个例子TableViewSuite,下面会给出注释。
  • 避免复杂的图形计算 - 正如透明效果问题一下,尽量远离那些需要计算的图形元素,比如在显示的时候把图片处理为渐变效果。
  • 编程创建单元格 - 如果你已经想方设法进行优化了,依然还是需要进一步提升性能,那么你可以不用Interface Builder来创建自定义UITableViewCell ,而是编程手写这个控件。关于编程构建视图的更多信息,可访问Craig Dunn的博客帖子。要学习如何不使用Interface Builder来创建MonoTouch应用程序,可以阅读MonoTouch.info里面列出的文章,在这里:http://monotouch.info/Tags/NoIB

  展示进行性能优化的更多例子,可以访问苹果的TableViewSuite示例应用程序。

  它虽然是用Objective-C编写的,不过概念是相同的。