在情节提要中,如何制作自定义单元以与多个控制器一起使用?


216

我正在尝试在正在使用的应用程序中使用情节提要。在该应用程序中,有“ 列表”和“ 用户”,每个列表用户都包含其他列表的集合(列表的成员,用户拥有的列表)。因此,我有ListCellUserCell类。目标是使它们在整个应用程序中可重用(即,在我的任何tableview控制器中)。

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

如何在情节提要中创建可在任何视图控制器中重复使用的自定义tableview单元?

这是到目前为止我尝试过的具体操作。

  • 在Controller#1中,添加了一个原型单元,将类设置为我的UITableViewCell子类,设置了重用ID,添加了标签并将其连接到该类的插座。在Controller#2中,添加了一个空的原型单元,将其设置为相同的类,并像以前一样重用ID。当它运行时,当控制器2中显示单元格时,标签将永远不会显示。在控制器1中正常工作。

  • 在不同的NIB中设计每种电池类型,并连接到适当的电池类别。在情节提要中,添加了一个空的原型单元,并设置其类和重用ID以引用我的单元类。在控制器的viewDidLoad方法中,注册那些NIB文件以获取重用ID。如图所示,两个控制器中的单元都像原型一样是空的。

  • 保留两个控制器中的原型为空,并设置类,并将id重用到我的单元格类中。完全用代码构造单元格的UI。单元在所有控制器中都能正常工作。

在第二种情况下,我怀疑原型始终会覆盖NIB,并且如果我杀死原型单元,则可以为重用ID注册我的NIB。但是,那时我将无法设置从单元到其他框架的序列,这实际上就是使用情节提要的全部要点。

归根结底,我想要两件事:在情节提要中连接基于tableview的流,并在视觉上而不是在代码中定义单元格布局。到目前为止,我还看不到如何做到这两个。

Answers:


205

据我了解,您想:

  1. 在IB中设计一个可以在多个情节提要场景中使用的单元。
  2. 根据该单元所在的场景,从该单元配置唯一的情节提要剧情。

不幸的是,目前尚无办法。要了解为什么以前的尝试不起作用,您需要更多地了解情节提要和原型表视图单元如何工作。(如果您不在乎为什么这些其他尝试不起作用,请立即离开。除了建议您提交错误外,我没有其他神奇的解决方法。)

从本质上讲,分镜脚本只是.xib文件的集合而已。当您加载一个具有一些原型单元的故事板之外的表视图控制器时,将发生以下情况:

  • 每个原型单元实际上都是其自己的嵌入式微型笔尖。因此,在加载表视图控制器时,它将遍历每个原型单元的笔尖并调用-[UITableView registerNib:forCellReuseIdentifier:]
  • 表格视图向控制器询问单元格。
  • 你可能会打电话 -[UITableView dequeueReusableCellWithIdentifier:]
  • 当您请求具有给定重用标识符的单元格时,它会检查其是否已注册笔尖。如果是这样,它将实例化该单元的实例。这由以下步骤组成:

    1. 查看单元格的类别,如单元格的笔尖中所定义。致电[[CellClass alloc] initWithCoder:]
    2. -initWithCoder:方法将遍历并添加子视图并设置在笔尖中定义的属性。(IBOutlet可能也被挂在这里了,尽管我还没有测试过;它可能发生在-awakeFromNib
  • 您可以根据需要配置单元。

这里要注意的重要一点是,单元格的类别与单元格的视觉外观之间存在区别。您可以创建两个相同类的独立原型单元,但是它们的子视图完全不同地布置。实际上,如果使用默认UITableViewCell样式,这就是正在发生的事情。例如,“默认”样式和“字幕”样式都由相同的表示UITableViewCell类。

这很重要:单元格的类别与特定的视图层次结构不具有一一对应的关系。视图层次结构完全取决于在此特定控制器中注册的原型单元中的内容。

还要注意,单元的重用标识符未在某些全局单元药房中注册。重用标识符仅在单个UITableView实例的上下文中使用。


有了这些信息,让我们看看您在上述尝试中发生了什么。

在Controller#1中,添加了一个原型单元,将类设置为我的UITableViewCell子类,设置了重用ID,添加了标签并将它们连接到类的插座。在Controller#2中,添加了一个空的原型单元,将其设置为相同的类,并像以前一样重用ID。当它运行时,当控制器2中显示单元格时,标签将永远不会显示。在控制器1中正常工作。

这是预期的。虽然两个单元格具有相同的类,但是传递给Controller#2中的单元格的视图层次结构完全没有子视图。这样您得到了一个空单元格,这正是您在原型中放入的单元格。

在不同的NIB中设计每种电池类型,并连接到适当的电池类别。在情节提要中,添加了一个空的原型单元,并设置其类和重用ID以引用我的单元类。在控制器的viewDidLoad方法中,注册那些NIB文件以获取重用ID。如图所示,两个控制器中的单元都像原型一样是空的。

同样,这是预期的。重用标识符不会在情节提要场景或笔尖之间共享,因此所有这些不同的单元格都具有相同的重用标识符这一事实是没有意义的。从tableview返回的单元格将具有与情节提要场景中的原型单元格相匹配的外观。

但是,此解决方案很接近。如您所述,您可以-[UITableView registerNib:forCellReuseIdentifier:]通过编程方式调用,传递UINib包含该单元格的内容,然后您将获得相同的单元格。(这不是因为原型“覆盖”了笔尖;您只是没有将笔尖注册到表格视图中,因此它仍在查看故事板中嵌入的笔尖。)不幸的是,这种方法存在缺陷–没有办法将情节提要片段连接到独立笔尖中的单元。

保留两个控制器中的原型为空,并设置类,并将id重用到我的单元格类中。完全用代码构造单元格的UI。单元在所有控制器中都能正常工作。

自然。希望这并不奇怪。


因此,这就是为什么它不起作用的原因。您可以在独立的笔尖中设计单元,并在多个情节提要场景中使用它们。您目前无法将情节提要片段连接到这些单元。希望您在阅读本文的过程中学到了一些东西。


知道了 您消除了我的误解—视图层次结构完全独立于我的班级。回想起来很明显!感谢您的出色回答。
克里夫W

不再是不可能的,这似乎是:stackoverflow.com/questions/8574188/...
富阿波达卡

7
@RichApodaca我在回答中提到了该解决方案。但是它不在情节提要中。在一个单独的笔尖中。因此,您无法进行连接或进行其他类似故事板的事情。因此,它不能完全解决最初的问题。
BJ荷马

从XCode8开始,如果您只希望使用情节提要,则以下解决方法似乎可以工作。步骤1)在ViewController#1的表格视图中创建原型单元,并与自定义UITableViewCell类关联。步骤2)在ViewController#2的表格视图中复制/粘贴该单元格。随着时间的流逝,您将必须记住要手动删除更新,将其传播到单元的副本,方法是删除在情节提要中制作的副本,然后将其粘贴回更新的原型中。
jengelsma

