본문 바로가기
프로그래밍/iOS

시작하세요! 아이폰 3 프로그래밍 - Part 8. 테이블 뷰 입문

by 백룡화검 2011. 6. 8.
* 테이블
- 테이블은 데이터의 목록을 표시한다.
- 테이블 목록의 각각의 항목은 행(row).각 행마다 열(column)은 하나만 있다.
- 테이블 뷰는 테이블에 있는 데이터를 보여주는 뷰이고 UITableView클래스의 인스턴스이다. 테이블에 각각의 행은 UITableViewCell 클래스로 구현된다. 그래서 테이블 뷰는 테이블의 전체적인 모양을 담당하는 객체이고 테이블 뷰 셀(table view cell)이 테이블의 각각의 행을 그리는 일을 담당한다.
- 테이블 뷰는 테이블의 데이터를 저장하는 역할은 하지 않는다. 단지 현재 보여주는 행을 그릴때 필요한 데이터만 저장한다.
- 테이블 뷰의 설정 데이터는 UITableVewDelegate 프로토콜을 따르는 객체에서 구하고 각 행의 데이터는 UITableViewDataSource프로토콜을 따르는 객체로부터 얻는다.

- 테이블 뷰에는 두가지 기본 스타일이 존재.
---- 그룹으로 묶은 방식(Group Table). 그룹으로 묶은 테이블의 각각의 그룹은 가장 왼쪽 그림처럼 둥근 사각형에 둘러싸인 행의 집합이다.
----- 인덱스로 구분한 테이블(Indexed Table). 등근 사각형이 없는 테이블.

- 테이블의 나누어진 영역이 데이터소스에서는 섹션이다.
---- 그룹으로 묶은 테이블에서 각 그룹은 세견이다.
---- 인덱스로 묶은 테이블에서는 데이터를 인덱스별로 묶은 것이 섹션이다.

* TableView data source
// 이것은 특정 섹션에 몇개의 행이 있는지 질의하는데 사용한다.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.listData count];
}


// 행을 그릴 필요가 있을 때 데이블 뷰가 이 메서드를 호출한다.
// NSIndexPath로부터 행이나 섹션을 얻을수 있다.
// 첫번째 인자인 tableView는 현재 메서드를 호출한 테이블의 레퍼런스
// 두번째 인자는 NSIndexPath의 객체
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    // 이 문자열은 테이블 셀의 종류를 나타내는 키로 쓰인다.
    static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";

    // 테이블 뷰의 셀들이 스크롤돼서 화면에서 사라지면 재사용 가능한 셀의 뷰(queue)에 들어간다.
    // 새로운 행이 이전에 사라졌던 행 중에서 다시 사용하게 된다면 시스템은 끊임없이 이러한 뷰를 만들고 해제하는 부담을 피할수 있다.
    // 이러한 방법을 사용하기 위해서 디큐(dequeue)된 셀중에서 필요한 타입을 테이블 뷰에서 얻어야 한다. (SimpleTableIdentifier)
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];

    if(cell == nil)
    {
        // 재사용 가능하게 동일한 셀로 만든다.
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleDefault] reuseIdentifier:SimpleTableIdentifier] autorelease];
    }

    // 어떤 행의 값을 사용할 것인지 결정
    NSInteger row = [indexPath row];
    cell.textLabel.text = [listData objectAtIndex:row];
    return cell;
}

* 각행에 이미지 추가하기
-(UITableViewCell *)tableView:(UITableView *) cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *SimpleTableIdentifier = @"SimpleTableIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SimpleTableIdentifier];

    if(cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero] reuseIdentifier:SimpleTableIdentifier] autorelease];
    }

    // cell에 이미지를 추가한다.    
    // UIImage는 파일 이름을 기반으로 캐시 기술을 사용하므로, 매번 새 이미지를 로딩하지 않을 것이다. 대신 캐쉬된 이미지를 사용할 것이다.
    UIImage *image = [UIImage imageName:@"star.png"];
    cell.imageView.image = image;

    // 해당 cell이 선택되었을때 이미지 추가
    // UIImage *highlightedImage = [UIImage imageName:@"star2.png"];
    // cell.imageView.highlightedImage = highlightedImage;

    NSInteger row = [indexPath row];
    cell.textLabel.text = [listData objectAtIndex:row];
    return cell;
}

