在Interface Builder中使用id<protocol>作为文件所有者?

9
我有一个自定义的UITableViewCell,我正在使用instantiateWithOwner:(id)owner options:(NSDictionary *)options从nib中实例化它。当nib被实例化时,我会将它保存到在.xib文件中设置为文件所有者的我的视图控制器中定义的IBOutlet中。一切都运作得很好。
现在我需要在多个视图控制器中使用这个自定义单元格。我希望能够定义一个协议(例如CustomCellOwner),多个视图控制器可以实现该协议。该协议将简单地定义用于引用实例化单元格的IBOutlet。
因此,理想情况下,我希望将“文件所有者”设置为:
id <CustomCellOwner>

在Interface Builder中。

然而,Interface Builder似乎只允许你将文件的所有者设置为已知类,而不是实现协议的id?

有没有什么方法可以做到这一点?或者,有更简单的方法来解决这个问题吗?

谢谢!


请帮我指点一下,您是如何实现这个功能的呢?能否提供一些指南或样例?这听起来很不错。目前我需要遍历nib文件来查找适合在自定义单元格中使用的视图,这并不理想。而且您的解决方案听起来非常好。 - j_mcnally
你能详细说明一下“它似乎只允许你将文件所有者设置为已知类,而不是实现协议的ID”吗?这是否意味着界面构建器? - rooftop
@rooftop 是的,“it” 在这里指的是 Interface Builder。当编辑/配置文件所有者对象时,有一个“Class”选项,可以让您选择文件所有者的类。但在 IB 中,该框不允许我以 id<some protocol> 形式输入类型,我想知道是否可能。谢谢! - thauburger
5个回答

3

虽然这不是你所寻求的解决方案,但你可以创建一个UIViewController子类来作为需要使用nib的每个视图控制器的子类。代码如下:

@interface CustomCellOwnerViewController : UIViewController
@property (nonatomic, strong) IBOutlet UIButton *someButton;
-(IBAction)doSomething;
@end

然后将其用作每个类的基类:

@interface FirstView : CustomCellOwnerViewController

那么,您只需将File's Owner设置为CustomCellOwnerViewController即可,没有任何问题。
只是一个想法。

1
今天我遇到这个问题,并没有找到一个很好的解决方案。但是,我通过一些技巧解决了它,现在似乎能够正常工作了,虽然感觉有点像骗局。
首先,我创建了一个"fakeOwner"类,像这样:
@interface fakeOwner : NSObject
@property (nonatomic, assign) IBOutlet MyBaseCell* itemTableCell;
@end

@implementation fakeOwner
@synthesize itemTableCell;
@end

然后我在XIB中将对象的所有者设置为fakeOwner并连接了outlet。然后对于每个想要使用这些单元格的控制器,我都添加了相同的属性并像这样创建类:

    [[NSBundle mainBundle] loadNibNamed:@"MyBaseCell" owner:self options:nil];
    MyBaseCell* itemCell = self.itemTableCell;
    self.itemTableCell = nil;

由于fakeOwner和我的控制器具有相同的IBOutlet,因此使用控制器作为所有者加载单元格会导致连接发生,即使在XIB中并没有明确设置。

目前不确定内存管理是否正确(我认为是可以的),但除此之外它似乎运作良好。不过我还是希望能找到更好的方法来做这件事。


1
创建一个虚假的所有者是可行的;但是,这样的解决方案可能是脆弱和不可扩展的。从某种意义上说,单元格拥有自己,但即使如此,这也是技术上不正确的。事实上,UITableViewCell没有所有者。
实现自定义表视图单元格的正确方法是首先创建一个UITableViewCell的自定义子类。在这个类中,您将为单元格定义所有的IBOutlets等。以下是一个头文件的示例:
@interface RBPersonCell : UITableViewCell

@property (nonatomic, strong) IBOutlet UILabel * nameLabel;
@property (nonatomic, strong) IBOutlet UILabel * ageLabel;

- (void)setupWithPerson:(Person *)person;

@end

接下来,我有一个方便的方法,如果需要的话,可以从nib创建单元格:

+ (id)cellForTableView:(UITableView *)tableView reuseIdentifier:(NSString *)reuseID fromNib:(UINib *)nib {

    if (!reuseID)
        reuseID = [self cellIdentifier];

    id cell = [tableView dequeueReusableCellWithIdentifier:reuseID];

    if (!cell) {

        NSArray * nibObjects = [nib instantiateWithOwner:nil options:nil];

        // Sanity check. 
        NSAssert2(([nibObjects count] > 0) && 
                  [[nibObjects objectAtIndex:0] isKindOfClass:[self class]],
                  @"Nib '%@' does not appear to contain a valid %@", 
                  [self nibName], NSStringFromClass([self class]));

        cell = [nibObjects objectAtIndex:0];
    }

    return cell;
}

这种方法封装了所有的创建代码,所以我永远不必看到它或重写它。它假设自定义单元格是nib中的第一个根视图。这是一个相当安全的假设,因为你应该只有自定义单元格作为根视图。

有了所有这些代码,你就可以在Interface Builder中工作了。你首先需要在identity inspect中设置自定义类。接下来,不要忘记设置你的单元格标识符。为了方便起见,最好使用自定义类的名称。当你拖动连接时,不要将它们拖到文件所有者,而是将它们拖到自定义单元格本身。

我学习关于自定义表格视图单元格的大部分内容都来自iOS Recipes的15-16个示例。这里是直接从The Pragmatic Bookshelf提取的免费摘录。你可以查看那本书获取更多细节。

编辑:

我终于抽出时间将我的RBSmartTableViewCell类开源了。你可以在我的GitHub上找到它。相比iOS Recipes中的代码,你应该会发现这个类更有用,因为我的类将所有单元格都视为同等处理,无论是使用XIBs、UIStoryboard还是代码构建的。这个仓库还包括可工作的示例。

1

另一个选择可能是创建一个轻量级的“工厂”对象来处理单元格的创建。这个对象将是接口构建器中的FilesOwner,并且需要适当设置rootObject插座。

@interface NibLoader : NSObject

@property (nonatomic, strong) UINib     * nib;
@property (nonatomic, strong) IBOutlet id rootObject;

- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundleOrNil;
- (id)instantiateRootObject;

@end


@implementation NibLoader

@synthesize nib, rootObject;

- (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundleOrNil {
    self = [super init];
    if (self) {
        self.nib = [UINib nibWithNibName:name bundle:bundleOrNil];
    }
    return self;
}

- (id)instantiateRootObject {
    self.rootObject = nil;
    [self.nib instantiateWithOwner:self options:nil];
    NSAssert(self.rootObject != nil, @"NibLoader: Nib did not set rootObject.");
    return self.rootObject;
}

@end

然后在视图控制器中:

NibLoader *customCellLoader = [[NibLoader alloc] initWithNibName:@"CustomCell" bundle:nil];
self.customCell = customCellLoader.instantiateRootObject; 

我更喜欢明确设置根对象,而不是在从instantiateWithOwner:options:返回的数组中搜索,因为我知道该数组中对象的位置过去已经改变。

1
在iOS 5.0中,UITableView现在有了registerNib:forCellReuseIdentifier:方法,我认为它试图解决类似的问题。
从文档中可以看到:

当您使用表视图注册一个nib对象并稍后调用dequeueReusableCellWithIdentifier:方法时,传递已注册的标识符,如果该单元格尚未在重用队列中,则表视图将从nib对象实例化单元格。

根据您的要求,这可能是一种替代方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接