很好的答案,但是我还有一个后续问题:>“在本质上,一个故事图板只不过是一个.xib文件的集合而已”,为什么在其中嵌入xib如此困难?故事板?
willcwf

58

尽管BJ Homer给出了很好的答案,但我还是觉得自己有解决方案。就我的测试而言,它是可行的。

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

我有一个DetailedTaskCell.xib表单元格,希望在多个表视图中使用:

详细的TaskCell.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

我有多个Segues,但它们都具有相同的名称:"FinishedTask"。如果您在此处需要灵活处理,建议添加另一个属性。

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

可能会有更优雅的方法实现相同目的,但它确实有效!:)


1
谢谢,我正在我的项目中实现这种方法。您可以改写此方法,这样就不必获取indexPath并自己选择行:-(void)setSelected:(BOOL)selected animation:(BOOL)animated {[super setSelected:selected animation:animated]; if(selected)[self.controller performSegueWithIdentifier:self.segue sender:self]; 我以为super会在调用[super touchesEnded:touches withEvent:event];时选择单元格。您是否知道何时未选中它?
thejaz 2012年

9
请注意,使用此解决方案,您每次在单元格内结束触摸时都会触发segue。这包括如果您只是滚动单元格,而不是实际尝试选择它。你可能有更好的运气覆盖-setSelected:的小区,只有从转变时触发SEGUE NOYES
BJ荷马

setSelected:BJ 我的运气更好。谢谢。确实,这是一个微不足道的解决方案(感觉很不对劲),但是同时它可以工作,所以我一直在使用它直到问题解决(或者苹果法院有所改变)。
本·克里格

16

我一直在寻找这个问题,而我找到了Richard Venable的答案。这个对我有用。

iOS 5在UITableView上包含一个新方法:registerNib:forCellReuseIdentifier:

要使用它,请将UITableViewCell放在笔尖中。它必须是笔尖中唯一的根对象。

您可以在加载tableView之后注册笔尖,然后使用单元格标识符调用dequeueReusableCellWithIdentifier:时,它将从笔尖中拉出它,就像您使用了Storyboard原型单元一样。


10

BJ Homer对发生的事情给出了很好的解释。

从实际的角度来看,我要补充一点,鉴于您不能将单元格作为xib并连接segue,因此,最好的选择是将单元格作为xib-过渡要比跨多个位置的单元格布局和属性容易维护,并且您的选择可能与您的其他控制器有所不同。您可以直接从表视图控制器到下一个控制器定义segue,然后用代码执行。。

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


10

迅捷3

BJ Homer给了很好的解释,它帮助我理解了这个概念。To make a custom cell reusable in storyboard,可以在我们需要使用的任何TableViewController中使用mix the Storyboard and xib。假设我们有一个名为的单元格,CustomCell该单元格将在TableViewControllerOne和中使用TableViewControllerTwo。我正在逐步进行。
1. File> New>单击File> Select Cocoa Touch Class>单击Next>给您的班级命名(例如CustomCell)>选择Subclass作为UITableVieCell>勾选同时创建XIB文件复选框,然后按Next。
2.根据需要自定义单元格,并在属性检查器中为该单元格设置标识符,这里我们将其设置为CellIdentifier。此标识符将在您的ViewController中用于标识和重用Cell。
3.现在我们只需要register this cell在我们的ViewController中viewDidLoad。不需要任何初始化方法。
4.现在,我们可以在任何tableView中使用此自定义单元格。

在TableViewController1

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}

5

我找到了一种为同一VC加载单元的方法,未针对segues进行测试。这可能是在单独的笔尖中创建单元的解决方法

假设您有一个VC和2个表,并且要在情节提要中设计一个单元,并在两个表中都使用它。

(例如:一个表和一个带有UISearchController的搜索字段以及一个用于结果的表,并且您要在两者中使用相同的Cell)

当控制器询问单元格时,请执行以下操作:

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

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

这是情节提要中的单元格


我尝试了这一点,但它似乎确实可以工作,但是永远不会重复使用这些单元。系统总是每次都创建和取消分配新的单元。
GilroyKilroy 2015年

这与在使用情节提要的表视图添加搜索栏时可以找到相同的建议。如果您有兴趣,可以在此找到有关此解决方案的更深入说明(搜索tableView:cellForRowAtIndexPath:)。
有意义,

但这种情况较少文字和答案的问题
若奥·努涅斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.