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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/540345/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 15:38:09  来源:igfitidea点击:

How do you load custom UITableViewCells from Xib files?

iosuitableviewcocoa-touchxib

提问by DrGary

The question is simple: How do you load custom UITableViewCellfrom Xib files? Doing so allows you to use Interface Builder to design your cells. The answer apparently is not simple due to memory managment issues. This threadmentions the issue and suggests a solution, but is pre NDA-release and lacks code. Here's a long threadthat discusses the issue without providing a definitive answer.

问题很简单:如何UITableViewCell从 Xib 文件加载自定义?这样做允许您使用 Interface Builder 来设计您的单元格。由于内存管理问题,答案显然并不简单。这个线程提到了这个问题并提出了一个解决方案,但它是 NDA 发布前的并且缺少代码。这是一个很长的线程,在没有提供明确答案的情况下讨论了这个问题。

Here's some code I've used:

这是我使用过的一些代码:

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];
}

To use this code, create MyCell.m/.h, a new subclass of UITableViewCelland add IBOutletsfor the components you want. Then create a new "Empty XIB" file. Open the Xib file in IB, add a UITableViewCellobject, set its identifier to "MyCellIdentifier", and set its class to MyCell and add your components. Finally, connect the IBOutletsto the components. Note that we did not set the File's Owner in IB.

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

Other methods advocate setting the File's Owner and warn of memory leaks if the Xib is not loaded via an additional factory class. I tested the above under Instruments/Leaks and saw no memory leaks.

其他方法提倡设置 File's Owner 并在 Xib 未通过附加工厂类加载时警告内存泄漏。我在 Instruments/Leaks 下测试了上面的内容,没有看到内存泄漏。

So what's the canonical way to load cells from Xibs? Do we set File's Owner? Do we need a factory? If so, what's the code for the factory look like? If there are multiple solutions, let's clarify the pros and cons of each of them...

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

采纳答案by bentford

Here are two methods which the original author states was recommended by an IB engineer.

以下是原作者所说的IB工程师推荐的两种方法。

See the actual post for more details. I prefer method #2 as it seems simpler.

有关更多详细信息,请参阅实际帖子。我更喜欢方法#2,因为它看起来更简单。

Method #1:

方法#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;
}

Method #2:

方法#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;
}

Update (2014):Method #2 is still valid but there is no documentation for it anymore. It used to be in the official docsbut is now removed in favor of storyboards.

更新(2014 年):方法#2 仍然有效,但不再有相关文档。它曾经在官方文档中,但现在被删除以支持故事板。

I posted a working example on Github:
https://github.com/bentford/NibTableCellExample

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

edit for Swift 4.2

为 Swift 4.2 编辑

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    self.tblContacts.register(UINib(nibName: CellNames.ContactsCell, bundle: nil), forCellReuseIdentifier: MyIdentifier)
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier, for: indexPath) as! ContactsCell

    return cell
}

回答by giuseppe

The right solution is this:

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

- (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;
}

回答by Can

Register

登记

After iOS 7, this process has been simplified down to (swift 3.0):

在 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")

(Note) This is also achievable by creating the cells in the .xibor .stroyboardfiles, as prototype cells. If you need to attach a class to them, you can select the cell prototype and add the corresponding class (must be a descendant of UITableViewCell, of course).

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

Dequeue

出队

And later on, dequeued using (swift 3.0):

后来,使用(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
}

The difference being that this new method not only dequeues the cell, it also creates if non-existant (that means that you don't have to do if (cell == nil)shenanigans), and the cell is ready to use just as in the example above.

不同之处在于,这种新方法不仅使单元格出列,如果不存在,它还创建(这意味着您不必做if (cell == nil)恶作剧),并且单元格已准备好使用,就像上面的示例一样。

(Warning) tableView.dequeueReusableCell(withIdentifier:for:)has the new behavior, if you call the other one (without indexPath:) you get the old behavior, in which you need to check for niland instance it yourself, notice the UITableViewCell?return value.

(警告)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?
}

And of course, the type of the associated class of the cell is the one you defined in the .xib file for the UITableViewCellsubclass, or alternatively, using the other register method.

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

Configuration

配置

Ideally, your cells have been already configured in terms of appearance and content positioning (like labels and image views) by the time you registered them, and on the cellForRowAtIndexPathmethod you simply fill them in.

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

All together

全部一起

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
    }
}

And of course, this is all available in ObjC with the same names.

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

回答by vilcsak

Took Shawn Craver's answer and cleaned it up a bit.

接受了 Shawn Craver 的回答并将其清理了一下。

BBCell.h:

BBCell.h:

#import <UIKit/UIKit.h>

@interface BBCell : UITableViewCell {
}

+ (BBCell *)cellFromNibNamed:(NSString *)nibName;

