본문 바로가기

개발/App Developer

IB 없이 하기, UITableView끝장보기 (4) 테이블 편집


- 툴바 작성
- 테이블뷰 구현 코드 수정
- NSMutableArray와 테이블뷰의 연동



이번엔 테이블의 편집 기능을 구현해 보겠습니다
테이블뷰에는 간단히 말해서 모드가 있습니다
편집중과 아닌상태, 이 모드를 설정 하는것으로 편집모드와 읽기 모드로 바꿔 가며 사용할수 있습니다

전 글까지 해보셨다면
현재 시뮬레이터 화면에는 하나의 꽉찬 테이블 뷰만 있습니다

* 이 강좌는 어디까지나 테이블뷰의 모든것(?)을 알아보려는 것입니다만 다른 컨트롤이 필요한 관계로 사용해보겠습니다

화면에 테이블만 있어가지곤 테이블뷰의 모드 전환 시킬 버튼 같은게 필요합니다
간단히 셀을 클릭하는걸로 해결볼수도 있습니다만

기왕 만들어 볼거 재대로 만들어 보죠

그래서 테이블뷰 상단에 툴바(UIToolBar)를 구현해 보겠습니다

* 여기서는 간단히 소스 구현하고 넘어가도록 하고 자세한 정보는 다른 강좌에서 봐주세요


그럼 우선 툴바 구현을 위한 코딩을 해보겠습니다

컨트롤을 처음 생성하는 init함수에서 다음 코드를 넣어줍니다

UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 40.0)];

[self.view addSubview:toolbar];

[toolbar release];


한번 돌려 보겠습니다
 
 
 
 
 
툴바가 상단에 추가 되었습니다
 
하지면 테이블뷰와 겹쳐서 테이블뷰위 상단 부분이 안보이는것을 보실수 있습니다.

그래서

다음 코드를 고칩니다

myTable = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];

->

myTable = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 40.0, 320.0, 480.0 - 40.0 - 20.0) style:UITableViewStylePlain 

 

 

* 참고

CGRect함수는 어떤 영역에 지정 크기만큼 공간을 할당해주는 역할을합니다.

CGRect cg = CGRectMake(10,10,100,100);//10,10좌표에서부터 100,100만큼 공간을 할당합니다.

- CGRectMake와 NSMakeRect의 차이 ..

   CGRectMake가 결국은 NSMakeRect를 사용합니다.  Cocoa는 맥에서 쓰이는 SDK인데, 이것 위에다 iPhone SDK를 만들었거든요.  iPhone개발할 때는 CGRectMake를 쓰시면 됩니다.

 

CGRECT와 관련

CGRectMake( x, y, width, height );  : CGRect 생성
NSStringFromCGRect( CGRect );  : String으로 변환
CGRectFromString( String );  : String을 CGRect으로 변환
CGRectInset( CGRect, CGFloat, CGFloat );  :  같은 위치, 가운데 정렬, CGRect 생성
CGRectIntersectsRect( CGRect, CGRect ); :  충돌여부
CGRectZero  : x, y를 0으로

(BOOL) CGRectContainsPoint(CGRect rect, CGPoint point);  : //포인트가 사각형에 속했는지 판단합니다.

(BOOL) CGRectContainsRect(CGRect rect1, CGRect rect2);   : //사각형1에 사각형2가 속해있는지 판단합니다.

(BOOL) CGRectIntersectsRect (CGRect rect1,  CGRect rect2);  :  //사각형이 서로 교차했는지 판단합니다.

 

 

 

화면에 맞춰졌습니다
 self.view.bounds는 화면에 맞춘 크기라 보시면 됩니다 그러나 툴바를 넣었기 때문에

수동으로 크기를 정해준것입니다

CGRectMake(X, Y, Width, Height)

넓이는 같지만 높이가 문제죠 

원래 화면 크기인 480에 맨위에 시계바 높이인 20과 툴바 높이인 40을 빼주면 화면에 딱 맞게 됩니다

이번에는 툴바에 버튼을 넣어보겠습니다

툴바 제작 코드 다음에 넣어 줍니다

toolbar.items = [NSArray arrayWithObjects:

[[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:nil] autorelease],

 nil];


돌려 보겠습니다


 

 
 
버튼이 만들어 졌습니다
다만 버튼을 눌러도 아무런 반응이 없습니다
당연히 눌렀을때 호출될 함수가 정의 되지 않아서 입니다.
 
버튼을 함수와 연결해 보겠습니다 
 
 툴바에 버튼을 넣는 코드를 다음과 같이 수정합니다

 [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(action)] autorelease],

 

nil 대신에 @selector(action)로 바뀌었습니다.

그리고 init함수 다음에 다음 함수를 만들어 줍니다

- (void)action {

}

편집버튼을 클릭하면 위 action함수가 호출되게 됩니다.

바로 action함수에 테이블을 편집 모드로 변경하도록 코드를 넣어보겠습니다
[myTable setEditing:!myTable.editing];
버튼을 누를때마다 editing모드가 매번 바뀌도록 했습니다

돌려 보겠습니다
 
 
 
