我试图在我正在工作的应用程序中使用故事板。在应用程序中有列表和用户,每个列表都包含其他列表的集合(列表的成员,用户拥有的列表)。因此,相应地,我有ListCell和UserCell类。目标是让那些是可重用的整个应用程序(即,在任何我的tableview控制器)。

这就是我遇到问题的地方。

我如何在故事板中创建一个可以在任何视图控制器中重用的自定义表视图单元格?

以下是我目前为止尝试过的具体方法。

In Controller #1, added a prototype cell, set the class to my UITableViewCell subclass, set the reuse id, added the labels and wired them to the class's outlets. In Controller #2, added an empty prototype cell, set it to the same class and reuse id as before. When it runs, the labels never appear when the cells are shown in Controller #2. Works fine in Controller #1. Designed each cell type in a different NIB and wired up to the appropriate cell class. In storyboard, added an empty prototype cell and set its class and reuse id to refer to my cell class. In controllers' viewDidLoad methods, registered those NIB files for the reuse id. When shown, cells in both controllers were empty like the prototype. Kept prototypes in both controllers empty and set class and reuse id to my cell class. Constructed the cells' UI entirely in code. Cells work perfectly in all controllers.

在第二种情况下,我怀疑原型总是覆盖NIB,如果我杀死了原型单元格,为重用id注册我的NIB就可以了。但这样我就不能从单元格设置segue到其他帧,这就是使用故事板的全部意义。

在一天结束的时候,我想要两件事:在故事板中连接基于tableview的流,并在视觉上定义单元格布局,而不是在代码中定义。到目前为止,我还不知道怎么把它们都弄到。


当前回答

尽管BJ Homer给出了很好的答案,但我觉得我有一个解决方案。从我的测试来看,它是有效的。

概念:为xib单元格创建自定义类。在那里,您可以等待触摸事件并以编程方式执行segue。现在我们所需要的是对执行Segue的控制器的引用。我的解决方案是在tableView:cellForRowAtIndexPath:中设置它。

例子

我有一个DetailedTaskCell。xib包含一个我想在多个表视图中使用的表单元格:

这个单元格有一个自定义类TaskGuessTableCell:

这就是奇迹发生的地方。

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

我有多个segue,但它们都有相同的名称:“完成任务”。如果您需要灵活一点,我建议添加另一个属性。

ViewController看起来是这样的:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

也许有更优雅的方式来达到同样的效果,但它确实有效!:)

其他回答

我找到了一种方法,为相同的VC加载单元格,没有测试segue。这可能是在单独的nib中创建单元格的一种变通方法

假设你有一个VC和两个表,你想在故事板中设计一个单元格,并在两个表中使用它。

(例如:一个表和一个搜索字段,一个UISearchController的结果表,你想在两者中使用相同的Cell)

当控制器要求单元格这样做:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * identifier = @"CELL_ID";

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

这里有来自故事板的单元格

如果我没有理解错的话,这个问题很简单。在你的故事板中创建一个UIViewController,它将保存你的原型单元格,并创建一个静态共享实例,从故事板中加载自己。要处理视图控制器segue,使用手动segue出口和触发表视图委托didSelectRow(手动segue出口是故事板中视图控制器顶部的中间图标,在'First Responder'和'Exit'之间)。

XCode 12.5, iOS 13.6

// A cell with a single UILabel

class UILabelCell: UITableViewCell {
    @IBOutlet weak var label: UILabel!
}

// A cell with a signle UISwitch

class UISwitchCell: UITableViewCell {
    @IBOutlet weak var uiSwitch: UISwitch!
}


// The TableViewController to hold the prototype cells.    

class CellPrototypeTableViewController: UITableViewController {
    
    // Loads the view controller from the storyboard
    
    static let shared: CellPrototypeTableViewController = {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let viewController = storyboard.instantiateViewController(withIdentifier: "cellProtoypeVC") as! CellPrototypeTableViewController
        viewController.loadViewIfNeeded()  // Make sure to force view controller to load the view!
        return viewController
    }()

    
    // Helper methods to deque the cells
    
    func dequeUILabeCell() -> UILabelCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "uiLabelCell") as! UILabelCell
        return cell
    }
    
    func dequeUISwitchCell() -> UISwitchCell {
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "uiSwitchCell") as! UISwitchCell
        return cell
    }
}

Use:

class MyTableViewController: UITableViewController {
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        // Dequeue the cells from the shared instance
        
        switch indexPath.row {
        case 0:
            let uiLabelCell = CellPrototypeTableViewController.shared.dequeUILabeCell()
            uiLabelCell.label.text = "Hello World"
            return uiLabelCell
        case 1:
            let uiSwitchCell = CellPrototypeTableViewController.shared.dequeUISwitchCell()
            uiSwitchCell.uiSwitch.isOn = false
            return uiSwitchCell
        default:
            fatalError("IndexPath out of bounds")
        }
    }
    
    // Handling Segues
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        switch indexPath.row {
        case 0: self.performSegue(withIdentifier: "first", sender: nil)
        case 1: self.performSegue(withIdentifier: "second", sender: nil)
        default:
            fatalError("IndexPath out of bounds")
        }
    }
}

我一直在寻找这个问题,我找到了理查德·维纳布尔的答案。这对我很管用。

iOS 5在UITableView上包含了一个新方法:registerNib:forCellReuseIdentifier: 要使用它,将UITableViewCell放在nib中。它必须是唯一的根 对象。 你可以在加载tableView后注册nib,然后当你 call dequeueReusableCellWithIdentifier:使用单元格标识符,它 将从笔尖拉它,就像如果你使用了一个故事板 原型细胞。

b·j·荷马(BJ Homer)对正在发生的事情做出了出色的解释。

从实际的角度来看,我想补充一点,鉴于你不能将单元格作为xib并连接segue,最好的选择是将单元格作为xib -过渡比跨多个地方维护单元格布局和属性要容易得多,而且无论如何,你的segue可能与不同的控制器不同。你可以定义直接从你的表视图控制器到下一个控制器的segue,并在代码中执行它。

进一步的注意是,将单元格作为一个单独的xib文件会阻止你直接将任何动作等连接到表视图控制器(我还没有解决这个问题,无论如何-你不能将文件的所有者定义为任何有意义的东西)。我正在通过定义一个协议来解决这个问题,单元格的表视图控制器将遵循该协议,并在cellForRowAtIndexPath中添加控制器作为一个弱属性(类似于委托)。

尽管BJ Homer给出了很好的答案,但我觉得我有一个解决方案。从我的测试来看,它是有效的。

概念:为xib单元格创建自定义类。在那里,您可以等待触摸事件并以编程方式执行segue。现在我们所需要的是对执行Segue的控制器的引用。我的解决方案是在tableView:cellForRowAtIndexPath:中设置它。

例子

我有一个DetailedTaskCell。xib包含一个我想在多个表视图中使用的表单元格:

这个单元格有一个自定义类TaskGuessTableCell:

这就是奇迹发生的地方。

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

我有多个segue,但它们都有相同的名称:“完成任务”。如果您需要灵活一点,我建议添加另一个属性。

ViewController看起来是这样的:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

也许有更优雅的方式来达到同样的效果,但它确实有效!:)