ios xib学习




你如何从Xib文件加载自定义的UITableViewCells? (14)

寄存器

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

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

出列

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

不同之处在于,这种新方法不仅使单元出队,而且还创建了不存在的情况(这意味着if (cell == nil) shenanigans不需要做),并且单元准备好使用就像在上面的例子中。

警告tableView.dequeueReusableCell(withIdentifier:for:)具有新的行为,如果你调用另一个(没有indexPath: tableView.dequeueReusableCell(withIdentifier:for:) ,你会得到旧行为,你需要检查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?
}

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

组态

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

全部一起

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

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

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

以下是我使用的一些代码:

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

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

其他方法主张设置文件的所有者并警告内存泄漏,如果Xib未通过额外的工厂类加载。 我在Instruments / Leaks下面测试了上述内容,没有发现内存泄漏。

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


  1. UITableViewCell创建您自己的定制类AbcViewCell子类(确保您的类文件名和nib文件名相同)

  2. 创建这个扩展类方法。

    extension UITableViewCell {
        class func fromNib<T : UITableViewCell>() -> T {
            return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  3. 用它。

    let cell: AbcViewCell = UITableViewCell.fromNib()


从XIB中加载UITableViewCells可以节省很多代码,但通常会导致可怕的滚动速度(实际上,它不是XIB,而是过度使用UIViews)。

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


以下是在UITableView注册单元的通用方法:

protocol Reusable {
    static var reuseID: String { get }
}

extension Reusable {
    static var reuseID: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable { }

extension UITableView {

func register<T: UITableViewCell>(cellClass: T.Type = T.self) {
    let bundle = Bundle(for: cellClass.self)
    if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil {
        let nib = UINib(nibName: cellClass.reuseID, bundle: bundle)
        register(nib, forCellReuseIdentifier: cellClass.reuseID)
    } else {
        register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID)
    }
}

说明:

  1. Reusable协议从其类名生成单元ID。 确保你遵循约定: cell ID == class name == nib name
  2. UITableViewCell符合Reusable协议。
  3. UITableView扩展通过nib或class抽象化注册单元格的差异。

用法示例:

override func viewDidLoad() {
    super.viewDidLoad()
    let tableView = UITableView()
    let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
    cellClasses.forEach(tableView.register)
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
    ...
    return cell
}

如果您使用Interface Builder创建单元格,请检查您是否在Inspector中设置了标识符。 然后在调用dequeueReusableCellWithIdentifier时检查它是否一样。

我不小心忘了在一个桌子重的项目中设置一些标识符,并且性能变化如日夜。


我不知道是否有规范的方式,但这是我的方法:

  • 为ViewController创建一个xib
  • 将文件所有者类设置为UIViewController
  • 删除视图并添加一个UITableViewCell
  • 将您的UITableViewCell的类设置为您的自定义类
  • 设置你的UITableViewCell的标识符
  • 将视图控制器视图的出口设置为您的UITableViewCell

并使用此代码:

MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
  UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
  cell = (MyCustomViewCell *)c.view;
  [c release];
}

在你的例子中,使用

[nib objectAtIndex:0]

如果Apple更改xib中项目的顺序,则可能会中断。


我决定发布,因为我不喜欢任何这些答案 - 事情总是可以更简单,这是迄今为止我找到的最简洁的方式。

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

  • 将文件的所有者设置为类NSObject
  • 添加一个UITableViewCell并将其类设置为MyTableViewCellSubclass - 如果您的IB崩溃(在本文写作时发生在Xcode> 4中),只需使用UIView做Xcode 4中的接口,如果您仍然在使用它
  • 在此单元格内布局子视图并将IBOutlet连接附加到.h或.m中的@interface(.m是我的偏好)

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.在你的MyTableViewCellSubclass中

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

    return self;
}

我所做的是在你的控制器类中声明一个IBOutlet UITableViewCell *cell 。 然后调用NSBundle loadNibNamed类方法,它将UITableViewCell给上面声明的单元格。

对于xib,我将创建一个空的xib,并在IB中添加UITableViewCell对象,以便根据需要进行设置。 这个视图然后连接到控制器类中的单元IBOutlet

- (UITableViewCell *)tableView:(UITableView *)table
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%@ loading RTEditableCell.xib", [self description] );

    static NSString *MyIdentifier = @"editableCellIdentifier";
    cell = [table dequeueReusableCellWithIdentifier:MyIdentifier];

    if(cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"RTEditableCell"
                                      owner:self
                                    options:nil];
    }

    return cell;
}

