我试图在我正在工作的应用程序中使用故事板。在应用程序中有列表和用户,每个列表都包含其他列表的集合(列表的成员,用户拥有的列表)。因此,相应地,我有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
也许有更优雅的方式来达到同样的效果,但它确实有效!:)
如果我没有理解错的话,这个问题很简单。在你的故事板中创建一个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")
}
}
}
尽管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
也许有更优雅的方式来达到同样的效果,但它确实有效!:)