ios - आप Xib फ़ाइलों से कस्टम UITableViewCells कैसे लोड करते हैं?




cocoa-touch (14)

रजिस्टर

आईओएस 7 के बाद, इस प्रक्रिया को सरल कर दिया गया है ( स्विफ्ट 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")

( नोट ) यह प्रोटोटाइप कोशिकाओं के रूप में .xib या .stroyboard फ़ाइलों में सेल्स बनाकर भी प्राप्त किया जा सकता है। यदि आपको उन्हें कक्षा संलग्न करने की आवश्यकता है, तो आप सेल प्रोटोटाइप का चयन कर सकते हैं और इसी वर्ग को जोड़ सकते हैं (निश्चित रूप से UITableViewCell वंशज होना चाहिए)।

विपंक्ति

और बाद में, ( स्विफ्ट 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) शेंगेनिन्स) करने की ज़रूरत नहीं है, और सेल बस के रूप में उपयोग करने के लिए तैयार है उपरोक्त उदाहरण में।

( चेतावनी ) tableView.dequeueReusableCell(withIdentifier:for:) के लिए नया व्यवहार है, यदि आप दूसरे को कॉल करते हैं tableView.dequeueReusableCell(withIdentifier:for:) आपको पुराना व्यवहार मिलता है, जिसमें आपको nil जांच करने की आवश्यकता होती है और इसे स्वयं उदाहरण दें, UITableViewCell? नोटिस करें UITableViewCell? प्रतिलाभ की मात्रा।

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

और निश्चित रूप से, सेल के संबंधित वर्ग का प्रकार वह है जिसे आपने UITableViewCell उपclass के लिए .xib फ़ाइल में परिभाषित किया है, या वैकल्पिक रूप से, अन्य रजिस्टर विधि का उपयोग कर।

विन्यास

आदर्श रूप से, आपके कक्षों को आपके द्वारा पंजीकृत किए जाने तक उपस्थिति और सामग्री स्थिति (जैसे लेबल और छवि दृश्य) के संदर्भ में पहले ही कॉन्फ़िगर किया गया है, और 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
    }
}

और निश्चित रूप से, यह सब एक ही नाम के साथ ओबीजेसी में उपलब्ध है।

सवाल सरल है: आप Xib फ़ाइलों से कस्टम UITableViewCell कैसे लोड करते हैं? ऐसा करने से आप अपनी कोशिकाओं को डिज़ाइन करने के लिए इंटरफेस बिल्डर का उपयोग कर सकते हैं। मेमोरी मैनेजमेंट मुद्दों के कारण उत्तर स्पष्ट रूप से आसान नहीं है। यह धागा इस मुद्दे का उल्लेख करता है और समाधान का सुझाव देता है, लेकिन पूर्व एनडीए रिलीज है और कोड की कमी है। यहां एक लंबा धागा है जो एक निश्चित उत्तर प्रदान किए बिना इस मुद्दे पर चर्चा करता है।

यहां कुछ कोड है जिसका मैंने उपयोग किया है:

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 जोड़ें। फिर एक नई "खाली XIB" फ़ाइल बनाएं। आईबी में एक्सआईबी फ़ाइल खोलें, एक UITableViewCell ऑब्जेक्ट जोड़ें, इसके पहचानकर्ता को "MyCellIdentifier" पर सेट करें, और अपनी कक्षा को MyCell पर सेट करें और अपने घटकों को जोड़ें। अंत में, IBOutlets को घटकों से कनेक्ट करें। ध्यान दें कि हमने आईबी में फाइल के मालिक को सेट नहीं किया है।

अन्य विधियां फ़ाइल के मालिक को सेट करने का समर्थन करती हैं और यदि अतिरिक्त फैक्ट्री क्लास के माध्यम से ज़िब लोड नहीं होता है तो मेमोरी लीक की चेतावनी दी जाती है। मैंने उपरोक्त परीक्षण उपकरण / लीक के तहत परीक्षण किया और कोई स्मृति रिसाव नहीं देखा।

तो 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()


इस एक्सटेंशन को 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
    }
}

एक Xib फ़ाइल बनाएं जिसमें केवल 1 कस्टम UITableViewCell शामिल है।

इसे लोड करें।

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

इसके लिए मेरी विधि यहां दी गई है: XIB फ़ाइलों से कस्टम UITableViewCells लोड हो रहा है ... फिर भी एक और तरीका

विचार UITableViewCell एक नमूनासेल सबक्लास को आईबीओटलेट IBOutlet UIView *content प्रॉपर्टी और कोड से कॉन्फ़िगर करने के लिए आवश्यक प्रत्येक कस्टम सबव्यू के लिए एक संपत्ति के साथ एक नमूना बनाना है। फिर एक SampleCell.xib फ़ाइल बनाने के लिए। इस निब फ़ाइल में, फ़ाइल मालिक को नमूनासेल में बदलें। अपनी जरूरतों को पूरा करने के लिए आकार UIView आकार जोड़ें। आप चाहते हैं कि सभी सबव्यू (लेबल, छवि दृश्य, बटन, आदि) जोड़ें और कॉन्फ़िगर करें। अंत में, सामग्री दृश्य और सबव्यू को फ़ाइल स्वामी से लिंक करें।


इसे जांचें - http://eppz.eu/blog/custom-uitableview-cell/ - एक छोटी कक्षा का उपयोग करके वास्तव में सुविधाजनक तरीका जो नियंत्रक कार्यान्वयन में एक पंक्ति को समाप्त करता है:

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