Edit를 누르니 각각의 셀을 지울지 물어보는 화면이 뜹니다 바로 편집 모드로 들어갔습니다
 
 
한번 하나를 지워보겠습니다.
 
 
뭔가 이상합니다

 

지워지는가 싶더니 지워져야할 셀이 그대로 있습니다


원인은 지워질때 호출되는 함수가 구현되어 있지 않아서 입니다 

 지워진다는 명령만 뜰뿐 실제로 셀을 지우는 코드가 없어서 입니다.

 

그냥 구현해 보기 전에 

 

 데이터와 테이블을 연동해 보겠습니다

 

우선 데이터와 테이블을 연동하기 전에

데이터를 어떻게 구현할것인지 정하는게 중요합니다

잘못하면 나중에 자신이 만들어 놓고 해매게 됩니다 (경험담;;;;;)



이번 테이블의 데이터는 그룹도 구현 해야 합니다

데이터를 구현하는데 NSMutableArray만한건 없습니다
NSArray와 같지만 수정/삭제/추가 가 가능합니다.

이것의 특징은 포인트 배열이라 배열안에 배열을 구현 하는게 가능 하다는 겁니다

제가 생각해본 데이터 구상도는 이렇습니다

배열 (그룹 모음, NSMutableArray){
0번(NSDictionary) {
그룹명(NSString) : "그룹 1"
데이터(NSMutableArray) : {
0번 (셀, NSDictionary) {
텍스트 (NSString): "텍스트"

}
1번 (셀) {
}
2번 (셀) {
}
...
}
}
1번 {
그룹명 : "그룹 2"
데이터 : {
0번 (셀) {
텍스트 : "텍스트"

}
1번 (셀) {
}
2번 (셀) {
}
...
}
}
}

첫 배열을 그룹들을 담을 그룹스가 되겠습니다
그 그룸스안에 NSDictionary형식으로 그룹명과 해당 배열 데이터(셀스)를 가지도록 하겠습니다
셀스는 NSMutableArray형식으로 NSDictionary의 조합이 되겠습니다

초장부터 좀 복잡하게 구현된거 같습니다만
그룹과 같이 구현하려다 보니 이렇게 됐습니다.

한번 코드로 구현해 보죠
우선 전역 변수(클래스 변수)로 

NSMutableArray *myList;

를 정의 합니다

그다음 init함수에서 정의한 myList를 만들고 데이터를 코드로 일일이 넣어보겠습니다
이 작업은 대체할 데이터가 없는 관계로 대충 코드로 만들었습니다만
실제로 어플을 개발 하실때 따로 데이터를 불러오는 작업을 하시면 됩니다.


우선 메모리에 배열을 할당합니다

myList = [[NSMutableArray alloc] init];

한가지 알아 두실 점은 같은 기능을 하는 코드가 또 있습니다

myList = [NSMutableArray array];

하지만 이 코드는 변수에 오토일리즈가 붙어있는 코드로 
위 같이 정의 하시면 init함수가 끝날때 자연 소멸되 버립니다.

메모리 관리법에 대해 설명하려면 글이 길어지는 관계로
간단히 말해서 클래스 전역에서 쓰려면 alloc을 사용하고 
그냥 함수 내부에서 쓰고 버릴 변수라면 각각의 변수 타입에 정의된 생성법을 쓰시면 됩니다
예)       전역(클래스) 변수                             |               함수 내부 변수
[[NSMutableArray alloc] init]                   [NSMutableArray array]
[[NSString alloc] initWithString:@""]      [NSString string]
[[NSDictionary alloc] initWith...];             [NSDictionary dictionaryWithObject...

 다시 본론으로 돌아가서
다음 코드를 넣어줍니다

[myList addObject:

[NSDictionary dictionaryWithObjectsAndKeys:
@"그룹1", @"grouptitle",
[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:@"cell1",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"cell2",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"cell3",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"cell4",@"text", nil],
nil],@"data",

nil]

];

[myList addObject:

[NSDictionary dictionaryWithObjectsAndKeys:
@"그룹2", @"grouptitle",
[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:@"dell1",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"dell2",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"dell3",@"text", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"dell4",@"text", nil],
nil],@"data",

nil]

]; 
 
 
여기서 잠시 궁금점이 생기실수 있습니다
왜 myList만 alloc하고 나머지 안에 들어가는 것들은 그냥 내부 변수를 썼는지...

배열에 들어가게 되면 자동으로 리테인 되서 해당 배열이 사라지기 전에는 함수를 나오더라도
사라지지 않습니다.

그래서 myList만 alloc하면 나머진 알아서 딸려온다는 거죠

비워줄때는 myList만 릴리즈하면 하위 객체들에 전부 릴리즈가 전송 된다고 합니다
만약 메모리 증가 현상이 있다면 다른곳에서 데이터를 추가할때 문제가 있었을수도 있습니다.

이제 데이터가 준비 되었습니다

테이블 뷰의 델리게이트와 연동해보겠습니다

하나하나 해보죠

우선 그룹 갯수를 묻는

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return 2;

}

이걸

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return [myList count];

}

수정합니다


지금은 간단한 샘플을 만드는거라 괜찬지만 복잡한 코딩을 하시게 되면 여기서 주의 하실것이 있습니다