* UITableViewCellStyle에 종류
- 하위 제목 스타일 적용 : 하위 제목은 작은 글자로 텍스트 레이블을 설명하는 글을 담고 있으면, 텍스트 레이블 밑에 회색 컬러를 사용하여 출력된다.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SimpleTableIdentifier] autorelease];
cell.detailTextLabel.text = @"XXXXXX";


- 텍스트 레이블과 상세 레이블을 한 줄에 배치하고 서로 대칭되도록 정렬 : 텍스트 레이블은 검은색으로 셀의 왼쪽에 나타나고, 상세 텍스트는 파란색으로 셀의 오른쪽에 나타난다.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1reuseIdentifier:SimpleTableIdentifier] autorelease];
cell.
detailTextLabel.text = @"XXXXXX";

- 셀에 대한 정보를 설명하기 위해 사용되는 레이블과 나란히 출력할 때 사용된다.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2reuseIdentifier:SimpleTableIdentifier] autorelease];
cell.
detailTextLabel.text = @"XXXXXX";

* Delegate를 사용해서 몇몇 행은 들여쓰기(Indent)
#pragma mark -
#pragma mark Table Delegate Methods
-(NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSUInteger row = [indexPath row];
    return row;
}


* 특정 행 선택 불가능하게 하기
- 델리게이트는 두개의 메서드를 사용해서 사용자가 특정행을 선택했는지 알수 있다. 그중 한개가 행이 선택되기 전에 호출되고 이 메서드에서 행이 선택되는 것을 막거나 심지어 선택되는 행을 바꿀수도 있다.
-(NSIndexPath *)tableView:(NSTableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSUInteger row = [indexPath row];

    // 선택하려는 항목이 첫번째이라면 nil를 리턴하여 선택하지 못하게 한다.
    if(row == 0)
        return nil;
    
    return indexPath;
}

* 선택한 특정 행 알아내기
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSUInteger row = [indexPath row];
    NSString *rowValue = [listData objectAtIndex:row];
    NSString *message = [[NSString alloc] initWithFormat:@"You selected %@", rowValue];

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Row Selected" message:message delegate:nil cancelButtonTitle:@"Yes I Did" otherButtonTitles:nil];

    [alert show];
    [message release];
    [alert relase];
    [tableView deselectRowAtIndexPath:IndexPath animated:YES];
}

* 폰트 크기 바꾸기
- cell.TextLabel.font = [UIFont boldSystemFontOfSize:50];

* 델리케이트를 에서 테이블 높이 지정
-(CGFolat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 70;
}


* 맞춤형 테이블 뷰 만들기
- UITableViewCell이 지원하는 방식이 아닌 다른 방식
---- UITableViewCell에 하위뷰를 추가 하는것
---- UITableViewCell의 하위클래스를 만드는것