एनआईबी को फिर से लोड करना महंगा है। इसे एक बार लोड करने के लिए बेहतर है, फिर जब आपको सेल की आवश्यकता होती है तो वस्तुओं को तुरंत चालू करें। ध्यान दें कि आप इस विधि का उपयोग करते हुए, एनआईबी, यहां तक ​​कि कई सेल्स में यूआईएममेज व्यू इत्यादि जोड़ सकते हैं (ऐप्पल का "रजिस्टर एनआईबी" आईओएस 5 केवल एक शीर्ष स्तरीय ऑब्जेक्ट की अनुमति देता है - बग 10580062 "आईओएस 5 टेबल देखें रजिस्टर एनआईबी: अत्यधिक प्रतिबंधित"

तो मेरा कोड नीचे है - आप एक बार एनआईबी में पढ़ते हैं (जैसे मैंने शुरू किया था या दृश्य में - डिस्लोड - जो भी हो। तब से, आप वस्तुओं में निब को तुरंत चालू करते हैं और फिर आपको जिसकी जरूरत होती है उसे चुनें। यह निब लोड करने से कहीं अधिक कुशल है बार बार।

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

मुझे नहीं पता कि एक कैननिक तरीका है, लेकिन यहां मेरी विधि है:

  • 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]

यदि ऐप्पल xib में आइटम्स का ऑर्डर बदलता है तो तोड़ सकता है।


मैंने पोस्ट करने का फैसला किया है क्योंकि मुझे इनमें से कोई भी जवाब पसंद नहीं है - चीजें हमेशा अधिक सरल हो सकती हैं और यह अब तक का सबसे संक्षिप्त तरीका है।

1. इंटरफ़ेस बिल्डर में अपनी ज़िब बनाएं जैसा आपको पसंद है

  • एनएसओब्जेक्ट कक्षा में फ़ाइल के मालिक को सेट करें
  • एक UITableViewCell जोड़ें और अपनी कक्षा को MyTableViewCellSubclass पर सेट करें - यदि आपका आईबी दुर्घटनाग्रस्त हो जाता है (इस लेखन के रूप में एक्सकोड> 4 में होता है), तो बस एक्सकोड 4 में इंटरफ़ेस करने के UIView का उपयोग करें यदि आप अभी भी इसे चारों ओर बिछा रहे हैं
  • इस सेल के अंदर अपने सबव्यूज़ लेआउट करें और अपने आईबीओलेट कनेक्शन को अपने @interface में .h या .m (.m मेरी प्राथमिकता है) में संलग्न करें।

2. आपके UIViewController या UITableViewController उपclass में

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

यदि आप सेल बनाने के लिए इंटरफ़ेस बिल्डर का उपयोग कर रहे हैं, तो जांचें कि आपने इंस्पेक्टर में इंस्पेक्टर को सेट किया है। फिर जांचें कि यह dequeueReusableCellWithIdentifier को कॉल करते समय वही है।

मैं गलती से एक तालिका-भारी परियोजना में कुछ पहचानकर्ताओं को सेट करना भूल गया, और प्रदर्शन परिवर्तन रात और दिन की तरह था।


यहां क्लास विधि है जिसका उपयोग मैं XIB से कस्टम सेल बनाने के लिए कर रहा हूं:

+ (CustomCell*) createNewCustomCellFromNib {

    NSArray* nibContents = [[NSBundle mainBundle]
                            loadNibNamed:@"CustomCell" owner:self options:NULL];

    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    CustomCell *customCell= nil;
    NSObject* nibItem = nil;

    while ( (nibItem = [nibEnumerator nextObject]) != nil) {

        if ( [nibItem isKindOfClass: [CustomCell class]]) {
            customCell = (CustomCell*) nibItem;

            if ([customCell.reuseIdentifier isEqualToString: @"CustomCell"]) {
                break; // we have a winner
            }
            else
                fuelEntryCell = nil;
        }
    }
    return customCell;
}

फिर, एक्सआईबी में, मैंने कक्षा का नाम सेट किया, और पहचानकर्ता का पुन: उपयोग किया। इसके बाद, मैं बस उस विधि को मेरे दृश्य नियंत्रक में कॉल कर सकता हूं

[[UITableViewCell] alloc] initWithFrame:]

यह काफी तेज़ है, और मेरे दो शिपिंग अनुप्रयोगों में उपयोग किया जा रहा है। यह [nib objectAtIndex:0] कॉल करने से अधिक विश्वसनीय है, और कम से कम मेरे दिमाग में स्टीफन बर्लोट के उदाहरण से अधिक विश्वसनीय है क्योंकि आप केवल एक एक्सआईबी से बाहर देखने के लिए गारंटी देते हैं जो कि सही प्रकार है।


सबसे पहले अपनी कस्टम सेल फ़ाइल #import "CustomCell.h" आयात करें #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;
}

सही समाधान यह है

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"CustomCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell  *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
    return cell; 
    }

आईबी इंजीनियर द्वारा मूल लेखक राज्यों की सिफारिश की गई दो विधियां यहां दी गई हैं।

अधिक जानकारी के लिए वास्तविक पोस्ट देखें। मैं विधि # 2 पसंद करता हूं क्योंकि यह आसान लगता है।

विधि # 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;
}

विधि # 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;
}

अपडेट (2014): विधि # 2 अभी भी मान्य है लेकिन इसके लिए अब कोई दस्तावेज नहीं है। यह आधिकारिक दस्तावेज़ों में होता था लेकिन अब स्टोरीबोर्ड के पक्ष में हटा दिया जाता है।

मैंने गिथूब पर एक कामकाजी उदाहरण पोस्ट किया:
https://github.com/bentford/NibTableCellExample


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 प्रोटोकॉल सेल वर्ग को अपने वर्ग के नाम से उत्पन्न करता है। सुनिश्चित करें कि आप सम्मेलन का पालन करें: 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
}




uitableview