问题很简单:如何从Xib文件加载自定义UITableViewCell ?这样做允许您使用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的一个新子类,并为您想要的组件添加iboutlet。然后创建一个新的“Empty XIB”文件。在IB中打开Xib文件,添加一个UITableViewCell对象,将其标识符设置为“MyCellIdentifier”,并将其类设置为MyCell并添加组件。最后,将iboutlet连接到组件。注意,我们没有在IB中设置文件的所有者。
其他方法主张设置文件的所有者,如果Xib没有通过额外的工厂类加载,则警告内存泄漏。我在Instruments/Leaks下测试了上面的内容,没有发现内存泄漏。
那么从xib加载单元格的规范方法是什么呢?我们设置文件的所有者吗?我们需要工厂吗?如果是,工厂的代码是什么样子的?如果有多种解决方案,让我们来阐明每一种方案的优缺点。
下面是在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) {
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)
}
}
解释:
可重用协议从其类名生成单元ID。请确保遵循约定:cell ID == class name == nib name。
UITableViewCell符合可重用协议。
UITableView扩展抽象了通过nib或类注册单元格的区别。
使用的例子:
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
cellClasses.forEach(tableView.register)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
...
return cell
}
重新加载NIB是昂贵的。最好只加载一次,然后在需要单元格时实例化对象。注意,你可以添加UIImageViews等到nib,甚至多个单元格,使用这个方法(苹果的“registerNIB”iOS5只允许一个顶级对象- Bug 10580062
"iOS5 tableView registerNib:过度限制"
所以我的代码如下-你在NIB读取一次(在初始化,就像我做的或在viewDidload -随便什么。从那时起,您将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;
}
这个扩展需要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")