@end

BBCell.m:

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

I make all my UITableViewCell's subclasses of BBCell, and then replace the standard

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

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

with:

和:

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

回答by funroll

I used bentford's Method #2:

我使用了本特福德的方法#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;
}

It works, but watch out for connections to File's Ownerin your custom UITableViewCell .xib file.

它可以工作,但要注意与自定义 UITableViewCell .xib 文件中文件所有者的连接。

By passing owner:selfin your loadNibNamedstatement, you set the UITableViewControlleras File's Owner of your UITableViewCell.

通过传递owner:self在你的loadNibNamed说法,你设置UITableViewController为您的文件拥有者UITableViewCell

If you drag and drop to the header file in IB to set up actions and outlets, it will set them up as File's Owner by default.

如果你拖放到IB中的头文件来设置actions和outlet,它会默认将它们设置为File's Owner。

In loadNibNamed:owner:options, Apple's code will try to set properties on your UITableViewController, since that's the owner. But you don't have those properties defined there, so you get an error about being key value coding-compliant:

在 中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.'

If an Event gets triggered instead, you'll get an NSInvalidArgumentException:

如果一个事件被触发,你会得到一个 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

An easy workaround is to point your Interface Builder connections at the UITableViewCellinstead of File's Owner:

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

  1. Right click on File's Owner to pull up the list of connections
  2. Take a screen capture with Command-Shift-4 (drag to select the area to be captured)
  3. x out the connections from File's Owner
  4. Right click on the UITableCell in the Object hierarchy and re-add the connections.
  1. 右键单击文件所有者以拉出连接列表
  2. 使用 Command-Shift-4 进行屏幕截图(拖动以选择要捕获的区域)
  3. x 从 File's Owner 中取出连接
  4. 右键单击 Object 层次结构中的 UITableCell 并重新添加连接。

回答by webstersx

I've decided to post since I don't like any of these answers -- things can always be more simple and this is by far the most concise way I've found.

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

1. Build your Xib in Interface Builder as you like it

1. 在 Interface Builder 中按照您的喜好构建您的 Xib

  • Set File's Owner to class NSObject
  • Add a UITableViewCell and set its class to MyTableViewCellSubclass -- if your IB crashes (happens in Xcode > 4 as of this writing), just use a UIView of do the interface in Xcode 4 if you still have it laying around
  • Layout your subviews inside this cell and attach your IBOutlet connections to your @interface in the .h or .m (.m is my preference)
  • 将文件的所有者设置为类 NSObject
  • 添加一个 UITableViewCell 并将其类设置为 MyTableViewCellSubclass -- 如果您的 IB 崩溃(在撰写本文时发生在 Xcode > 4 中),如果您仍然有它,只需在 Xcode 4 中使用 UIView
  • 在此单元格中布局您的子视图,并将您的 IBOutlet 连接附加到 .h 或 .m 中的@interface(.m 是我的偏好)

2. In your UIViewController or UITableViewController subclass

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. In your MyTableViewCellSubclass

3. 在你的 MyTableViewCellSubclass 中

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

    return self;
}

回答by Alex R. Young

If you're using Interface Builder to make cells, check that you've set the Identifier in the Inspector. Then check that it's the same when calling dequeueReusableCellWithIdentifier.

如果您使用 Interface Builder 制作单元格,请检查您是否已在 Inspector 中设置了标识符。然后在调用 dequeueReusableCellWithIdentifier 时检查它是否相同。

I accidentally forgot to set some identifiers in a table-heavy project, and the performance change was like night and day.

不小心忘了在一个表繁重的项目中设置一些标识符,性能变化就像白天和黑夜。

回答by Can Berk Güder

Loading UITableViewCells from XIBs saves a lot of code, but usually results in horrible scrolling speed (actually, it's not the XIB but the excessive use of UIViews that cause this).

从 XIB 加载 UITableViewCells 可以节省大量代码,但通常会导致滚动速度很糟糕(实际上,不是 XIB 而是过度使用 UIViews 导致了这种情况)。

I suggest you take a look at this: Link reference

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

回答by Shawn Craver

Here's the class method that I've been using for creating custom cells out of XIBs:

这是我一直用于从 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;
}

Then, in the XIB, I set the class name, and reuse identifier. After that, I can just call that method in my view controller instead of the

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

[[UITableViewCell] alloc] initWithFrame:]

It's plenty fast enough, and being used in two of my shipping applications. It's more reliable than calling [nib objectAtIndex:0], and in my mind at least, more reliable than Stephan Burlot's example because you're guaranteed to only grab a view out of a XIB that is the right type.

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

回答by Hamiz Ahmed

Correct Solution is this

正确的解决方案是这样的

- (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; 
    }