How do you load custom UITableViewCells from Xib files?

前端 未结 23 1729
抹茶落季
抹茶落季 2020-11-22 11:11

The question is simple: How do you load custom UITableViewCell from Xib files? Doing so allows you to use Interface Builder to design your cells. The answer app

相关标签:
23条回答
  • 2020-11-22 11:33

    In Swift 4.2 and Xcode 10

    I have three XIB cell files

    in ViewDidLoad register your XIB files like this...

    This is first approach

    tableView.register(UINib.init(nibName: "XIBCell", bundle: nil), forCellReuseIdentifier: "cell1")
    tableView.register(UINib.init(nibName: "XIBCell2", bundle: nil), forCellReuseIdentifier: "cell2")
    //tableView.register(UINib.init(nibName: "XIBCell3", bundle: nil), forCellReuseIdentifier: "cell3")
    

    Second approach directly register XIB files in cellForRowAt indexPath:

    This is my tableview delegate functions

    //MARK: - Tableview delegates
    override func numberOfSections(in tableView: UITableView) -> Int {
    
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
        return 6
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //This is first approach
        if indexPath.row == 0 {//Load first XIB cell
            let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! XIBCell
            return placeCell
        //Second approach
        } else if indexPath.row == 5 {//Load XIB cell3
            var cell = tableView.dequeueReusableCell(withIdentifier:"cell3") as? XIBCell3
            if cell == nil{
                let arrNib:Array = Bundle.main.loadNibNamed("XIBCell3",owner: self, options: nil)!
                cell = arrNib.first as? XIBCell3
            }
    
            //ADD action to XIB cell button
            cell?.btn.tag = indexPath.row//Add tag to button
            cell?.btn.addTarget(self, action: #selector(self.bookbtn1(_:)), for: .touchUpInside);//selector
    
            return cell!
        //This is first approach
        } else {//Load XIB cell2
            let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell2") as! XIBCell2
    
            return placeCell
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 11:34

    Loading UITableViewCells from XIBs saves a lot of code, but usually results in horrible scrolling speed (actually, it's not the XIB but the excessive use of UIViews that cause this).

    I suggest you take a look at this: Link reference

    0 讨论(0)
  • 2020-11-22 11:36

    Register

    After iOS 7, this process has been simplified down to (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")
    

    (Note) This is also achievable by creating the cells in the .xib or .stroyboard files, as prototype cells. If you need to attach a class to them, you can select the cell prototype and add the corresponding class (must be a descendant of UITableViewCell, of course).

    Dequeue

    And later on, dequeued using (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
    }
    

    The difference being that this new method not only dequeues the cell, it also creates if non-existant (that means that you don't have to do if (cell == nil) shenanigans), and the cell is ready to use just as in the example above.

    (Warning) tableView.dequeueReusableCell(withIdentifier:for:) has the new behavior, if you call the other one (without indexPath:) you get the old behavior, in which you need to check for nil and instance it yourself, notice the UITableViewCell? return value.

    if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCellClass
    {
        // Cell be casted properly
        cell.myCustomProperty = true
    }
    else
    {
        // Wrong type? Wrong identifier?
    }
    

    And of course, the type of the associated class of the cell is the one you defined in the .xib file for the UITableViewCell subclass, or alternatively, using the other register method.

    Configuration

    Ideally, your cells have been already configured in terms of appearance and content positioning (like labels and image views) by the time you registered them, and on the cellForRowAtIndexPath method you simply fill them in.

    All together

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

    And of course, this is all available in ObjC with the same names.

    0 讨论(0)
  • 2020-11-22 11:37

    Reloading the NIB is expensive. Better to load it once, then instantiate the objects when you need a cell. Note that you can add UIImageViews etc to the nib, even multiple cells, using this method (Apple's "registerNIB" iOS5 allows only one top level object - Bug 10580062 "iOS5 tableView registerNib: overly restrictive"

    So my code is below - you read in the NIB once (in initialize like I did or in viewDidload - whatever. From then on, you instantiate the nib into objects then pick the one you need. This is much more efficient than loading the nib over and over.

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 11:38

    Here are two methods which the original author states was recommended by an IB engineer.

    See the actual post for more details. I prefer method #2 as it seems simpler.

    Method #1:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
        if (cell == nil) {
            // Create a temporary UIViewController to instantiate the custom cell.
            UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil];
            // Grab a pointer to the custom cell.
            cell = (BDCustomCell *)temporaryController.view;
            [[cell retain] autorelease];
            // Release the temporary UIViewController.
            [temporaryController release];
        }
    
        return cell;
    }
    

    Method #2:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
        if (cell == nil) {
            // Load the top-level objects from the custom cell XIB.
            NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
            // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
            cell = [topLevelObjects objectAtIndex:0];
        }
    
        return cell;
    }
    

    Update (2014): Method #2 is still valid but there is no documentation for it anymore. It used to be in the official docs but is now removed in favor of storyboards.

    I posted a working example on Github:
    https://github.com/bentford/NibTableCellExample

    edit for Swift 4.2

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // Do any additional setup after loading the view.
        self.tblContacts.register(UINib(nibName: CellNames.ContactsCell, bundle: nil), forCellReuseIdentifier: MyIdentifier)
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: MyIdentifier, for: indexPath) as! ContactsCell
    
        return cell
    }
    
    0 讨论(0)
  • 2020-11-22 11:39

    The correct way to do it is to create a UITableViewCell subclass implementation, header, and XIB. In the XIB remove any views and just add a table cell. Set the class as the name of the UITableViewCell subclass. For file owner, make it the UITableViewController subclass class name. Connect the file owner to the cell using the tableViewCell outlet.

    In the header file:

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

    In the implementation file:

    @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;
    }
    
    0 讨论(0)
提交回复
热议问题