[ios] Xib 파일에서 사용자 정의 UITableViewCell을 어떻게로드합니까?

문제는 간단합니다. UITableViewCellXib 파일에서 사용자 정의 를 어떻게로드 합니까? 그렇게하면 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오브젝트를 추가 하고 ID를 “MyCellIdentifier”로 설정 한 후 클래스를 MyCell로 설정하고 구성 요소를 추가하십시오. 마지막으로을 IBOutlets구성 요소에 연결하십시오 . IB에서 파일 소유자를 설정하지 않았습니다.

다른 메소드는 파일 소유자 설정을 옹호하고 Xib가 추가 팩토리 클래스를 통해로드되지 않은 경우 메모리 누수에 대해 경고합니다. 위의 Instruments / Leaks에서 테스트했으며 메모리 누수가 없음을 확인했습니다.

Xibs에서 셀을로드하는 정식 방법은 무엇입니까? File ‘s Owner를 설정합니까? 우리는 공장이 필요합니까? 그렇다면 공장 코드는 어떻게 생겼습니까? 여러 솔루션이있는 경우 각 솔루션의 장단점을 명확히하십시오.



답변

IB 엔지니어가 원 저자가 추천 한 두 가지 방법은 다음과 같습니다 .

자세한 내용은 실제 게시물을 참조하십시오. 더 간단한 것처럼 방법 # 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는 여전히 유효하지만 더 이상 설명서가 없습니다. 이전에는 공식 문서에 있었지만 이제 스토리 보드를 위해 제거되었습니다.

나는 Github에 실제 예제를 게시했다 :
https://github.com/bentford/NibTableCellExample

스위프트 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
}


답변

올바른 해결책은 다음과 같습니다.

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


답변

레지스터

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

( ) 프로토 타입 셀로 .xib또는 .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:기존 동작을 확인할 nilUITableViewCell?있습니다.

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

물론 셀의 연관된 클래스 유형은 UITableViewCell서브 클래스 의 .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
    }
}

물론 이것은 ObjC에서 모두 같은 이름으로 제공됩니다.


답변

Shawn Craver의 답변을 가져 와서 약간 정리했습니다.

BBCell.h :

#import <UIKit/UIKit.h>

@interface BBCell : UITableViewCell {
}

+ (BBCell *)cellFromNibNamed:(NSString *)nibName;

@end

BBCell.m :

#import "BBCell.h"

@implementation BBCell

+ (BBCell *)cellFromNibNamed:(NSString *)nibName {
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    BBCell *customCell = nil;
    NSObject* nibItem = nil;
    while ((nibItem = [nibEnumerator nextObject]) != nil) {
        if ([nibItem isKindOfClass:[BBCell class]]) {
            customCell = (BBCell *)nibItem;
            break; // we have a winner
        }
    }
    return customCell;
}

@end

내 UITableViewCell의 BBCell 하위 클래스를 모두 만든 다음 표준을 바꿉니다.

cell = [[[BBDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BBDetailCell"] autorelease];

와:

cell = (BBDetailCell *)[BBDetailCell cellFromNibNamed:@"BBDetailCell"];


답변

나는 bentford의 방법 # 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;
}

작동하지만 사용자 정의 UITableViewCell .xib 파일에서 File ‘s Owner 에 연결 되어 있는지 확인하십시오.

전달하여 owner:self귀하의 loadNibNamed성명, 당신은 설정 UITableViewController당신의 파일의 소유자로 UITableViewCell.

IB의 헤더 파일로 끌어서 놓아 조치 및 아울렛을 설정하면 기본적으로 파일 소유자로 설정됩니다.

에서 loadNibNamed:owner:options, 애플의 코드는에 속성을 설정하려고합니다 UITableViewController그 소유자이기 때문에. 그러나 해당 속성이 정의되어 있지 않으므로 키 값 코딩 호환에 대한 오류가 발생 합니다 .

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:     '[<MyUITableViewController 0x6a383b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key myLabel.'

이벤트가 대신 트리거되면 NSInvalidArgumentException이 발생합니다.

-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0'
*** First throw call stack:
(0x1903052 0x15eed0a 0x1904ced 0x1869f00 0x1869ce2 0x1904ec9 0x5885c2 0x58855a 0x62db76 0x62e03f 0x77fa6c 0x24e86d 0x18d7966 0x18d7407 0x183a7c0 0x1839db4 0x1839ccb 0x1f8b879 0x1f8b93e 0x585a9b 0xb904d 0x2c75)
terminate called throwing an exceptionCurrent language:  auto; currently objective-c

쉬운 해결 방법은 UITableViewCell파일 소유자 대신 인터페이스 빌더 연결을 지정하는 것입니다.

  1. File ‘s Owner를 마우스 오른쪽 버튼으로 클릭하여 연결 목록을 불러옵니다.
  2. Command-Shift-4로 화면을 캡처합니다 (끌어서 캡처 할 영역을 선택합니다)
  3. 파일 소유자의 연결을 x
  4. 오브젝트 계층에서 UITableCell을 마우스 오른쪽 단추로 클릭하고 연결을 다시 추가하십시오.

답변

이 답변 중 어느 것도 마음에 들지 않기 때문에 게시하기로 결정했습니다. 일이 항상 더 간단 할 수 있으며 이것이 내가 찾은 가장 간결한 방법입니다.

1. 원하는대로 Interface Builder에서 Xib를 빌드하십시오.

  • 파일 소유자를 NSObject 클래스로 설정
  • UITableViewCell을 추가하고 클래스를 MyTableViewCellSubclass로 설정하십시오-IB가 충돌하는 경우 (이 글에서 Xcode> 4로 발생) Xcode 4에서 UIView를 사용하십시오.
  • 이 셀 안에 하위 뷰를 배치하고 .h 또는 .m에서 IBOutlet 연결을 @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;
}


답변

Interface Builder를 사용하여 셀을 만드는 경우 Inspector에서 식별자를 설정했는지 확인하십시오. 그런 다음 dequeueReusableCellWithIdentifier를 호출 할 때 동일한 지 확인하십시오.

실수로 테이블이 많은 프로젝트에서 일부 식별자를 설정하는 것을 잊어 버렸고 성능 변경은 밤낮과 같습니다.