NSBundle添加loadNibNamed(ADC登录)

cocoawithlove.com文章我找到了这个概念(获取电话号码示例应用程序)


检查这个 - http://eppz.eu/blog/custom-uitableview-cell/ - 使用一个微型的类,在控制器实现中结束一行真的很方便:

-(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath
{
    return [TCItemCell cellForTableView:tableView
                          atIndexPath:indexPath
                      withModelSource:self];
}


正确的做法是创建一个UITableViewCell子类实现,头文件和XIB。 在XIB中删除任何视图,只需添加一个表格单元格。 将该类设置为UITableViewCell子类的名称。 对于文件所有者,使其成为UITableViewController子类的名称。 使用tableViewCell插座将文件所有者连接到单元格。

在头文件中:

UITableViewCell *_tableViewCell;
@property (assign) IBOutlet UITableViewCell *tableViewCell;

在实现文件中:

@synthesize tableViewCell = _tableViewCell;

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:kCellIdentifier owner:self options:nil];
        cell = _tableViewCell;
        self.tableViewCell = nil;
    }

    return cell;
}

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

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

这个扩展需要Xcode7 beta6

extension NSBundle {
    enum LoadViewError: ErrorType {
        case ExpectedXibToExistButGotNil
        case ExpectedXibToContainJustOneButGotDifferentNumberOfObjects
        case XibReturnedWrongType
    }

    func loadView<T>(name: String) throws -> T {
        let topLevelObjects: [AnyObject]! = loadNibNamed(name, owner: self, options: nil)
        if topLevelObjects == nil {
            throw LoadViewError.ExpectedXibToExistButGotNil
        }
        if topLevelObjects.count != 1 {
            throw LoadViewError.ExpectedXibToContainJustOneButGotDifferentNumberOfObjects
        }
        let firstObject: AnyObject! = topLevelObjects.first
        guard let result = firstObject as? T else {
            throw LoadViewError.XibReturnedWrongType
        }
        return result
    }
}

创建一个仅包含1个自定义UITableViewCell的Xib文件。

加载它。

let cell: BacteriaCell = try NSBundle.mainBundle().loadView("BacteriaCell")

重新加载NIB是昂贵的。 最好加载一次,然后在需要单元格时实例化对象。 请注意,您可以使用此方法(Apple的“registerNIB”iOS5仅允许一个顶级对象 - Bug 10580062“iOS5 tableView registerNib:过度限制”)将UIImageViews等添加到笔尖,甚至多个单元格。

所以我的代码如下 - 你只需要在NIB中读取一次(像初始化一样,或者在viewDidload中 - 不管怎么样),然后你将实例化nib到对象中,然后选择你需要的那个,这比加载nib更有效率一遍又一遍。

static UINib *cellNib;

+ (void)initialize
{
    if(self == [ImageManager class]) {
        cellNib = [UINib nibWithNibName:@"ImageManagerCell" bundle:nil];
        assert(cellNib);
    }
}

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(cell == nil) {
        NSArray *topLevelItems = [cellNib instantiateWithOwner:nil options:nil];
        NSUInteger idx = [topLevelItems indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
                            {
                                UITableViewCell *cell = (UITableViewCell *)obj;
                                return [cell isKindOfClass:[UITableViewCell class]] && [cell.reuseIdentifier isEqualToString:cellID];
                            } ];
        assert(idx != NSNotFound);
        cell = [topLevelItems objectAtIndex:idx];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"Howdie %d", indexPath.row];

    return cell;
}

首先导入您的自定义单元格文件#import "CustomCell.h" ,然后更改委托方法,如下所述:

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

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

     return cell;
}




uitableview