myList의 count가 0이 될 경우도 생각해 둬야 합니다

0을 돌려주게 되면 어플이 튕기게 됩니다

저의 경우

return [myList count]; 

대신에

if (!myList) return 1;

return ([myList count] == 0 ? 1: [myList count]);

사용합니다

지금은 myList의 갯수를 건들일이 없으니 그냥 넘어 갑니다


이번에는 그룹 헤더의 문자를 돌려주는 함수를 수정해 보겠습니다

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

return [NSString stringWithFormat:@"test %i",section];

}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

return [[myList objectAtIndex:section] objectForKey:@"grouptitle"];

}

로 수정



그리고 셀의 갯수 리턴 함수


- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {

return 7;

}

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {

return [[[myList objectAtIndex:section] objectForKey:@"data"] count];

}

 

 

 

 

 

 

 

마지막으로 셀 정의 함수만 처리하면 끝이군요

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


switch문은 필요없으니 그만 지웁니다.


함수를 보시면 딸려 오는 인자에서 indexPath가 있습니다


여태껏 indexPath.row만 썼습니다만

indexPath는 그룹번호(섹션)와 셀번호(로우)를 같이 가지고 있는 변수입니다


불필요한 셀을 설정하는 코드를 지워주고 다음 코드를 넣습니다

cell.text = [[[[myList objectAtIndex:indexPath.section] objectForKey:@"data"] objectAtIndex:indexPath.row] objectForKey:@"text"];

좀 알아보기 어렵나요? 이건 어떻나요?


NSDictionary *group = [myList objectAtIndex:indexPath.section];

NSMutableArray *cells = [group objectForKey:@"data"];

NSDictionary *oneCell = [cells objectAtIndex:indexPath.row];

cell.text = [oneCell objectForKey:@"text"];



한번 돌려 보겠습니다

 

 

 

 

 

잘뜹니다.  이제부터 본격 편집을 구현 해보겠습니다.

 

다시한번 UITableView의 헤더를 보면 Editing에 대한 델리게이트 및 데이터소스가 있습니다

 

우선 중요한 것만 써보죠


- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;


우선 위 함수는 테이블이 편집 모드에 들어갈때 호출됩니다

셀 하나당 한번씩 호출되며 헤당 셀이 편집될수 있는지 물어보는겁니다

YES를 돌려주면 헤당셀이 편집 가능하다는 거고 NO를 돌려주면 편집이 안됩니다

위 함수를 구현 하지 않으면 자동적으로 모든셀에 YES가 들어갑니다


잠시 가지고 놀아보겠습니다


- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {

return (indexPath.row % 2 == 0);

}

그냥 간단히 짝수번 셀만 편집 가능하도록 했습니다

한번 돌려 보겠습니다.
 
 
 
 
지워졌습니다.  이번에는 셀의 순서를 변경해보겠습니다
 
 셀의 순서 변경관련 함수 또한 두개 입니다

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath

위 함수는 편집 가능한지 보던 함수와 동일하게 셀의 이동이 가능한지 보는 함수 입니다
위 함수가 정의 되어 있지 않으면 기본 값으로 전부 YES가 됩니다

하지만 이것만 가지곤 셀의 이동이 되지 않습니다

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath

이 함수가 셀의 이동이 이루어 졌을때 호출되는 함수 입니다
다만 재미있는 점은 이 함수가 구현 되야만 셀의 이동이 되는겁니다

셀의 이동을 하려면 함수가 구현 되야 하고 이 함수가 호출되려면 이동이 되야 하는... 

아무튼 이 함수가 구현되면 자동으로 셀의 악세사리뷰에 이동가능을 뜻하는 작대기 세줄짜리 이미지가 뜹니다

함수를 구현해 보겠습니다

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {

NSDictionary *source = [NSDictionary dictionaryWithDictionary:[[[myList objectAtIndex:sourceIndexPath.section] objectForKey:@"data"] objectAtIndex:sourceIndexPath.row]];
[[[myList objectAtIndex:sourceIndexPath.section] objectForKey:@"data"] removeObjectAtIndex:sourceIndexPath.row];
[[[myList objectAtIndex:destinationIndexPath.section] objectForKey:@"data"] insertObject:source atIndex:destinationIndexPath.row];

}

우선 하나의 셀 데이터가 NSDictionary로 되어 있으므로 우선 하나 받아 냅니다
SDictionary *source = [[[myList objectAtIndex:sourceIndexPath.sectionobjectForKey:@"data"objectAtIndex:sourceIndexPath.row];
식으로 하지 않은 이유는 이 코드는 주소만 받아오기 때문입니다 그 다음 코드에서 해당 객체를 빼면 리테인 카운트가 0이 되서 지워져 버리죠 그래서 [NSDictionary dictionaryWithDictionary:를 해줌으로서 리스트에서 우선 빼더라도 데이터가 지워지지 않게 하는겁니다

우선 원래 그룹에서 뺀다음
다시 해당 그룹에 넣도록 했습니다
 
 

여기까지 데이터와 테이블 편집을 구현해봤습니다.