* UITableViewCell에 하위뷰를 추가하는방식
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellTableIdentifier = @"CellTableIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellTableIdentifier];

    if(cell == nil)
    {
        // 새로 추가될 새로운 셀을 만든다.
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellTableIdentifier] autorelease];
        
        CGRect nameLabelRect = CGRectMake(0,5,70,15);
        UILabel *nameLabel = [[UILabel alloc] initWithFrame:nameLabelRect];
        nameLabel.textAlignment = UITextAlignmentRight;
        nameLabel.text = @"Name";
        nameLabel.font = [UIFont boldSystemFontOfSize:12];
        [cell.contentView addSubview:nameLabel];
        [nameLabel release];

        CGRect colorLabelRect = CGRectMake(0, 26,70,15);
        UILabel *colorLabel = [[UILabel alloc] initWithFrame:colorLabelRect];
        UILabel.textAlignment = UITextAlignmentRight;
        UILabel.text = @"Color";
        UILabel.font = [UIFont boldSystemFontOfSize:12];
        [cell.contentView addSubview:colorLabel];
        [colorLabel release];

        CGRect nameValueRect = CGRectMake(80,5,200,15);
        UILabel *nameValue = [[UILabel alloc] initWithFrame:nameValueRect];
        nameValue.tag = kNameValueTag;        // 나중에 해당 레이블에게 값을 할당할 수 있도록 이 필드를 찾을 방법을 추가
        [cell.contentView addSubview:nameValue];
        [nameValue release];

        CGRect colorValueRect = CGRectMake(80,25,200,15);
        UILabel *colorValue = [[UILabel alloc] initWithFrame:colorValueRect];
        colorValue.tag = kColorValueTag;            // 나중에 해당 레이블에게 값을 할당할 수 있도록 이 필드를 찾을 방법을 추가
        [cell.contentView addSubview:colorValue];
        [colorValue release];
    }

    NSUInteger row = [indexPath row];
    NSDictionary *rowData = [self.computers objectAtIndex:row];

    UILabel *name = (UILabel *)[cell.contentView viewWithTag:kNameValueTag];    // 해당 레이블 찾기
    name.text = [rowData objectForKey:@"Name"];

    UILabel *color = (UILabel *)[cell.contentView viewWithTag:kColorValueTag];        // 해당 레이블 찾기 
    color.text = [rowData objectForKey:@"Color"];

    return cell;
}


* UITableViewCell의 하위클래스 만들어 사용하기
- 인터페이스 빌더를 사용해서 테이블 셀 뷰를 설계하기
- UITableViewCell의 하위클래스와 테이블 뷰 셀을 담을 새 nib 파일을 만든다. 그런 후에 테이블 뷰 셀이 행을 표현할 때 테이블 뷰 셀에 하위뷰를 추가하는 대신에 단순히 nib 파일에서 구현한 하위클래스를 로드하고 두개의 새 아웃렛을 사용해서 이름과 색을 걸정할것

1. Xcode에서 class폴더에 Cocoa Touch Classes를 고르고 Objective-C class 를 선택한 다음 하위 클래스로 UITableViewCell을 선택하고 새로운 파일을 추가 한다. 파일명은 "CustomCell". Also create가 선택되었는지 확인..

2. Xcode에서 resources폴더에 User Interfaces를 클릭하고 Empty XIB를 추가한다. 파일명은 "CustomCell"

3. CustomCell.h파일에 아래 코드 추가
#import <UIKit/UIKit.h>
@interface CustomCell:UITableViewCell
{
    UILabel *nameLabel;
    UILabel *colorLabel;
}
@property (nonatomic, retain) IBOutlet UILabel *nameLabel;
@property (nonatomic, retain) IBOutlet UILabel *colorLabel;

@end


4. CustomCell.m파일에 추가.
@synthesize nameLabel;
@synthesize colorLabel;

-(void)delloc {
    [nameLabel release];
    [colorLabel release];
    [super dealloc];
}


5. CustomCell.xib를 더블클릭해서 인터페이스 빌더를 연다.
- Table View Cell을 라이브러리에서 찾아서 nib 메인윈도우에 끌어 놓는다.
- 테이블 뷰 셀을 선택했는지 확인하고 아이덴티티 인스펙터를 띄우고 ClassIdentify에서 Class를 CustomCell로 변경.
- View size에서 높이를 65로 변경.
- Table View Cell에서 Identifier를 CustomCellIdentifier로 변경
- 라이브러리에서 view를 찾아서 Custom Cell윈도우를 추가 한다.
- View의 크기를 x를 0, y를 0, w를 320, h를 65로 변경
- 라이블러리에서 Label를 4개 추가하여 위치를 맞추고 모양을 결정한다.
- Custom Cell 아이콘에서 드래드 해서 nameLabel과 colorLabel를 아웃렛에 할당한다.
- 이 테이블 셀은 데이터를 보여주기 위해서 사용하지만 사용자와의 상호작용은 테이블 뷰가 수행하므로 독자적인 컨트롤러 클래스가 필요없다.


