你如何从Xib文件加载自定义的UITableViewCells?

问题很简单:如何从Xib文件加载自定义的UITableViewCells? 这样做可以让您使用Interface Builder来设计单元格。 显然,由于记忆管理问题,答案并不简单。 这个线程提到了这个问题,并提出了一个解决方案,但是是NDA发布之前,并没有代码。 这里有一个很长的讨论话题,没有提供明确的答案。

以下是我使用过的一些代码:

static NSString *CellIdentifier = @"MyCellIdentifier"; MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil]; cell = (MyCell *)[nib objectAtIndex:0]; } 

要使用此代码,请创建MyCell.m / .h(UITableViewCell的一个新子类),并为所需的组件添加IBOutlets。 然后创建一个新的“空XIB”文件。 在IB中打开Xib文件,添加一个UITableViewCell对象,将其标识符设置为“MyCellIdentifier”,并将其类设置为MyCell并添加组件。 最后,将IBOutlets连接到组件。 请注意,我们没有在IB中设置文件所有者。

其他方法主张设置文件的所有者,并警告内存泄漏,如果没有通过额外的工厂类加载Xib。 我在Instruments / Leaks下测试了上面的内容,没有发现内存泄漏。

那么从Xibs加载单元格的规范方法是什么? 我们设置文件的所有者? 我们需要一个工厂吗? 如果是这样,工厂的代码是什么样的? 如果有多种解决方案,让我们来澄清他们每个人的利弊。

原始作者陈述的两种方法是由IB工程师推荐的 。

看到实际的职位了解更多详情。 我更喜欢方法#2,因为它似乎更简单。

方法#1:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"]; if (cell == nil) { // Create a temporary UIViewController to instantiate the custom cell. UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil]; // Grab a pointer to the custom cell. cell = (BDCustomCell *)temporaryController.view; [[cell retain] autorelease]; // Release the temporary UIViewController. [temporaryController release]; } return cell; } 

方法2:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"]; if (cell == nil) { // Load the top-level objects from the custom cell XIB. NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil]; // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain). cell = [topLevelObjects objectAtIndex:0]; } return cell; } 

更新(2014):方法2仍然有效,但没有任何文档。 它曾经是在官方文档,但现在被删除赞成故事板。

我在Github上发布了一个工作示例:
https://github.com/bentford/NibTableCellExample