6. 새 테이블 뷰 셀 사용하기
#import "CustomCell.h"

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

    CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CustomCellIdentifier];

    if(cell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
        
        for( id oneObject in nib )
            if([oneObject isKindOfClass:[CustomCell class]])
                cell = (CustomCell *)oneObject;

    }

    NSUInteger row = [indexPath row];
    NSDictionary *rowData = [self.computers objectAtIndex:row];
    cell.colorLabel.text = [rowData objectForKey:@"Name"];
    cell.nameLabel.text = [rowData objectForKey:@"Color"];
    return cell;
}


7. 이 델리케이트 메서드는 셀이 생기기 전에 호출돼서 필요한 값을 셀에서 얻지 못하므로 그 값을 하드코딩해야 한다.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 66;
}

* 그룹으로 묶은 세션과 인덱스로 구분한 섹션
- 인터페이스 빌더에서 추가한 TableView에서 Table View Style을 Grouped으로 변경한다.

### SectionsViewController.h ####
#import <UIKit/UIKit.h>

@interface SectionsViewController:UIViewController <UITableViewDelegate, UITableViewDataSource>
{
    NSDictionary *names;
    NSArray *keys;
}

@property (nonatomic, retain) NSDictionary *names;
@property (nonatomic, retain) NSArray *keys;

@end


### SectionsViewController.m ####
#import "SectionViewController.h"

@implementation SectionsViewController
@synthesize names;
@synthesize keys;

-(void)viewDidLoad
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
    NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];

    self.names = dict;
    [dict release];

    NSArray *array = [[names allKeys] sortedArrayUsingSelector:@selector(compare:)];
    self.keys = array;
}

-(void)viewDidUnload
{
    self.names = nil;
    self.keys = nil;
}

-(void)delloc
{
    [names release];
    [keys release];
    [super delloc];
}

#pragma mark -
#pragma mark Table View Data Source Methods
// 세션의 갯수가 몇개인지를 리턴한다.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [keys count];
}

// 특정 섹션의 행의 갯수가 몇개인지를 리턴한다.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectForKey:key];

    return [nameSection count];
}

// 테이블 셀 생성
// indexPath에서 section과 row를 뽑아서 딕셔너리의 어떤 값에 해당하는지 찾을때 이용한다.
// section을 사용해서 names Dictionary에서 어떤 배열을 가져올지 알수 잇고 그래서 row를 그 배열에서 어떤 값을 이용할지 알아내는 데 사용할수 있다.

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSUInteger section = [indexPath section];
    NSUInteger row = [indexPath row];

    NSString *key = [keys objectAtIndex:section];
    NSArray *nameSection = [names objectAtIndex:key];

    static NSString *SelectionsTableIdentifier = @"SelectionsTableIdentifier";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SelectionsTableIdentifier];

    if(cell == nil)
    {
        cell = [[[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SelectionsTableIdentifier] autorelease];
    }

    cell.textLabel.text = [nameSection objectAtIndex:row];
    return cell;
}

// 각 섹션의 헤더 값을 설정할수 있다.
-(NSString *)viewTable:(UIViewTable *)viewTable titleForHeaderInSection:(NSInteger)section
{
    NSString *key = [keys objectAtIndex:section];
    return key;
}

// 다음 메서드를 추가해서 Table View style이 Plain (인덱스 정렬)일 경우 Index를 추가 할수 있다.
-(NSArray *)sectionIndexForTitlesTableview:(UITableView *)tableView
{
    return keys;
}

* 깊은 뮤터블 복사
- NSDictionary는 NSMutableDictionary 프로토콜을 따르기 때문에 얕은 복사본인 NSMutableDictionary를 리턴한다. 이말은 mutableCopy 메서드를 호출 했을때 원래 딕셔너리가 가진 객체 모두를 가지는 새 NSMutableDictionary 객체를 만드는것을 위미한다.
- 얕은 복사는 해당 메모리 영역을 가리키는 주소 값만을 복사하는것을 의미한다. 보통 얇은 복사를 하게 되면 한 메모리 영역을 두 개의 포인터가 가리키게 되므로, 한쪽에서 할당된 메모리를 해제하면 다른 한쪽에서 사용할 수 있는 메모리 영역을 잃게 되는것이다.
- 깊은 복사는 메모리 영역 전체를 별도로 복사하는것을 의미한다.

출처 :  http://persiacat7.egloos.com/2618085