正确的解决方案是这样的:

 - (void)viewDidLoad { [super viewDidLoad]; UINib *nib = [UINib nibWithNibName:@"ItemCell" bundle:nil]; [[self tableView] registerNib:nib forCellReuseIdentifier:@"ItemCell"]; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Create an instance of ItemCell PointsItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ItemCell"]; return cell; } 

采取肖恩Craver的答案,并清理了一下。

BBCell.h:

 #import <UIKit/UIKit.h> @interface BBCell : UITableViewCell { } + (BBCell *)cellFromNibNamed:(NSString *)nibName; @end 

BBCell.m:

 #import "BBCell.h" @implementation BBCell + (BBCell *)cellFromNibNamed:(NSString *)nibName { NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL]; NSEnumerator *nibEnumerator = [nibContents objectEnumerator]; BBCell *customCell = nil; NSObject* nibItem = nil; while ((nibItem = [nibEnumerator nextObject]) != nil) { if ([nibItem isKindOfClass:[BBCell class]]) { customCell = (BBCell *)nibItem; break; // we have a winner } } return customCell; } @end 

我制作了我所有的UITableViewCell的BBCell的子类,然后替换标准

 cell = [[[BBDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BBDetailCell"] autorelease]; 

有:

 cell = (BBDetailCell *)[BBDetailCell cellFromNibNamed:@"BBDetailCell"]; 

寄存器

在iOS 7之后,这个过程被简化为( swift 3.0 ):

 // For registering nib files tableView.register(UINib(nibName: "MyCell", bundle: Bundle.main), forCellReuseIdentifier: "cell") // For registering classes tableView.register(MyCellClass.self, forCellReuseIdentifier: "cell") 

)这也可以通过在.stroyboard.stroyboard文件中创建单元格作为原型单元格来实现。 如果你需要给它们添加一个类,你可以选择单元格原型并添加相应的类(当然,必须是UITableViewCell的后代)。

出列

后来,使用( swift 3.0 )出列:

 override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = "Hello" return cell } 

不同之处在于,这种新方法不仅使单元出队,而且还创建了不存在的(也就是说, if (cell == nil) shenanigans, if (cell == nil)不需要做),并且单元准备好使用在上面的例子中。

警告tableView.dequeueReusableCell(withIdentifier:for:)具有新的行为,如果你调用另一个(没有indexPath:你得到旧的行为,你需要检查nil和自己实例,注意UITableViewCell? 返回值。

 if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCellClass { // Cell be casted properly cell.myCustomProperty = true } else { // Wrong type? Wrong identifier? } 

当然,单元的关联类的类型是在UITableViewCell子类的.xib文件中定义的类型,或者使用其他注册方法。

组态

理想情况下,您的单元格已经在注册外观和内容定位(如标签和图像视图)方面进行了配置,在cellForRowAtIndexPath方法中,您只需填充它们即可。

全部一起

 class MyCell : UITableViewCell { // Can be either created manually, or loaded from a nib with prototypes @IBOutlet weak var labelSomething : UILabel? = nil } class MasterViewController: UITableViewController { var data = ["Hello", "World", "Kinda", "Cliche", "Though"] // Register override func viewDidLoad() { super.viewDidLoad() tableView.register(MyCell.self, forCellReuseIdentifier: "mycell") // or the nib alternative } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return data.count } // Dequeue override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyCell cell.labelSomething?.text = data[indexPath.row] return cell } } 

当然,这在ObjC中都有相同的名字。

我使用了bentford的方法2

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"]; if (cell == nil) { // Load the top-level objects from the custom cell XIB. NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil]; // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain). cell = [topLevelObjects objectAtIndex:0]; } return cell; } 

它可以工作,但要注意在自定义的UITableViewCell .xib文件中连接到File's Owner

通过在loadNibNamed语句中传递owner:self ,将UITableViewController设置为UITableViewController文件所有者。

如果您拖放到IB中的头文件来设置动作和插座,默认情况下它会将其设置为文件所有者。

loadNibNamed:owner:options ,Apple的代码会尝试在你的UITableViewController上设置属性,因为这是所有者。 但是你没有在那里定义这些属性,所以你会得到一个错误的关键是值的编码兼容

 *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<MyUITableViewController 0x6a383b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key myLabel.' 

如果事件被触发,你会得到一个NSInvalidArgumentException:

 -[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0' *** First throw call stack: (0x1903052 0x15eed0a 0x1904ced 0x1869f00 0x1869ce2 0x1904ec9 0x5885c2 0x58855a 0x62db76 0x62e03f 0x77fa6c 0x24e86d 0x18d7966 0x18d7407 0x183a7c0 0x1839db4 0x1839ccb 0x1f8b879 0x1f8b93e 0x585a9b 0xb904d 0x2c75) terminate called throwing an exceptionCurrent language: auto; currently objective-c 

一个简单的解决方法是将您的Interface Builder连接指向UITableViewCell而不是File's Owner:

  1. 右键单击文件的所有者来拉起连接列表
  2. 使用Command-Shift-4进行屏幕截图(拖动以选择要捕获的区域)
  3. 从文件所有者连接
  4. 右键单击对象层次结构中的UITableCell,然后重新添加连接。

我决定发帖,因为我不喜欢任何这些答案 – 事情总是可以更简单,这是迄今为止我找到的最简洁的方式。

1.如你所愿,在Interface Builder中构建你的Xib

  • 将文件的所有者设置为类NSObject
  • 添加一个UITableViewCell并将其类设置为MyTableViewCellSubclass – 如果您的IB崩溃(在本文写作时发生在Xcode 4中),只需使用Xcode 4中的接口的UIView(如果您仍然在其中)
  • 在这个单元格内布局你的子视图并将你的IBOutlet连接附加到.h或.m(@m是我的偏好)

2.在你的UIViewController或UITableViewController子类中

 @implementation ViewController static NSString *cellIdentifier = @"MyCellIdentier"; - (void) viewDidLoad { ... [self.tableView registerNib:[UINib nibWithNibName:@"MyTableViewCellSubclass" bundle:nil] forCellReuseIdentifier:cellIdentifier]; } - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyTableViewCellSubclass *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; ... return cell; } 

3.在你的MyTableViewCellSubclass

 - (id) initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { ... } return self; } 

如果您使用Interface Builder创建单元格,请检查您是否在Inspector中设置了标识符。 然后检查调用dequeueReusableCellWithIdentifier时是否一样。

我不小心忘记了在一个重磅的项目中设置一些标识符,性能的改变像日夜。

从XIB加载UITableViewCells保存了大量的代码,但通常会导致可怕的滚动速度(实际上,这不是XIB,而是过度使用UIViews)。

我建议你看看这个: 链接参考

下面是我用来从XIB创建自定义单元格的类方法:

 + (CustomCell*) createNewCustomCellFromNib { NSArray* nibContents = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:NULL]; NSEnumerator *nibEnumerator = [nibContents objectEnumerator]; CustomCell *customCell= nil; NSObject* nibItem = nil; while ( (nibItem = [nibEnumerator nextObject]) != nil) { if ( [nibItem isKindOfClass: [CustomCell class]]) { customCell = (CustomCell*) nibItem; if ([customCell.reuseIdentifier isEqualToString: @"CustomCell"]) { break; // we have a winner } else fuelEntryCell = nil; } } return customCell; } 

然后,在XIB中,我设置了类名,并重用了标识符。 之后,我可以在视图控制器中调用该方法而不是

 [[UITableViewCell] alloc] initWithFrame:] 

这足够快了,并在我的两个发货应用程序中使用。 比调用[nib objectAtIndex:0]更可靠,在我看来,至少比Stephan Burlot的例子更可靠,因为你保证只从一个正确类型的XIB中获取视图。

重新加载NIB是昂贵的。 最好加载一次,然后在需要单元格时实例化对象。 请注意,您可以添加UIImageViews等到笔尖,甚至多个单元格,使用此方法(苹果的“registerNIB”iOS5只允许一个顶级对象 – 错误10580062“iOS5 tableView registerNib:过度限制”

所以我的代码如下所示 – 你在NIB中读过一次(像初始化一样,或者在viewDidload中 – 不管怎么样,从此你将实例化nib到对象中,然后选择你需要的那个,这比加载nib一遍又一遍。

 static UINib *cellNib; + (void)initialize { if(self == [ImageManager class]) { cellNib = [UINib nibWithNibName:@"ImageManagerCell" bundle:nil]; assert(cellNib); } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *cellID = @"TheCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID]; if(cell == nil) { NSArray *topLevelItems = [cellNib instantiateWithOwner:nil options:nil]; NSUInteger idx = [topLevelItems indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { UITableViewCell *cell = (UITableViewCell *)obj; return [cell isKindOfClass:[UITableViewCell class]] && [cell.reuseIdentifier isEqualToString:cellID]; } ]; assert(idx != NSNotFound); cell = [topLevelItems objectAtIndex:idx]; } cell.textLabel.text = [NSString stringWithFormat:@"Howdie %d", indexPath.row]; return cell; } 

检查这个 – http://eppz.eu/blog/custom-uitableview-cell/ – 真正方便的方式使用一个微型的类,在控制器实现中结束一行:

 -(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath { return [TCItemCell cellForTableView:tableView atIndexPath:indexPath withModelSource:self]; } 

在这里输入图像描述

正确的解决办法是这样的

 - (void)viewDidLoad { [super viewDidLoad]; [self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"CustomCell"]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"]; return cell; } 

正确的方法是创建一个UITableViewCell子类实现,头和XIB。 在XIB中删除任何视图,只需添加一个表格单元格。 将该类设置为UITableViewCell子类的名称。 对于文件所有者,使其成为UITableViewController子类的名称。 使用tableViewCell插座将文件所有者连接到单元格。

在头文件中:

 UITableViewCell *_tableViewCell; @property (assign) IBOutlet UITableViewCell *tableViewCell; 

在执行文件中:

 @synthesize tableViewCell = _tableViewCell; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *kCellIdentifier = @"reusableCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier]; if (cell == nil) { [[NSBundle mainBundle] loadNibNamed:kCellIdentifier owner:self options:nil]; cell = _tableViewCell; self.tableViewCell = nil; } return cell; } 

我所做的是在控制器类中声明一个IBOutlet UITableViewCell *cell 。 然后调用NSBundle loadNibNamed类方法,它将UITableViewCell给上面声明的单元格。

对于xib,我将创建一个空的xib,并在IB中添加UITableViewCell对象,可以根据需要进行设置。 这个视图然后连接到控制器类中的单元IBOutlet

 - (UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"%@ loading RTEditableCell.xib", [self description] ); static NSString *MyIdentifier = @"editableCellIdentifier"; cell = [table dequeueReusableCellWithIdentifier:MyIdentifier]; if(cell == nil) { [[NSBundle mainBundle] loadNibNamed:@"RTEditableCell" owner:self options:nil]; } return cell; } 

NSBundle添加loadNibNamed(ADC登录)

cocoawithlove.com文章我从这个概念(获取电话号码示例应用程序)

首先导入你的自定义单元文件#import "CustomCell.h" ,然后改变如下所述的委托方法:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *simpleTableIdentifier = @"CustomCell"; CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier]; if (cell == nil) { NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil]; cell = [nib objectAtIndex:0]; [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; } return cell; } 

我不知道是否有一个规范的方式,但这里是我的方法:

  • 为ViewController创建一个xib
  • 将文件所有者类设置为UIViewController
  • 删除视图并添加一个UITableViewCell
  • 将您的UITableViewCell的类设置为您的自定义类
  • 设置你的UITableViewCell的标识符
  • 将视图控制器视图的出口设置为你的UITableViewCell

并使用此代码:

 MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil]; cell = (MyCustomViewCell *)c.view; [c release]; } 

在你的例子中,使用

 [nib objectAtIndex:0] 

如果Apple更改xib中的项目顺序,则可能会中断。

这是我的方法: 从XIB文件加载自定义的UITableViewCells …另一种方法

这个想法是创建UITableViewCell的SampleCell子类与IBOutlet UIView *content属性和每个自定义子视图的属性,你需要从代码配置。 然后创建一个SampleCell.xib文件。 在此nib文件中,将文件所有者更改为SampleCell。 添加一个内容UIView大小适合您的需求。 添加和配置所有你想要的子视图(标签,图像视图,按钮等)。 最后,将内容视图和子视图链接到文件所有者。

  NSString *CellIdentifier = [NSString stringWithFormat:@"cell %ld %ld",(long)indexPath.row,(long)indexPath.section]; NewsFeedCell *cell = (NewsFeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; cell=nil; if (cell == nil) { NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"NewsFeedCell" owner:nil options:nil]; for(id currentObject in topLevelObjects) { if([currentObject isKindOfClass:[NewsFeedCell class]]) { cell = (NewsFeedCell *)currentObject; break; } } } return cell; 

这个扩展需要Xcode7 beta6

 extension NSBundle { enum LoadViewError: ErrorType { case ExpectedXibToExistButGotNil case ExpectedXibToContainJustOneButGotDifferentNumberOfObjects case XibReturnedWrongType } func loadView<T>(name: String) throws -> T { let topLevelObjects: [AnyObject]! = loadNibNamed(name, owner: self, options: nil) if topLevelObjects == nil { throw LoadViewError.ExpectedXibToExistButGotNil } if topLevelObjects.count != 1 { throw LoadViewError.ExpectedXibToContainJustOneButGotDifferentNumberOfObjects } let firstObject: AnyObject! = topLevelObjects.first guard let result = firstObject as? T else { throw LoadViewError.XibReturnedWrongType } return result } } 

创建一个只包含1个自定义UITableViewCell的Xib文件。

加载它。

 let cell: BacteriaCell = try NSBundle.mainBundle().loadView("BacteriaCell") 

以下是在UITableView注册单元的通用方法:

 protocol Reusable { static var reuseID: String { get } } extension Reusable { static var reuseID: String { return String(describing: self) } } extension UITableViewCell: Reusable { } extension UITableView { func register<T: UITableViewCell>(cellClass: T.Type = T.self) where T: Reusable { let bundle = Bundle(for: cellClass.self) if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil { let nib = UINib(nibName: cellClass.reuseID, bundle: bundle) register(nib, forCellReuseIdentifier: cellClass.reuseID) } else { register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID) } } 

说明:

  1. Reusable协议从其类名生成单元ID。 请确保遵循惯例: cell ID == class name == nib name
  2. UITableViewCell符合Reusable协议。
  3. UITableView扩展通过nib或class抽象化注册单元格的差异。

用法示例:

 let tableView = UITableView() let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self] cellClasses.forEach(tableView.register) 
  1. UITableViewCell创建自己定制的类AbcViewCell子类(确保类文件名和nib文件名相同)

  2. 创建这个扩展类的方法。

     extension UITableViewCell { class func fromNib<T : UITableViewCell>() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T } } 
  3. 用它。

    let cell: AbcViewCell = UITableViewCell.fromNib()