'iPhone'에 해당되는 글 20건

  1. 2010.07.18 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 9. 내비게이션 컨트롤러와 테이블 뷰
  2. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 8. 테이블 뷰 입문 2
  3. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 7. 탭바와 피커
  4. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 6. 멀티뷰 애플리케이션
  5. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 5. 자동회전과 자동크기조절
  6. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 4. 사용자 인터페이스, 좀 더 재미있게 만들기
  7. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 3. 기본적인 상호작용 다루기
  8. 2010.07.12 [iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 2. 티키신 달래기
  9. 2010.07.12 [iPhone] 2번째 교재.. 시작하세요! 아이폰3 프로그래밍 - Beginning Iphone 3 Development
  10. 2010.07.12 [iPhone]Objective-C 시작

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 9. 내비게이션 컨트롤러와 테이블 뷰

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 18. 17:44

* 네비게이션 컨트롤러
- 계층구조 애플리케이션을 만드는 데 사용할 가장 주된 도구는 UINavigationController이다.
- UINavigationController는 계층 구조를 다루기 적합한 스택(Stack)을 사용해서 구현되었다.

* 스택을 사용하면 좋은 점
- 스택은 새로 추가된 자료는 맨 마지막에 추가되고, 읽을 때는 가장 나중에 추가된 자료를 꺼내오는 후입선출(Last In, First Out:LIFO)원칙에 근간해서 작동하는 흔히 통용되는 자료구조. 다시 말해서.. 입력되는 데이터는 무조건 뒤쪽에 들어가고 꺼내올때도 뒤에서 부터 꺼내오는 방식.
- 객체를 스택에 추가하는 행위를 푸시(Push). 즉 객체를 스택에 밀어넣게되는 행위
- 객체를 스택에서 제거하는 행위를 팝(Pop). 즉 객체를 스택에서 꺼내오는 과정.

* 컨트롤러들의 스택
- 네비케이션 컨트롤러는 뷰 컨트롤러들을 스택에 담아두고 있다.
- 네비게이션 컨트롤러를 설계 할때는 먼저 사용자가 맨 처음 보게 되는 뷰를 지정해야 한다. 이 뷰는 계층의 가장 하단에 위치하고, 이 뷰를 담당하는 컨트롤러를 최상위 컨트롤러라고 부른다.
- 최상위 컨트롤러는 네비게이션 컨트롤러가 자신의 스택에 맨 처음으로 푸시하는 뷰 컨트롤러다.

* 여섯 부분으로 구성된 계층 구조 애플리케이션, Nav
- 상세보기 버튼은 단순한 아이콘이 아닌 사용자가 누를 수 있는 일종의 독립적인 컨트롤이다. 따라서 주어진 행에 대해, 두 가지 상반된 옵션을 지정할 수 있다. 한 액션은 사용자가 행을 선택할 때 시작된다. 다른 액션은 사용자가 더보기(disclosure)버튼을 누를 때 시작된다.
- 행을 눌러 완전히 다른 뷰가 나타난다면, 그 뷰는 행에 대한 상세 뷰라고 할 수 없다. 이럴때는 회색 화살표의 더 보기(dislosure indicator)를 행에 표시하면된다.

* Nav 애플리케이션의 뼈대 구성하기
- Xcode에서 Window-based Application을 선택하되 Use Core Data for storage는 사용하지 않는다. 프로젝트 이름은 Nav로 설정
- 모든 네비게이션 컨트롤러는 각각 고유한 최상위 컨트롤러를 가져야 한다.

* 최상위 레벨의 뷰 컨트롤러 생성하기
- UITableViewController의 하위 클래스를 만들어 사용하면 nib파일 없이도 테이블 뷰를 생성하는 것이 가능하다.
- Class 폴더에 새로운 파일을 취가한다. Cocoa Touch Class를 선택한 뒤 Objective-C class를 선택하고 Subclass of에서는 NSObject를 선택한다. 파일이름은 FirstLevelViewController.m으로 변경하고 헤더 파일도 같이 생성한다.

#### FirstLevelViewController.h ####
#import 

@interface FirstLevelViewController:UITableViewController
{

}
@end


* 네비게이션 컨트롤러 설정하기
- 애플리케이션의 최상위 계층에는 최상위 컨트롤러 이름의 윈도우에 추가되는 뷰와 관련된 컨트롤러가 위치하게 된다.
- 이번 애플리케이션과 같은 경우에는 다른 모든 뷰로의 전환과 뷰의 게층구조를 담당하는 네비게이션 컨트롤러가 최상위 컨트롤러에 해당된다.
- 애플리케이션의 최상위 컨트롤러는 navController로 사용되고, navController의 최상위 컨트롤러를 FirstLevelController으로 상용할것이다.

#### NavAppDelegate.h #####
#import 

@interface NavAppDelegate : NSObject  
{
    UIWindow *window;
	UINavigationController *navController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navController;

@end

#### NavAppDelegate.m ####
@synthesize navController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
    // Override point for customization after application launch.
	
	[window addSubview:navController.view];
    [window makeKeyAndVisible];
	
	return YES;
}

- (void)dealloc {
	[navController release];
    [window release];
    [super dealloc];
}

- 다음으로 네비게이션 컨트롤러를 만들고, 방금 선언한 navController아웃렛에 연결해야 한다. 그리고 나서 네비게이션 컨트롤러에게 최상위 컨트롤러를 알려줘야 한다.
- 인터페이스 빌더에서 MainWindow.xib를 더블크릭해서 파일을 열어보자.
- 네비게이션 컨트롤러를 라이브러리에서 찾은 후에 nib의 메인 위도우로 드래그 해보자 (view 창이 아닌 xib파일에 추가)
- Nav App Delgate 아이콘을 컨트롤-드래그해서, 새로 추가한 Navigation Controller로 끌어다 놓고 navController 아웃렛을 선택
- 이베 네비게이션 컨트롤러에게 최상위 컨트롤러의 위치를 알려줘야 한다. View Mode를 리스트 모드로 변경하여 Navigation Controller의 좌측에 작은 삼각형 모양의 더보기표시를 클릭하고 View Controller(Navigation Item)를 클릭한 후 아이덴티티 인스펙터 4를 실행한다. 클래스를 FirstLevelViewController로 변경한다.

- 각 행마다 아이콘을 표시하고 싶기 때문에 UIImage속성을 생성하는 모든 하위컨트롤러에 푸가해야 한다.

모든 컨트롤러는 UITableViewContoller가 아닌 SecondLevelViewController를 상속받아야 한다.
여기서는 SecondLevelViewController를 상속하고 있기 때문에 모든 클래스들은 각각의 행의 이미지를 저장할 수 있는 속성을 가지게 된다.
#### SecondLevelViewController.h ####
#import 

@interface SecondLevelViewController:UITableViewController
{
	UIImage *rowImage;
}

@property (nonatomic, retain) UIImage *rowImage;

@end

#### SecondLevelViewController.m ####
#import "SecondLevelViewController.h"

@implementation SecondLevelViewController
@synthesize rowImage;

@end

#### FirstLevelViewController.h ####
#import 

@interface FirstLevelViewController:UITableViewController
{
	// 해당 배열은 뷰 컨트롤러들의 인스턴스들을 가지게 된다. 이제 이것을 테이블의 데이터를 채워 넣는데 사용할것이다.
	NSArray *controllers;
}

@property (nonatomic, retain) NSArray *controllers;

@end

#### FirstLevelViewController.m ####
#import "FirstLevelViewController.h"
#import "SecondLevelViewController.h"

@implementation FirstLevelViewController

@synthesize controllers;

-(void)viewDidLoad
{
	// 네비게이션 컨트롤러는 네비게이션 바의 제목(title)부분에 무엇을 표시 할지 현재 활성화 상태에 있는 컨트롤러에게 물어보게 된다.
	self.title = @"First Level";
	
	// 나중에 테이블에 행을 추가할 준비가 되면 뷰 컨트롤러를 이 배열에 추가 할것이다.
	NSMutableArray *array = [[NSMutableArray alloc] init];
	self.controllers = array;
	
	[array release];
	[super viewDidLoad];
}

-(void)viewDidUnload
{
	self.controllers = nil;
	[super viewDidUnload];
}

-(void)dealloc
{
	[controllers release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
// 배열에서 개수를 리턴한다. 섹션당 몇개에 행이 있는지 알기 위해서.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [self.controllers count];
}

// 재사용 가능한 셀을 언거나, 그런 셀이 없으면 새 셀을 만드는 역할
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *FirstLevelCell = @"FirstLevelCell";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:FirstLevelCell];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:FirstLevelCell] autorelease];
	
	// Configure the cell
	NSUInteger row = [indexPath row];
	SecondLevelViewController *controller = [controllers objectAtIndex:row];
	
	// 선택된 뷰의 제목을 설정한다.
	cell.textLabel.text = controller.title;
	// 해당하는 이미지를 추가 한다.
	cell.imageView.image = controller.rowImage;
	// 더보기 버튼을 추가 시킨다.
	cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
	return cell;
}

#pragma mark -
#pragma mark Table View Delegate Methods
// 사용자가 행을 누르면 호출되는 메서드
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	NSUInteger row = [indexPath row];
	SecondLevelViewController *nextController = [self.controllers objectAtIndex:row];
	
	// 네비게이션 컨트롤러의 스택에 푸시하기 위해 애플리케이션의 네비게이션 컨트롤러를 가리키는 navigationContoller프로퍼티를 사용
	[self.navigationController pushViewController:nextController animated:YES];
	
}


@end


* 첫번째 하위컨트롤러 : 더보기 버튼 뷰
-  뷰 컨트롤러는 지연 로딩 방식을 사용한다. 컨트롤러를 생성하더라도, 실제로 화면에 표시 할 일이 있을 때까지는 nib파일을 로딩하지 않는 것이다. 컨트롤러를 네비게이션 컨트롤러의 스택에 푸시하고 나면, 그때는 레이블을 설정 할 수 있는지 여부를 확인할 수 없다.
- viewDidLoad 메서드의 문제는 처음 컨트롤러의 뷰가 로딩될 때만 호출된다. 그래서 사용자가 DisclosureDetailController 뷰가 맨 처음 업데이트되는 시점에서만 업데이트될 것이다. 두번째 픽사 영화를 선택하더라도 여전히 첫 번째 픽사 영화에 대한 상세 메시지를 가진다. 그래서 여기서는 viewWillAppear 메서드를 사용한다.
- viewWillAppear: 메서드를 사용하면 뷰가 그려질 때마다 매번 메서드가 호출된다.

### DisclosureDetailController.h ###
#import 

@interface DisclosureDetailController:UIViewController
{
	UILabel *label;
	NSString *message;
}

@property (nonatomic, retain) IBOutlet UILabel *label;
@property (nonatomic, retain) NSString *message;

@end

### DisclosureDetailController.m ###
#import "DisclosureDetailController.h"


@implementation DisclosureDetailController

@synthesize label;
@synthesize message;

-(void)viewWillAppear:(BOOL)animated
{
	label.text = message;
	[super viewWillAppear:animated];
}

-(void)viewDidUnload
{
	self.label = nil;
	self.message = nil;
	[super viewDidUnload];
}

-(void)dealloc
{
	[label release];
	[message release];
	[super dealloc];
}

@end

- nib 파일 생성.
  1.Resources폴더에서 새파일을 추가한다. 좌측 창에서 User Interface를 선택하고, View XIB를 선택하고 파일명은 DisclosureDetail.xib로 설정
 2. 인터페이스 빌더에서 File's Owner를 클릭하고 아이텐티티 인스펙터 4에서 클래스를 DisclosureDetailController로 변경하고 File's Owner 아이콘을 컨트롤 키를 누른 상태에서 view아이콘으로 드래그해 놓자. 그리고 뷰 아웃렛을 선택하자 class를 변경하였기때문에 연결이 끊어져있는 상태이다.
 3. Label을 하나 추가하고 label 아웃렛과 연결한다.

### DisclosureButtonController.h ####
#import 
#import "SecondLevelViewController.h"
@class DisclosureDetailController;

@interface DisclosureButtonController:SecondLevelViewController
{
	NSArray *list;
	DisclosureDetailController *childController;
}

@property (nonatomic, retain) NSArray *list;

@end

### DisclosureButtonController.m ####
#import "DisclosureButtonController.h"
#import "NavAppDelegate.h"
#import "DisclosureDetailController.h"

@implementation DisclosureButtonController
@synthesize list;

-(void)viewDidLoad
{
	NSArray *array = [[NSArray alloc] initWithObjects:@"Toy Story",
					  @"A Bug's Life", @"Toy Story 2", @"Monsters, Inc.",
					  @"Finding Nemo", @"The Incredibles", @"Cars",
					  @"Retatouille", @"WALL-E", @"Up", @"Toy Story 3",
					  @"Cars 2", @"The Bear and the Bow", @"Newt", nil];
	
	self.list = array;
	[array release];
	[super viewDidLoad];
	
}

-(void)viewDidUnload
{
	self.list = nil;
	[childController release];
	childController = nil;
}

-(void)dealloc
{
	[list release];
	[childController release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *DisclosureButtonCellIdentifier = @"DisclosureButtonCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DisclosureButtonCellIdentifier];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:DisclosureButtonCellIdentifier] autorelease];
	
	NSUInteger row = [indexPath row];
	NSString *rowString = [list objectAtIndex:row];
	cell.textLabel.text = rowString;
	cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
	[rowString release];
	return cell;
	
}

#pragma mark -
#pragma mark Table Delegate Methods
// 행이 선택되었을때 반응 하는 메서드 
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Hey, do you see the disclosure Button?"
													message:@"If you're trying to drill down, touch the instead"
												   delegate:nil
										  cancelButtonTitle:@"Won't happen again"
										  otherButtonTitles:nil];
	
	[alert show];
	[alert release];	
}

// 사용자가 상세보기 버튼을 클릭하면 호출되는 델리게이트 메서드
-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
	//childController가 nil일 경우 메모리에 할당하고 초기화 한다.
	if(childController == nil)
		childController = [[DisclosureDetailController alloc] initWithNibName:@"DisclosureDetail" bundle:nil];
	
	// 스택에 밀어넣기 전에 화면상에 출력될 문자열을 할당하는 작업
	childController.title = @"Disclosure Button Pressed";
	NSUInteger row = [indexPath row];
	
	// 선택된 버튼을 확인..
	NSString *selectedMovie = [list objectAtIndex:row];
	NSString *detailMessage = [[NSString alloc] initWithFormat:@"You pressed the disclosure button for %@.", selectedMovie];
	
	childController.message = detailMessage;
	childController.title = selectedMovie;
	
	[detailMessage release];
	
	// 네비게이션 스택에 세부 내용을 보여줄 뷰의 컨트롤러를 밀어 넣는다.
	[self.navigationController pushViewController:childController animated:YES];
	
}

@end




* 두 번쨰 하위컨트롤러 : 체크리스트
- 테이블 뷰를 이용. 액세서리 아이콘을 사용해서, 사용자가 목록에서 단 하나의 항목을 선택할 수 있다.
- SecondLevelViewController의 하위클래스로 만들 필요가 있다.
- Classes 폴더에서 Cocoa Touch Classes를 선택하고, Objective-C class와 Subclass of의 NSObject를 선택하자.
- 파일명은 CheckListController이다. 헤더 파일과 같이 생성

### CheckListController.h ###
#import 
#import "SecondLevelViewController.h"

@interface CheckListController:SecondLevelViewController
{
	NSArray *list;
	NSIndexPath *lastIndexPath;
}

@property (nonatomic, retain) NSArray *list;
@property (nonatomic, retain) NSIndexPath *lastIndexPath;

@end

### CheckListController.m ###
#import "CheckListController.h"

@implementation CheckListController
@synthesize list;
@synthesize lastIndexPath;

-(void)viewDidLoad
{
	NSArray *array = [[NSArray alloc] initWithObjects:@"Who Hash",
					  @"Bubba Gump Shrimp Etouffee", @"Who Pudding", @"Secooby Snacks",
					  @"Everlastring Gobstopper", @"Green Eggs and Ham", @"Soylent Green",
					  @"Hard Tack", @"Lembas Bread", @"Roast Beast", @"Blancmange", nil];
	
	self.list = array;
	
	[array release];
	[super viewDidLoad];
}

-(void)viewDidUnload
{
	self.list = nil;
	self.lastIndexPath = nil;
	
	[super viewDidUnload];
}

-(void)dealloc
{
	[list release];
	[lastIndexPath release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CheckMarkCellIdentifier];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CheckMarkCellIdentifier] autorelease];
	
	// 현재 선택된 셀에서 행에 대한 정보를 추출한다.
	NSUInteger row = [indexPath row];
	// 기존에 선택된 셀에서 행에 대한 정보를 추출한다.
	NSUInteger oldRow = [lastIndexPath row];	
	// 배열에서 값을 갖온 후에 셀의 제목을 할당한다.
	cell.textLabel.text = [list objectAtIndex:row];
	// 액세서리 아이콘을 사용해, 체크 표시를 보여주거나 아예 아무것도 표시하지 않는다.
	// 현재 선택한 앵이, 테이블의 해당 셀이 가리키는 행과 동일하다면, 행이 액세서리 아이콘을 설정해서
	// 체크 표시를 설정하고, 그렇지 않다면 아무것도 설정하지 않는다.
	// lastIndexPath가 nil이 아닌지 여부도 반드시 확인해야 한다.
	// 아무것도 선택되지 않았는되도 row 메서드를 호출하면 nil의 값이 0으로 리턴될수도 있기 때문이다.
	cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;

	return cell;
}

#pragma mark -
#pragma mark Table Delegate Methods
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	int newRow = [indexPath row];
	int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;
	
	// 이전행과 새항이 다르다면
	if(newRow != oldRow)
	{
		// 현재 선택된 셀을 가져와서, 액세서리 아이콘으로 체크 표시 정보를 할당한다.
		UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
		newCell.accessoryType = UITableViewCellAccessoryCheckmark;
		
		// 이전에 선택되었던 셀을 가져와서, 액세서리 아이콘이 아무것도 설정되지 않은 상태로 설정한다.
		UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:lastIndexPath];
		oldCell.accessoryType = UITableViewCellAccessoryNone;
		
		// 방금 선택했던 인덱스 경로 정보를 lastIndexPath에 저정한다.
		lastIndexPath = indexPath;
	}
	
	// 선택한 행이 계속 하이라이트 상태로 표시할 필요가 없기 때문에 선택을 해제한다.
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
	
}

@end

### FistLevelViewController.m 에 아래 내용 추가 ###
#import "CheckListController.h"

-(void)viewDidLoad
{
......
	CheckListController *checkListController = [[CheckListController alloc] initWithStyle:UITableViewStylePlain];
	checkListController.title = @"Check One";
	checkListController.rowImage = [UIImage imageNamed:@"checkmarkControllerIcon.png"];
	[array addObject:checkListController];
	
	[checkListController release];
.....
}




* 세 번째 하위컨트롤러 : 테이블 행 위에 컨트롤 추가
- 테이블 뷰 셀에 하위뷰를 추가해서 모양을 바꾸는 방법
- Classes 폴더에 새로운 파일을 하나 추가 한다. Cocoa Touch Classes를 선택하고 나서 Objective-C와 Subclass of의 NSObject를 선택한다. 파일명은 RowControlsController.m을 입력한다. 헤더 파일까지 같이 생성한다.

### RowControlsController.h ###
#import 
#import "SecondLevelViewController.h"

@interface RowControlsController:SecondLevelViewController
{
	NSArray *list;
}

@property (nonatomic, retain) NSArray *list;
-(IBAction)buttonTapped:(id)sender;
@end

### RowControlsController.m ###
#import "RowControlsController.h"

@implementation RowControlsController
@synthesize list;

-(IBAction)buttonTapped:(id)sender
{
	// 매번 sender에 대한 형변환을 하지 않기 위해서
	UIButton *senderButton = (UIButton *)sender;
	// 버튼의 상위뷰인 테이블 뷰 셀의 인스턴스를 만든다.
	// 사용자가 누른 행과 행의 제목을 얻는다.
	UITableViewCell *buttonCell = (UITableViewCell *)[senderButton superview];
	NSUInteger buttonRow = [[self.tableView indexPathForCell:buttonCell] row];	
	NSString *buttonTitle = [list objectAtIndex:buttonRow];
	
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the button"
													message:[NSString stringWithFormat:@"You tapped the button for %@", buttonTitle]
												   delegate:nil
										  cancelButtonTitle:@"OK"
										  otherButtonTitles:nil];
	[alert show];
	[alert release];	
}

-(void)viewDidLoad
{
	NSArray *array = [[NSArray alloc] initWithObjects:@"R2-R2",
					  @"C3PO", @"Tik-Tok",@"Robby", @"Rosie", @"Uniblab",
					  @"Bender", @"Marvin", @"Lt. Commander Data",
					  @"Evil Brother Lore", @"Optimus Prime", @"Tobor", @"HAL",
					  @"Orgasmatron", nil];
	
	self.list = array;
	[array release];
	[super viewDidLoad];
}

-(void)viewDidUnload
{
	self.list = nil;
	[super viewDidUnload];
}

-(void)dealloc
{
	[list release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *ControlRowIdentifier = @"ControlRowIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ControlRowIdentifier];
	
	if(cell == nil)
	{
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:ControlRowIdentifier] autorelease];
		
		UIImage *buttonUpImage = [UIImage imageNamed:@"button_up.png"];
		UIImage *buttonDownImage = [UIImage imageNamed:@"button_down.png"];
		
		// UIButton의 buttonType 플로퍼티는 읽기 전용으로 선언되어 있으므로 buttonWithType:라는 팩토리
		// 메서드를 사용하여 버튼을 생성. 
		// 사용자화된 버튼을 만들때는 UIButtonTypeCustom을 사용하는데 UIButtonTypeCustom 타입의 버튼은 
		// alloc이나 init를 사용하여 생성할수 없다.
		UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
		button.frame = CGRectMake(0.0f, 0.0f, buttonUpImage.size.width, buttonUpImage.size.height);
		[button setBackgroundImage:buttonUpImage forState:UIControlStateNormal];
		[button setBackgroundImage:buttonDownImage forState:UIControlStateHighlighted];
		[button setTitle:@"Tap" forState:UIControlStateNormal];
		
		// 버튼에 대한 Touch Up Inside 이벤트에 대한 액션 메서들 설정하고 셀의 액세서리 뷰를 할당한다.
		[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
		cell.accessoryView = button;
	}
	
	NSUInteger row = [indexPath row];
	NSString *rowTitle = [list objectAtIndex:row];
	
	cell.textLabel.text = rowTitle;
	
	return cell;	
}

#pragma mark -
#pragma mark Table Delegate Methods
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	NSUInteger row = [indexPath row];
	NSString *rowTitle = [list objectAtIndex:row];
	
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the row."
													message:[NSString stringWithFormat:@"You tapped %@.", rowTitle]
												   delegate:nil
										  cancelButtonTitle:@"OK"
										  otherButtonTitles:nil];
	[alert show];
	[alert release];
	[tableView deselectRowAtIndexPath:indexPath animated:YES];
	
}


@end

### FirstLevelViewController.m ####
#import "RowControlsController.h"

-(void)viewDidLoad
{
.....
	RowControlsController *rowControlsController = [[RowControlsController alloc] initWithStyle:UITableViewStylePlain];
	rowControlsController.title = @"Row Controls";
	rowControlsController.rowImage = [UIImage imageNamed:@"rowControlsIcon.png"];
	[array addObject:rowControlsController];
	
	[rowControlsController release];
.....
}




* 네번째 하위컨트롤러 : 이동 가능한 행
- 또 다른 2차 레벨 뷰 컨트롤러를 만들고 애플리케이션에 추가해볼 것이다.

* 편집모드
- 테이블의 특정 지점에 행을 이동하고 삭제하고, 삽입하는 일련의 동작은 사실구현하기 쉬운 편이다.
- 세가지 동작 모두 테이블 뷰에서 setEditing:animated:를 사용해서, 편집 모드라고 부르는 상태로 전환하면 구현할 수 있다. 이 메서드는 두 개의 불린(Boolean) 값을 인자로 취한다.
- 첫번쨰 불린 값은 편집 모드를 켤 것인지, 끌것인지의 여부
- 두번째 불린 값은 테이블 모양이 바뀔때 애니메이션 효과를 사용할지 여부를 나타낸다.

* 새로운 2차 레벨 컨트롤러 생각하기
- Classes폴더에 새로운 파일을 추가한다. Cocoa Touch Classes를 선택하고, UIViewController subclass아이콘을 선택하자. 파일명은 MoveMeController.m

### MoveMeController.h ###
#import 
#import "SecondLevelViewController.h"

@interface MoveMeController:SecondLevelViewController
{
	NSMutableArray *list;
}

@property (nonatomic, retain) NSMutableArray *list;
-(IBAction)toggleMove;
@end

### MoveMeController.m ###
#import "MoveMeController.h"

@implementation MoveMeController
@synthesize list;

-(IBAction)toggleMove
{
	[self.tableView setEditing:!self.tableView.editing animated:YES];
	
	if(self.tableView.editing)
		[self.navigationItem.rightBarButtonItem setTitle:@"Done"];
	else 
		[self.navigationItem.rightBarButtonItem setTitle:@"Move"];
	
}

-(void)viewDidLoad
{
	if(list == nil)
	{
		NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:
								 @"Eeny", @"Meeny", @"Miney", @"Moe", @"Catch", @"A",
								 @"Tiger", @"By", @"The", @"Toe", nil];
		self.list = array;
		[array release];		
	}
	
	// 네비게이션 바에 위치할 버튼 바 아이템을 생성한다.
	// 제목은 Move
	// 간단한 버튼임을 나타내는 UIBarButtonItemStyleBordered
	// Target과 action이라는 두 갱의 인자는 버튼이 눌릴때 무엇을 해야 할지를 알려주는 역할
	// 버튼에게 언제든지 버튼이 눌리면 toggleMove 메서드를 호출하라고 지정한다.
	UIBarButtonItem *moveButton = [[UIBarButtonItem alloc] initWithTitle:@"Move"
																   style:UIBarButtonItemStyleBordered
																  target:self
																  action:@selector(toggleMove)];
	self.navigationItem.rightBarButtonItem = moveButton;
	[moveButton release];
	[super viewDidLoad];
	
}

-(void)dealloc
{
	[list release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *MoveMeCellIdentifier = @"MoveMeCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MoveMeCellIdentifier];
	
	if(cell == nil)
	{
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MoveMeCellIdentifier] autorelease];
		
		// 표준 액세서리 아이콘은 셀의 accessoryType 프로퍼티를 사용하여 설정할수 있다.
		// 하지만 재배치가 가능한 컨트롤은 표준 액세서리 아이콘의 범주에 속하지 않는다.
		// 편집 모드상에서만 테이블에 표시되는 컨트롤이기 때문이다.
		// 재배치 가능한 컨트롤을 활성화시키려면 셀에 직접 속성을 설정해야 한다.
		// 이 showReorderControl 속성을 YES로 지정한다고 하더라도 
		// 테이블이 편집 모드로 바뀌기 전까지는 재배치 컨트롤이 실제로 표시되지 않는다.
		cell.showsReorderControl = YES;
	}
	
	NSUInteger row = [indexPath row];
	cell.textLabel.text = [list objectAtIndex:row];
	
	return cell;
}

// 이 메서드는 테이블 뷰에게 특정 행이 삭제가능한 상태인지 또는 특정 지점에 삽입 가능한지를 물어보는 역할을 한다.
// 재배치만을 지원할 것이기때문에 UITableViewCellEditingStyleNone를 리턴해서 행의 삽입이나 삭제를 지원하지 않는다고 알려준다.
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
	return UITableViewCellEditingStyleNone;
}

// 이 메서드는 각행마다 호출되어서 특정열의 이동을 원천적으로 막을 수 있는 기능을 제공한다.
// No를 리턴하면 재배치 컨트롤이 해당 행에 대해서는 나타나지 않고, 사용자는 현재 위치에서 더 이상 행을 움직일 수 없다.
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
	return YES;
}

// 실제로 사용자가 행을 움직일 때 호출되는 메서드 
// 매개변수는 하나는 움직인 행을 가리키고, 다른 하나는 행의 새로운 위치를 가리킨다.
// 데이터 모델을 업데이트해서 실제 행과 데이터상의 행 위치를 동기화 하지 않으면, 나중에 화면을 표시 할때 문제가 있을수 있다.
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
	// 움직여야 하는 행에 대한 정보를 가져온다.
	NSUInteger fromRow = [fromIndexPath row];
	// 행의 새위치에 대한 정보를 가져온다.
	NSUInteger toRow = [toIndexPath row];
	
	// 이동이 일어나려는 객체에 대한 포인터를 가져와서 저장한다.
	// 배열에서 객체를 지울때, 객체가 아예 없어지는 일을 막기 위해서 말이다.
	id object = [[list objectAtIndex:fromRow] retain];
	[list removeObjectAtIndex:fromRow];
	
	// 미리 저장해둔 새로운 위치에 객체를 다시 삽입한다.
	[list insertObject:object atIndex:toRow];
	
	[object release];
}

@end

### FirstLevelViewController.m ###
#import "MoveMeController.h"

-(void)viewDidLoad
{
......
	MoveMeController *moveMeController = [[MoveMeController alloc] initWithStyle:UITableViewStylePlain];
	moveMeController.title = @"Move Me";
	moveMeController.rowImage = [UIImage imageNamed:@"moveMeIcon.png"];
	[array addObject:moveMeController];
	
	[moveMeController release];
......
}




* 다섯 번째 하위컨트롤러 : 삭제 가능한 행
- Classes폴더에 새파일을 추가한다. Cocoa Touch Classes을 선택하고 나서, Objective-C class를 선택하고 Subclass of의 NSObject를 선택한다. 파일 이름은 DeleteMeController.m을 입력한다. 헤더파일도 같이 생성한다.

### DeleteMeController.h ###
#import 
#import "SecondLevelViewController.h"

@interface DeleteMeController:SecondLevelViewController
{
	NSMutableArray *list;
}

@property (nonatomic, retain) NSMutableArray *list;
-(IBAction)toggleEdit:(id)sender;
@end

### DeleteMeController.m ###
#import "DeleteMeController.h"

@implementation DeleteMeController
@synthesize list;

// 현재 편집 모드가 아니면 편집 모드로 전환하거나 편집 모드라면 일반 모드로 전환
-(IBAction)toggleEdit:(id)sender
{
	[self.tableView setEditing:!self.tableView.editing animated:YES];
	
	if(self.tableView.editing)
		[self.navigationItem.rightBarButtonItem setTitle:@"Done"];
	else
		[self.navigationItem.rightBarButtonItem setTitle:@"Delete"];
}

-(void)viewDidLoad
{
	if(list == nil)
	{
		NSString *path = [[NSBundle mainBundle] pathForResource:@"computers" ofType:@"plist"];
		
		NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:path];
		
		self.list = array;
		[array release];
	}
	
	UIBarButtonItem *editButton = [[UIBarButtonItem alloc] initWithTitle:@"Delete"
																   style:UIBarButtonItemStyleBordered
																  target:self
																  action:@selector(toggleEdit:)];
	self.navigationItem.rightBarButtonItem = editButton;
	[editButton release];
	[super viewDidLoad];
}

-(void)dealloc
{
	[list release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *DeleteMeCellIdentifier = @"DeleteMeCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DeleteMeCellIdentifier];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:DeleteMeCellIdentifier] autorelease];
	
	NSUInteger row = [indexPath row];
	cell.textLabel.text = [list objectAtIndex:row];
	return cell;
}

#pragma mark -
#pragma mark Table View Data Source Methods
// 사용자가 편집동작을 수행할때, 즉 삽입 또는 삭제를 수행할때 테이블 뷰가 이 메서드를 호출하게 된다.
// 첫번째 인자는 테이블 뷰에서 어떤 행이 편집 중인지를 나타낸다.
// 두번째 인자인 editingStyle는 방금 어떤 종류의 편집 동작이 수행되었는지를 알려주는 준다.
// 그중 하나는 UITableViewCellEditingStyleNone / UITableViewCellEditingStyleDelete / UITableViewCellEditingStyleInsert
// 이 메서드에는 절대 UITableViewCellEditingStyleNone이 절대로 이 메서드에는 전달되지 않는데, 이 옵션은 해당 행을 편집 할 수 없다는 사실을 나타내는 데 사용되기 때문이다.
// 세번째 인자인 indexPath는 현재 편집 중인 행이 어는 것인지를 알려준다.
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
	// indexPath를 통해 편집 중인 행에 대한 정보를 가져온다.
	NSUInteger row = [indexPath row];
	// 변경 가능한 배열에서 앞서 생성했던 객체를 삭제 한다.
	[self.list removeObjectAtIndex:row];
	
	// 테이블이 행을 삭제 할때 어떤 애니메이션 타입을 사용해서 삭제효과를 나타낼지를 알려주는 용도..
	[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

@end

### FirstLevelViewController.m ###
#import "DeleteMeController.h"

-(void)viewDidLoad
{
......
	DeleteMeController *deleteMeController = [[DeleteMeController alloc] initWithStyle:UITableViewStylePlain];
	deleteMeController.title = @"Delete Me";
	deleteMeController.rowImage = [UIImage imageNamed:@"deleteMeIcon.png"];
	[array addObject:deleteMeController];
	
	[deleteMeController release];
......
}




* 여섯 번째 하위컨트롤러 : 편집 가능한 상세 창
- 편집 가능한 상세 뷰를 구현하는 방법

* 데이터 모델 객체 생성하기
- Xcode에서 Classes폴더를 클릭해서 새로운 파일을 만들자. Cocoa Touch Classes를 선택하고 Objective-C class와 Subclass of의 NSObject를 선택한다. 클래스의 이름은 President.m으로 하고 헤더 파일 생성 체크박스도 선택한다.

#### President.h ####
#define kPresidentNumberKey @"President"
#define kPresidentNameKey @"Name"
#define kPresidentFromKey @"FromYear"
#define kPresidentToKey @"ToYear"
#define kPresidentPartyKey @"Party"

#import 

@interface President:NSObject  
{
	int number;
	NSString *name;
	NSString *fromYear;
	NSString *toYear;
	NSString *party;
}

@property int number;
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *fromYear;
@property (nonatomic, retain) NSString *toYear;
@property (nonatomic, retain) NSString *party;

@end

#### President.m ####
#import "President.h"

@implementation President

@synthesize number;
@synthesize name;
@synthesize fromYear;
@synthesize toYear;
@synthesize party;

-(void)dealloc
{
	[name release];
	[fromYear release];
	[toYear release];
	[party release];
	[super dealloc];
}

// encodeWithCoder: / initWithCoder: 메서드는 하드 디스크에 데이터를 저장할때 사용하는 NSCoding프로토콜의 일부분
#pragma mark -
#pragma mark NSCoding
// encodeWithCoder: 메서드는 저장할 객체를 인코딩하는 역할
-(void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeInt:self.number forKey:kPresidentNumberKey];
	[coder encodeObject:self.name forKey:kPresidentNameKey];
	[coder encodeObject:self.fromYear forKey:kPresidentFromKey];
	[coder encodeObject:self.toYear forKey:kPresidentToKey];
	[coder encodeObject:self.party forKey:kPresidentPartyKey];
}

// initWithCoder: 메서드는 저장소로부터 새로운 객체를 생성하는 사용된다.
-(id)initWithCoder:(NSCoder *)coder
{
	if(self = [super init])
	{
		self.number = [coder decodeIntForKey:kPresidentNumberKey];
		self.name = [coder decodeObjectForKey:kPresidentNameKey];
		self.fromYear = [coder decodeObjectForKey:kPresidentFromKey];
		self.toYear = [coder decodeObjectForKey:kPresidentToKey];
		self.party = [coder decodeObjectForKey:kPresidentPartyKey];
	}
	
	return self;
}


@end

* 컨트롤러 생성하기
- 두개에 새로운 컨트롤러를 만든것이다. 하나는 편집할 목록을 보여주는데 사용, 하나는 목록에서 선택된 아이템의 상세항목을 편집하는 뷰로 사용
- Xcode에서 Classes폴더를 선택하고 새로운 파일을 추가한다. Cocoa Touch Classes를 선택하고 Objective-C class와 Subclass of의 NSObject를 선택한다. 이름은 PresidentsViewController.m을 입력하고 헤더 파일도 같이 생성한다.
- 위와 같은 방법으로 PresidentDetailController.m파일도 생성. 역시 헤더 파일도 같이 생성한다.

#### PresidentsViewController.h ####
#import 
#import "SecondLevelViewController.h"

@interface PresidentsViewController:SecondLevelViewController 
{
	NSMutableArray *list;
}

@property (nonatomic, retain) NSMutableArray *list;

@end

#### PresidentsViewController.m ####
#import "PresidentsViewController.h"
#import "PresidentDetailController.h"
#import "President.h"

@implementation PresidentsViewController
@synthesize list;

-(void)viewDidLoad
{
	NSString *path = [[NSBundle mainBundle] pathForResource:@"Presidents" ofType:@"plist"];
	
	// 인코딩된 저장소를 임시로 담고 있는 데이터 객체
	NSData *data;
	// 저장소에서 실제로 객체를 복원하는데 사용되는 객체
	NSKeyedUnarchiver *unarchiver;
	
	// 속성 리스트를 data로 로딩
	data = [[NSData alloc] initWithContentsOfFile:path];
	// unarchiver를 초기화하는데 data를 사용
	unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
	
	NSMutableArray *array = [unarchiver decodeObjectForKey:@"Presidents"];
	self.list = array;
	[unarchiver finishDecoding];
	[unarchiver release];
	[data release];
	
	[super viewDidLoad];
	
}

-(void)viewWillAppear:(BOOL)animated
{
	// 사용자가 상세보기 뷰에서 무언가를 변경한다면, 상위뷰가 변경된 데이터를 보여 주어야 하기 때문에
	// 데이터를 다시 로딩한다.
	[self.tableView reloadData];
	[super viewWillAppear:animated];
}

-(void)dealloc
{
	[list release];
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return [list count];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *PresidentListCellIdentifier = @"PresidentListCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentListCellIdentifier];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:PresidentListCellIdentifier] autorelease];
	
	NSUInteger row = [indexPath row];
	President *thePres = [self.list objectAtIndex:row];
	cell.textLabel.text = thePres.name;
	cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", thePres.fromYear, thePres.toYear];
	
	return cell;
	
}
	
#pragma mark -
#pragma mark Table Data Delegate Methods
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	NSUInteger row = [indexPath row];
	President *prez = [self.list objectAtIndex:row];
	
	// 선택된 셀에 대한 상세 뷰를 생성한다.
	PresidentDetailController *childController = [[PresidentDetailController alloc] initWithStyle:UITableViewStyleGrouped];
	
	childController.title = prez.name;
	childController.president = prez;
	
	[self.navigationController pushViewController:childController animated:YES];
	[childController release];
}


@end

#### PresidentDetailController.h ####
#import 

@class President;

#define kNumberOfEditableRows 4
#define kNameRowIndex 0
#define kFromYearRowIndex 1
#define kToYearRowIndex 2
#define kPartyIndex 3

#define kLabelTag 4096

// UITextFieldDelegate 프로토콜을 따르면 사용자가 텍스트 필드의 값을 변경했는지 여부를 알수 있다.
@interface PresidentDetailController:UITableViewController 
{
	// 이객체는 실제로 이 뷰를 편집할 때 사용되는 객체로, 현재 선택된 행의 부모 컨트롤러의 
	//tableView:didSelectRowAtIndexPath: 메서드에서 설정된다.
	President *president;
	
	// Index 상수 kNameRowIndex / kFromYearRowIndex / kToYearRowIndex / kPartyIndex에 대응하는
	// 레이블의 목록을 가지고 있는 배열
	NSArray *fieldLabels;
	
	// 사용자가 텍스트 필드에서 변경한 값을 임시로 가지고 있게 하는 객체
	// 사용자가 cancel버튼을 선택할수도 있기 때문에 임시로 저장 
	// 변경사항을 통보받으면 키를 나타내는 속성의 이름을 사용해서 딕셔너리에 새로운 값을 저장한다.
	// UITextFieldDelegate가 이 사실을 통보해준다.
	NSMutableDictionary *tempValues;
	
	// 사용자가 텍스트 필드 중 하나를 클릭할때 textFieldBeingEdited는 해당 텍스트 필드를 가리키도록 지정
	UITextField *textFieldBeingEdited;
}

@property (nonatomic, retain) President *president;
@property (nonatomic, retain) NSArray *fieldLabels;
@property (nonatomic, retain) NSMutableDictionary *tempValues;
@property (nonatomic, retain) UITextField *textFieldBeingEdited;

-(IBAction)cancel:(id)sender;
-(IBAction)save:(id)sender;
-(IBAction)textFieldDone:(id)sender;

@end

#### PresidentDetailController.m ####
#import "PresidentDetailController.h"
#import "President.h"

@implementation PresidentDetailController
@synthesize president;
@synthesize fieldLabels;
@synthesize tempValues;
@synthesize textFieldBeingEdited;

// 사용자가 cancel버튼을 누를 때 이 메서드가 호출된다.
-(IBAction)cancel:(id)sender
{
	// 현재 뷰를 스택에서 꺼내오고(pop) 이전 뷰는 스택의 상단으로 한 단계 위치를 이동하게 된다.
	[self.navigationController popViewControllerAnimated:YES];
}

// Save버튼을 누를 때 호출되는 메서드.
// Save버튼을 누르면 사용자가 이미 입력했던 값들이 tempValues 딕셔너리에 저장된다.
-(IBAction)save:(id)sender
{
	// 만약 아직 textField가 편집 상태일 경우 현재 편집중인 textField에서 데이터를 가져와서
	// tempValues에 저장한다.
	// 현재 편집 상태인지는 textFieldBeingEdited가 nil인지 아닌지 확인하면된다.
	// textFieldDidBeginEditing: 메서드에서 현재에 textField값을 저장해준다.
	if(textFieldBeingEdited != nil)
	{
		NSNumber *tagAsNum = [[NSNumber alloc] initWithInt:textFieldBeingEdited.tag];
		[tempValues setObject:textFieldBeingEdited.text forKey:tagAsNum];
		[tagAsNum release];
	}
	
	// 딕셔너리의 모든 키와 값을 조회하기 위해, 행 번호를 키로 활용하는 열거문을 사용
	// NSDictionary에는 int와 같은 기본 데이터형은 저장할 수 없다. 이때문에 int형 대신 행번호를 사용해서
	// NSNumber 객체를 생성해서 사용한다.
	for(NSNumber *key in [tempValues allKeys])
	{
		// intValue메서드를 사용해서 이 키가 가리키는 값을 다시 int형으로 변환..
		switch ([key intValue])
		{
			// 매칭되는 데이터를 다시 president 객체의 지정된 필드에 저장.
			case kNameRowIndex :
				president.name = [tempValues objectForKey:key];
				break;
			case kFromYearRowIndex :
				president.fromYear = [tempValues objectForKey:key];
				break;
			case kToYearRowIndex :
				president.toYear = [tempValues objectForKey:key];
				break;
			case kPartyIndex :
				president.party = [tempValues objectForKey:key];
				break;
			default :
				break;
		}
	}
	
	// 데이터를 저장하고 뷰 계층에서 한 단계 위로 올라간다.
	[self.navigationController popViewControllerAnimated:YES];
	
	//  사용자가 편집을 하였기 때문에 무보 뷰의 데이블에게 데이터를 다시 로딩하라고 알려준다.
	NSArray *allControls = self.navigationController.viewControllers;
	UITableViewController *parent = [allControls lastObject];
	[parent.tableView reloadData];
	
}

// 사용자가 Done 버튼을 클릭시 호출된다.
-(IBAction)textFieldDone:(id)sender
{
	// 키보드를 보이지 않게 처리한다.
	[sender resignFirstResponder];
}

-(void)viewDidLoad
{
	NSArray *array = [[NSArray alloc] initWithObjects:@"Name:", @"From:", @"To:", @"Party:", nil];
	
	self.fieldLabels = array;
	[array release];
	
	// Cancel버튼을 네비게이션 바 좌측에 추가한다. 버튼 클릭시 cancel:메서드가 반응하게 한다.
	UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:@"Cancel"
																	 style:UIBarButtonItemStylePlain
																	target:self
																	action:@selector(cancel:)];
	self.navigationItem.leftBarButtonItem = cancelButton;
	[cancelButton release];
	
	// Save버튼을 네비게이션 바 우측에 추가한다. 버튼 클릭시 save:메서드가 반응하게 한다.
	// 또한 style을 UIBarButtonItemStyleDone으로 지정하여 파란색이되게 한다.
	UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithTitle:@"Save"
																   style:UIBarButtonItemStyleDone
																  target:self
																  action:@selector(save:)];
	self.navigationItem.rightBarButtonItem = saveButton;
	[saveButton release];
	
	// 변경가능한 NSDictionary 객체를 하나 생성하여 tempValues에 할당한다.
	// 사용자가 cancel버튼을 누를수도 있기 때문에 곧바로 President 객체를 바로 변경하는것은 
	// 원본 데이터를 되돌리기 쉽지 않기 때문이다.
	NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
	self.tempValues = dict;
	[dict release];
	[super viewDidLoad];
}

-(void)dealloc
{
	[textFieldBeingEdited release];
	[tempValues release];
	[president release];
	[fieldLabels release];
	
	[super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	return kNumberOfEditableRows;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	static NSString *PresidentCellIdentifier = @"PresidentCellIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PresidentCellIdentifier];
	
	if(cell == nil) 
	{
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:PresidentCellIdentifier] autorelease];
		
		// 해당 Cell에 Label를 하나 추가 하고 텍스트 정렬을 right로 하고 bold처리를 한다.
		UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
		label.textAlignment = UITextAlignmentRight;
		label.tag = kLabelTag;
		label.font = [UIFont boldSystemFontOfSize:14];
		[cell.contentView addSubview:label];
		[label release];
		
		// 사용자가 실제로 내용을 입력하게 되는 TextField를 추가한다.
		UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(90, 12, 200, 25)];
		// 사용자가 편집을 시작했을때 데이터가 지워지기를 원하지 않기 때문에 clearsOnBeginEditing은 ON으로 설정한다.
		textField.clearsOnBeginEditing = NO;
		// 텍스트 필드의 델리케이트를 self로 설정함으로써 UITextFieldDelegate 프로토콜의 적합한 메서드를
		// 구현하면 텍스트 필드가 해당 이벤트를 알려주도록 한다.
		[textField setDelegate:self];
		// 키보드의 리턴 키에 해당하는 타입도 설정한다.
		// returnKeyType 을 UIReturnKeyDone으로 설정하여 키보드의 우측 하단의 키에 적을 텍스트를 명시한다.
		textField.returnKeyType = UIReturnKeyDone;
		// 텍스트 필드가 Did End on Exit 이벤트를 발생시킬 때 textFieldDone: 메서드가 호출되게 한다.
		[textField addTarget:self action:@selector(textFieldDone:) forControlEvents:UIControlEventEditingDidEndOnExit];
		[cell.contentView addSubview:textField];		
	}
	
	// 이 셀이 어떤 행을 나타낼지를 지정한다.
	NSUInteger row = [indexPath row];
	
	// 셀에 있는 레이블과 텍스트 필드에 대한 참조를 획득한다.
	// label의 경우는 tag를 사용해서 찾으면 쉽제 찾을수 있다.
	UILabel *label = (UILabel *)[cell viewWithTag:kLabelTag];
	UITextField *textField = nil;
	
	// 셀의 contentView의 하위 뷰에 단 하나의 텍스트 필드만 존재한다고 가정하여...
	// 각 하위뷰를 순환하고 해당 뷰가 UITextField라면 textField에 할당한다.
	for(UIView *oneView in cell.contentView.subviews)
	{
		if([oneView isMemberOfClass:[UITextField class]])
			textField = (UITextField *)oneView;
	}
	
	// 이 행이 가르키는 president 객체의 필드값에 적합한 값을 할당.
	label.text = [fieldLabels objectAtIndex:row];
	
	// NSDictionary 객체에서 값을 찾기 위해서 NSUInteger 객체를 NSNumber 형태로 변경한다.
	// 이값을 key로 사용하기 때문에..
	NSNumber *rowAsNum = [[NSNumber alloc] initWithInt:row];
	
	// -(BOOL)containsObject:(id)anObject 는 현재 배열에 anObject오브젝트를 포함하면 YES 아니면 NO
	switch (row) {
		// case 0 : 과 같은 것이다.
		// tempValues에 값이 들어 있지 않다면, 이 필드에 대해서는 아직 변경사항이 없는것으로 간주하고 President에 있는 값을 할당하고
		// 값이 있다면 변경되었다고 판단하고 tempValues에 있는 값으로 할당한다.
		case kNameRowIndex :
			if([[tempValues allKeys] containsObject:rowAsNum])
				textField.text = [tempValues objectForKey:rowAsNum];
			else
				textField.text = president.name;
			break;
		// case 1 : 과 같은 것이다.
		case kFromYearRowIndex :
			if([[tempValues allKeys] containsObject:rowAsNum])
				textField.text = [tempValues objectForKey:rowAsNum];
			else
				textField.text = president.fromYear;
			break;
		// case 2 : 과 같은 것이다.
		case kToYearRowIndex :
			if([[tempValues allKeys] containsObject:rowAsNum])
				textField.text = [tempValues objectForKey:rowAsNum];
			else
				textField.text = president.toYear;
			break;
		// case 3 : 과 같은 것이다.
		case kPartyIndex :
			if([[tempValues allKeys] containsObject:rowAsNum])
				textField.text = [tempValues objectForKey:rowAsNum];
			else
				textField.text = president.party;
			break;
		default :
			break;
			
	}
	
	// 만약 우리가 사용하고 있는 필드가 현재 편집중이라면 우리가 textFieldBeingEdited에 가지고 있는 값은
	// 더 이상 유효하지 않기 때문에 textFieldBeingEdited를 nil로 설정해야 한다. 
	// 텍스트 필드가 해제되거나 재사용되면 텍스트 필드의 델리게이트가 호출되고, tempValues 딕셔너리에 이미 
	// 올바른 값이 들어가 있을것이다.
	if(textFieldBeingEdited == textField)
		textFieldBeingEdited = nil;
	
	// 텍스트 필드의 tag에 어떤 행을 가리키는지를 설정해서, 어떤 필드가 텍스트 필드의 델리게이트 메서드를
	// 호출하는지를 알려 주도록하자.
	textField.tag = row;
	[rowAsNum release];
	return cell;
	
}

#pragma mark -
#pragma mark Table Delegate Methods
// 행을 편집 상태로 만드는것이지 하이라트 상태로 만드는것을 원치 않기 때문에
// 행이 선택되지 전에 호출되는 willSelectRowAtIndexPath: 메서드에서 nil 을 리턴한다.
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	return nil;
}

#pragma mark Text Field Delegate Methdos
// 델리게이트가 퍼스트 리스폰더가 될 때 호출되는 메서드다. 사용자가 텍스트 필드를 눌러 키보드를 띄우면 알 수 있게 된다.
// 이 메서드에서 현재 편집 중인 필드를 가리키는 포인터를 저장.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
	self.textFieldBeingEdited = textField;
}

// 텍스트 필드에 대한 편집을 멈추는 경우에 호출된다.
// 사용자가 다른 텍스트 필드를 누르거나 Done버튼을 누를 때등....
-(void)textFieldDidEndEditing:(UITextField *)textField
{
	NSNumber *tagAsNum = [[NSNumber alloc] initWithInt:textField.tag];
	[tempValues setObject:textField.text forKey:tagAsNum];
	[tagAsNum release];
}
@end



* 한 가지가 더 남아 있다..
- 키보드에서 Done버튼 대신  Return 버튼을 가지고 있어서 버튼을 누르면 사용자는 다음 행의 텍스트 필드로 이동하게 하는것..

먼저 tableView:cellForRowAtIndexPath:메서드에서 다음 코드를 주석처리 한다.
#### PresidentDetailController.m ####
//		textField.returnKeyType = UIReturnKeyDone;

그리고 textFieldDone:메서드에서 sender에게 다음 필드가 퍼스트 리스톤더 상태가 되었음을 알려주어야 한다.
#### PresidentDetailController.m ####
// 사용자가 Done 버튼을 클릭시 호출된다.
-(IBAction)textFieldDone:(id)sender
{
	// 셀은 어떤 행을 나타내고 있는지 알지 못한다.
	// 하지만, 테이블 뷰에서는 해당 셀이 어떤 행을 나타내는지를 알고 있다. 즉, 테이블 뷰의 참조를 해결할 수 있는 문제라는 말이다.
	// 이 액션 메서드를 실행하는 텍스트 필드가 테이블 셀 뷰의 콘텐츠 뷰의 하의뷰라는 것은 익히 알고 있다. 
	// 즉, sender의 상의 뷰의 상위뷰를 알아야 한다는 말이다.
	
	// 이 경우에 sender는 편집중인 텍스트 필드이다.
	// sender의 상위뷰는 텍스트 뷰와 레이블의 그룹으로 구성된 콘텐츠 뷰다.
	// sender의 상위뷰의 상위뷰는 이 콘테츠 뷰를 포함 하는 셀이 된다.
	UITableViewCell *cell = (UITableViewCell *)[[sender superview] superview];
	// 셀이 포함하고 있는 테이블 뷰에 전급.
	UITableView *table = (UITableView *)[cell superview];
	// 셀이 어떤 행을 나타내는지를 물어보자. 응답으로 NSIndexPath를 얻을 수 있고, 여기에서 행 정보를 얻어낼 수 있다.
	NSIndexPath *textFieldIndexPath = [table indexPathForCell:cell];
	NSUInteger row = [textFieldIndexPath row];
	
	// row값을 하나 증가시켜 현재 선택되어 있는 행의 다음 행을 가르키도록 세팅
	row++;
	
	// 마지막 행 번호를 넘어간다면 다시 row를 0으로 세팅.
	if(row >= kNumberOfEditableRows)
		row = 0;
	
	// 다음행을 가리키는 새로운 NSIndexPath를 만들어서, 다음 행을 현재 나타나게 되는 셀에 대한 참조를 얻어 인덱스 경로로 사용하자.
	NSInteger newIndex[] = {0, row};
	NSIndexPath *newPath = [[NSIndexPath alloc] initWithIndexes:newIndex length:2];
	UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:newPath];
	[newPath release];
	
	// 셀의 콘텐츠 뷰가 가지고 있는 하위뷰를 하나하나씩 순회하도록한다.
	UITextField *nextField = nil;
	for(UIView *oneView in nextCell.contentView.subviews)
	{
		if([oneView isMemberOfClass:[UITextField class]])
		{
			nextField = (UITextField *)oneView;
		}
		// 새로운 텍스트 필드가 퍼스트 리스폰더가 되었음.
		[nextField becomeFirstResponder];
	}
	
	
	// 키보드를 보이지 않게 처리한다.
	//[sender resignFirstResponder];
}
:

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

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:07

* 테이블
- 테이블은 데이터의 목록을 표시한다.
- 테이블 목록의 각각의 항목은 행(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:UITableViewCellStyleValue2 reuseIdentifier: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 객체를 만드는것을 위미한다.
- 얕은 복사는 해당 메모리 영역을 가리키는 주소 값만을 복사하는것을 의미한다. 보통 얇은 복사를 하게 되면 한 메모리 영역을 두 개의 포인터가 가리키게 되므로, 한쪽에서 할당된 메모리를 해제하면 다른 한쪽에서 사용할 수 있는 메모리 영역을 잃게 되는것이다.
- 깊은 복사는 메모리 영역 전체를 별도로 복사하는것을 의미한다.

* 카테고리 사용
- 카테고리를 사용하면 하위클래스를 만들지 않고 이미 존재하는 객체에 메서드를 추가할 수 있다.

### NSDictionary-MutableDeepCopy.h ####
#import 

@interface NSDictionary (MutableDeepCopy)
-(NSMutableDictionary *)mutableDeepCopy
@end

### NSDictionary-MutableDeepCopy.m ####
#import "NSDictionary-MutableDeepCopy.h"
-(NSMutableDictionary *)mutableDeepCopy
{
	NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
	
	NSArray *keys = [self allKeys];
	
	for( id key in keys )
	{
		id oneValue = [self valueForKey:key];
		id oneCopy = nil;
		
		if([oneValue respondsToSelector:@selector(mutableDeepCopy)])
			oneCopy = [oneValue mutableDeepCopy];
		else if([oneValue respondsToSelector:@selector(mutableCopy)])
			oneCopy = [oneValue mutableCopy];
		
		if(oneCopy == nil)
			oneCopy = [oneValue copy];
		
		[ret setValue:oneCopy forKey:key];
	}
	
	return ret;

}








### SectionsViewController.h ###
#import 

@interface SectionsViewController : UIViewController 
{
	// 검색 결과에 따라서 테이블에게 자신의 내용을 갱신하라고 전달할 필요가 있기 때문에 
	UITableView *table;
	// 검색할 때 필요한 컨트롤
	UISearchBar *search;
	// 원본 데이터가 들어 있는 NSDictionary
	NSDictionary *allNames;
	// 검색된 결과가 들어 있는 NSMutableDictionary
	NSMutableDictionary *names;
	// 검색된 결과가 들어가는 NSMutableArray
	NSMutableArray *keys;
	// 인덱스가 cancel 버튼위에 있는 것을 막기 위해서
	BOOL isSearching;	
}

@property (nonatomic, retain) IBOutlet UITableView *table;
@property (nonatomic, retain) IBOutlet UISearchBar *search;
@property (nonatomic, retain) NSDictionary *allNames;
@property (nonatomic, retain) NSMutableDictionary *names;
@property (nonatomic, retain) NSMutableArray *keys;

-(void)resetSearch;
-(void)handleSearchForTerm:(NSString *)searchForm;

@end


### SectionsViewController.m ###
#import "SectionsViewController.h"
#import "NSDictionary-MutableDeepCopy.h"

@implementation SectionsViewController
@synthesize names;		// NSMutableDictionary
@synthesize keys;		// NSMutableArray
@synthesize table;		// UITableView
@synthesize search;		// UISearchBar
@synthesize allNames;	// NSDictionary

#pragma mark -
#pragma mark Custom Methods
// 이 메소드는 검색을 취소하거나 검색어가 바뀌면 호출된다.
// allNames의 뮤터블 복사본을 만들어서 names에 할당하고 그런 후에 keys 배열을 갠신해서 알파벳의 모든 문자를 포함하게 한다.
-(void)resetSearch
{
	self.names = [self.allNames mutableDeepCopy];
	
	// 검색 결과에 한 섹션의 모든 값이 필요 없다면 그 섹션 자체도 제거해야 하기 때문에 수정가능한 배열로 선언한다.
	NSMutableArray *keyArray = [[NSMutableArray alloc] init];
	
	// 테이블뷰 인덱스 위에 검색창을 나타내기 위한 돋보기 아이콘 추가를 위한 값
	[keyArray addObject:UITableViewIndexSearch];
	
	[keyArray addObjectsFromArray:[[self.allNames allKeys] sortedArrayUsingSelector:@selector(compare:)]];
	self.keys = keyArray;
	[keyArray release];
	
}

// 비록 검색을 검색창 델리게이트 메서드에서 시작되지만 실제 아래 메소드가 검색을 담당한다.
// -(void)searchBarSearchButtonClicked:searchBar
// -(void)searchBar:textDidChange
-(void)handleSearchForTerm:(NSString *)searchTerm
{
	// 이 배열을 나중에 빈 섹션(검색 결과가 없는)을 지울때 사용한다.
	NSMutableArray *selectionsToRemove = [[NSMutableArray alloc] init];
	[self resetSearch];
	
	// 새로 복구한 keys 배열의 모든 키에 빠른 열거를 수행한다.
	for(NSString *key in self.keys)
	{
		// 매번 루프를 돌면서 현재 키에 해당하는 names배열을 구한다.
		NSMutableArray *array = [names valueForKey:key];
		// names 배열에서 지울 값들을 담은 배열을 선업한다.
		NSMutableArray *toRemove = [[NSMutableArray alloc] init];
		
		// 현재 배열의 전체 이름을 훑어보기 위해서 빠른 열거를 수행한다.
		for(NSString *name in array)
		{
			// NSString의 메서드 중 하나인 문자열 안의 부분 문자열의 위치를 리턴하는 메서드를 사용한다.
			// location과 length인 멤버 두개가 있는 NSRange struct인 rangeOfString을 사용한다.
			// 만약 섬색어가 없으면 location은 NSNotFound로 설정된다.
			// NSCaseInsensitiveSearch 옵션을 사용하여 대소문자 구분을 무시한다.
			// 검색된 결과가 없다면 추후에 지우기 위해서 지운는 배열에 추가한다.
			if([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location == NSNotFound)
				[toRemove addObject:name];
		}
		
		// 만약 검색된 결과가 전부 없다면 해당 섹션을 삭제하기 위해서 배열에 추가해 놓는다.
		if([array count] == [toRemove count])
			[selectionsToRemove addObject:key];
		
		// 원래 값에서 검색 결과가 없는 배열을 삭제 한다.
		[array removeObjectsInArray:toRemove];
		[toRemove release];
	}
	
	// 필요없는 섹션을 삭제한다.
	[self.keys removeObjectsInArray:selectionsToRemove];
	[selectionsToRemove release];
	[table reloadData];
	
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
	
	NSString *path = [[NSBundle mainBundle] pathForResource:@"sortednames" ofType:@"plist"];
	
	NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
	self.allNames = dict;
	[dict release];
	
	[self resetSearch];
	
	// 일반적으로 애플리케이션이 실행될 때는 사용자가 테이블을 눈으로 확인하기 전에 reloadData가 실행된다.
	// 그래서 호출할 필요는 없지만 setContentOffset를 호출하기 위해서는 테이블의 데이터들이 올바르게
	// 로딩된 상태인지를 반드시 확인해야 하기 때문에 reloadData를 호출한다.
	// reloadData가 호출되면 데이터들이 로딩되었다는 것을 보장한다.
	[table reloadData];
	// 테이블의 컨텐츠를 검색창의 높이인 44픽셀만큼 제외하고 계산하다. 그래서 첨에는 검색창이 보이지 않는다.
	[table setContentOffset:CGPointMake(0.0f, 44.0f) animated:NO];
	
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;

	self.table = nil;
	self.search = nil;
	self.allNames = nil;
	self.names = nil;
	self.keys = nil;
	
}


- (void)dealloc {
	[table release];
	[search release];
	[allNames release];
	[keys release];
	[names release];
    [super dealloc];
}

#pragma mark -
#pragma mark Table View Data Source Methods
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
	// 테이블 뷰는 최소한 하나의 섹션을 가져야 하기 때문에 검색된 결과가 하나도 없을수 있기 때문에
	// 검색된 결과가 없을때는 section의 값을 1개로 보장한다.
	return ([keys count] > 0) ? [keys count] : 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
	if([keys count] == 0)
		return 0;
	
	NSString *key = [keys objectAtIndex:section];
	NSArray *nameSection = [names objectForKey:key];
	
	return [nameSection count];
	
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
	NSUInteger section = [indexPath section];
	NSUInteger row = [indexPath row];
	
	NSString *key = [keys objectAtIndex:section];
	NSArray *nameSection = [names objectForKey:key];
	
	static NSString *SectionTableIdentifier = @"SectionTableIdentifier";
	
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SectionTableIdentifier];
	
	if(cell == nil)
		cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SectionTableIdentifier] autorelease];
	
	cell.textLabel.text = [nameSection objectAtIndex:row];
	return cell;
	
}

// 각섹션에 헤더값을 설정할수 있는 메서드
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
	if([keys count] == 0)
		return nil;
	
	NSString *key = [keys objectAtIndex:section];
	// 섹션 헤더 제목을 나타날때 특정 값을 숨겨야 한다.
	// UITableViewIndexSearch 값이 들어온다면 nil 값을 리턴한다.
	if(key == UITableViewIndexSearch)
		return nil;
	
	return key;
}

// 테이블뷰 옆에 인덱스를 추가한다.
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
	// 사용자가 검색창을 사용하고 있다면 nil를 리턴한다.
	if(isSearching)
		return nil;
	
	return keys;
}

#pragma mark -
#pragma mark Table View Delegate Methods
// 사용자가 행을 선택할때 선택되기 전에 미리 호출된다.
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
	// 사용자가 행을 선택하면 검색창에 대한 키보드를 없앤다.
	[search resignFirstResponder];
	isSearching = NO;
	search.text = @"";
	[tableView reloadData];
	return indexPath;
}

#pragma mark -
#pragma mark Search Bar Delegate Methods
// 사용자가 리턴이나 키보도의 search 키를 탭하면 호출되는 메서드
// 검색창을 사용할때는 항상 구현해야 하는 메서드 이다.
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
	NSString *searchTerm = [searchBar text];
	[self handleSearchForTerm:searchTerm];
}

// 사용자가 검색창에 새로운 검색어를 입력할때마다 검색을 진행한다.
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm
{
	// 검색어가 없을경우 초기화를 하고 데이터를 다시 로드한다.
	if([searchTerm length] == 0)
	{
		[self resetSearch];
		[table reloadData];
		return;
	}
	
	[self handleSearchForTerm:searchTerm];
	
}

// 사용자가 cancel을 클릭했을 때 호출되는 메서드
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
	// 사용자가 검색이 마치면 isSearching을 NO로 설정하여 테이블뷰에 인덱스가 나오게 한다.
	isSearching = NO;
	// 검색창을 초기화 시키고 키보드를 삭제 시킨다.
	search.text = @"";
	[self resetSearch];
	[table reloadData];
	[searchBar resignFirstResponder];
}

// 검색이 시작되면 isSearching에 YES를 할당하도록 설정
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
	isSearching = YES;
	[table reloadData];
}

// 테이블 뷰 옆에 인덱스가 선택된다면 호출되는 메서드
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
	NSString *key = [keys objectAtIndex:index];
	// 만약 돋기기 라면
	if(key == UITableViewIndexSearch)
	{
		// 컨텐츠의 오프셋을 옆에야 한다. 그리고 NSNotFound를 리턴한다면 화면 상단으로 이동한다.
		[tableView setContentOffset:CGPointZero animated:NO];
		return NSNotFound;
	} else 
		return index;
}

@end
사용자 삽입 이미지
:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 7. 탭바와 피커

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:06

* 델리게이트와 데이터소스
- 피커는 여러 가지 작업을 델리게이에게 위임한다. 그 중에서도 가장 중요한 것은 각 행과 해당 행에 속한 컴포넌트 중에서 실제로 무엇을 그릴지를 결정하는 일이다. 피커는 델리게이트에게 문자열 또는 주어진 컴ㅁ포넌트의 특정 위치에 내용을 그리기 위한 뷰를 요청한다.

- 데이터소스는 피커에게 몇 개의 컴포턴트와 몇 개의 목록이 각각의 컴포넌트를 구성하고 있는지 알려준다. 데이터소스는 델리케이트와 유사한 방식으로 동작하지만, 메서드가 미리 정해진 시간에 호출된다는 점이 다르다.

* 탭바 아이콘
- 아이콘은 24 * 24 픽셀 크기여야 하고, .png 포맷으로 저장되어야 한다. 또 아이콘 파일은 투명 배경을 가지고 있어야 한다. 일반적으로 중간크기의 회색 아이콘이 탭바에서 가장 잘 어울린다.

* Tab Bar Controller
+ Tab Bar Item Attributes
-- Tab Bar Item
---- Badge = 이 옵션은 메일 애플리케이션에서 읽지 않은 메일이 몇 개인지를 나타낼 때 아이콘 위에 빨간색 숫자를 보여주는 것처럼 탭자에 빨간색 아이콘을 보여주기 위해 사용된다.
---- Identifier = 이것은 Favorites와 Search와 같은 일반적인 아이콘을 탭바에서 사용할 수 있도록 만들어 준다.

-- Bar Item
---- Title = 제목
---- Image = 사용할 이미지 파일

* 작업 순서
1. 탭을 클릭하고 속성 인스펙터를 사용하여 뷰 컨트롤러와 연결시킨 탭의 nib파일 이름을 입력
2. 아이텐터티 인스펙터를 열고 탭과 연결된 뷰 컨트롤러의 하위클래스를 변경
3. 탭바 아이템을 수정하기 위해 뷰 컨트롤러 대신에 탭을 한번 더 클릭하고 탭바 아이템의 텍스트와 아이콘을 설정

* Picker View
-- 날짜 피커와는 조금 다른점은 일반적인 피커는 자신이 어떤 데이터를 가지고 있는지를 알려주지 않는다. 데이터를 직접 보관하지 않기 때문이다. 대신 델리게이트와 데이터소스가 그 역할을 담당한다. 우리는 대신에 직접 피커에게 어떤 행이 선택되었는지 물어보고, PickerData 배열에 대응하는 데이터를 선택해야 한다.
-- viewDidLoad에서 우리는 피커에 데이터를 공급할 수 있도록, 여러 객체를 담을 수 있는 배열을 생성했다. 일반적으로 데이터는 프로젝트 폴더의 Resources폴더에 있는 프로퍼티 리스트 파일과 같은 소스들로부터 전달된다.

피커 구현에 필요한 새로운 메서드
* UIPickerViewDataSource에서 나온 공통으로 필요한 메서드

- (NSInteger) numberOfComponentsInPickerView:(UIPickerView *) pickerView
{
    return 1;
}

--- 피커는 하나 이상의 회전판 또는 컴포넌트를 가질 수 있기 때문에, 이 방법을 사용해서 피커가 얼마나 많은 컴포넌트를 표시해야 할지를 물어볼수 있다. UI 피커가 매개변수로 전달되었다는데 주목하자. 이 매개변수는 우리가 요청을 하는 피커 뷰를 가리키는 매개변수로서, 이를 통해 동일한 데이터소스가 여러 개의 피커를 관리 할 수 있게 된다.

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    return [pickerData count];
}

-- 피커에게 해당 컴포넌트에 몇개의 행이 있는지를 물어보는 메서드

*UIPickerViewDelegate에서 나온 공통으로 필요한 메서드
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger) row forComponent:(NSInteger)component
{
    return [pickerData objectAtIndex:row];
}

-- 이 메서드에서 피커는 특정 컴포넌트에서 특정 행에 대한 자료를 제공하도록 요청한다. 우리는 요청받은 컴포넌트와 행의 정보와 함께 피커에 대한 포인터를 제공하면 된다.

* 2개 이상의 회전판 피커 구현시 델리게이트와 데이터 소스 예제
// 몇개의 회전판을 구성할것인가?
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
 return 2;  // 몇개의 회전판을 구성할것인가 ... 2라면 2개의 회전판이 존재하는 피커
}
 
// 어떤 회전판에 어떤 데이터소스를 사용할것인가?
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
   if(component == 0)
      return [self.breadTypes count];
   else
      return [self.fillingTypes count];
}
// 선택된 회전판에서 데이터 알아내기
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
   if(component == 0)
      return [self.breadTypes objectAtIndex:row];
   else
      return [self.filiingTypes objectAtIndex:row];
}

* 번들(Bundle)이란?
- 특별한 구조를 따르는 컨텐츠를 가진 좀 특별한 폴더의 일종. 애플리케이션과 프레임워크도 모두 번들이고, 이들을 호출하면 우리의 애플리케이션을 나타내는 번들 객체를 리턴하게 된다. NSBundle의 주된 용도는 프로젝트에 추가한 Resources폴더에서 리소스를 획득하는 것이다. 애플리케이션을 빌드할 때 이 파일들이 여러분의 애플리케이션의 번들로 복사된다.

* Dictionary로 file에서 데이터 가져오기
-(void)viewDidLoad
{
  NSBundle *bundle = [NSBundle  mainBundle];     //애플리케이션의 메인 번들에서 참조를 획득
  NSString *plistPath = [bundle pathForResource:@"statedictionary" OfType:@"plist"];
  NSDictionary *dictionary = [[NSDictionary alloc] initWithContensOfFile:plistPath];
  self.stateZips = dictionary;
  [dictionary release];
 
  NSArray *components = [self.stateZips allKeys];    
  NSArray *sorted = [components sortedArrayUsingSelector:@selector(compare:)];
  self.states = sorted;
 
  NSString *selectState = [self.states objectAtIndex:0];
  NSArray *array = [stateZips objectForKey:selectedState];
  slef.zips = array;
}

* 피커의 선택이 변결될 때마다 메서드가 게속 호출된다.
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
  if(component == kStateComponent)
  {
    NSString *selectedState = [self.states objectAtIndex:row];
    NSArray *array = [statZips objectForKey:selectedState];
    self.zips = array;
    [picker selectRow:0 inComponent:kStateComponent animated:YES];
    [picker reloadComponent:kZipComponent];
  }
}
:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 6. 멀티뷰 애플리케이션

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:06

* 탭바 / 툴바
- 탭바 : 두 개 이상의 대상 중 단 하나만 고를 때 사용한다. 실제 애플리케이션에서 탭바는 두 개 이상의 컨텐츠 뷰를 선택하기 위해서 사용
- 툴바 / 버튼과 다른 특정 컨트롤을 담을수 있지만 상호배타적이지 않다. 일반적인 작업을 수행하는 버튼을 나타내기 위해 사용된다. 툴바 버튼은 다른 아이폰 커트롤과 다른다. 툴바 버튼은 오직 하나의 액션만 지원하고 다른 아이폰 컨트롤의 Touch up inside이벤트 같이 적절한 순간에만 그 액션을 유발한다.

* -(void)viewDidLoad
- nib이 로드 될때 호출되는 UIViewController의 메서드이다. viewDidLoad 메서드는 상위클래스에 정의되어 있고 뷰의 로딩이 끝났을 때 호출되기 때문에 뷰의 로딩 완료 시점을 알고 싶은 클래스에서 재정의(overriding)한다.

* -(void)didReceiveMemoryWarning
- 아이폰 OS는 메모가 부족하여 시스템 자체적으로 메모리를 삭제하려고 결정할 때 뷰 컨트롤러에 의해 상속되는 UIViewController의 메서드인 didReceiveMemoryWarning 메서드를 호출한다.

* 전환시 애니메이션 주기
1. [UIView beginAnimations:@"view Flip" context:nil];
- 첫번째 인자는 애니메이션 블록의 제목이다. 이 제목은 애니메이션을 재생시키는 코어 애니메이션 프레임워크를 사용할때 참조한다.
- 두번째 인자는 (viod *)타입으로 이 애니메이션 블록과 결합시키려는 객체의 포인터를 사용한다.

2. [UIView setAnimationDuration:1.25f]
- 애니메이션의 속도를 결정

3. [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]
- 애니메이션 커브를 설정. 일정한 속도롤 진행 하는 선형 커브

4. [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:YES]
- 트랜지션 설정 아이폰에서는 네개의 뷰 트랜지션을 선택 할수 있다.
+ UIViewAnimationTransitionFlipFromLeft
+ UIViewAnimationTransitionFlipFromRight
+ UIViewAnimationTransitionCurlUp
+ UIViewAnimationTransitionCurlDown

- 캐쉬(cache)옵션은 애니메이션을 시작할 때 뷰의 스냅샷을 직어서 매 단계마다 다시 그리지 않고 이미지를 사용해 뷰를 그리는 속도를 높여준다. 애니메이션을 진행하는 동안 뷰의 모습이 바뀌지 않으면 항상 캐시를 사용한다.

5. 트랜지션을 설정한 후 트랜지션할때 사용하는 뷰별로 각각 한번 씩 메서드를 호출한다.
- [self.blueViewController viewWillAppear:YES];
- [self.yellowViewController viewWillDisappear:YES];

6. 뷰 바꾸기가 끝나면 이 뷰들에 대해 두 번의 호출을 더 한다.
- [self.yellowViewController viewDidDisappear:YES];
- [self.blueViewController viewDidAppear:YES];

:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 5. 자동회전과 자동크기조절

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:05

* 세로보기 모드 (portrait - 길고 얇은) / 가로보기 모드 (landscape - 짧고 옆으로 넓은)

* 세로보기 모드에서 뷰는 너비 320 px / 높이 460 px (상태 표시줄이 없다면 높이는 480 px)

* 가로보기 모드에서 너비는 480 px / 높이 300 px (상태 표시줄이 없다면 높이는 320 px)

* 애플리케이션에서 회전 기능을 관리하는 3가지 기능

-- autoSize속성으로 회전 처리 하기
-- 회전할때 뷰 재구성하기
-- 뷰 전환하기 (세로보기모드 / 가로보기모드)

* autoSize속성으로 회전 처리 하기
-(BOOL)shouldAutorotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
return 값에 지원하고자 하는 방향만 return YES를 설정해주면된다.

-- 4가지 방식으로 응답
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpSideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight


* 회전할 때 뷰 재구성하기
- willAnimateRorotationToInterfaceOrientation:duration: 메서드를 사용하면된다. 이메서드는 회전이 일어나면, 마지막 애니메이션 동작 전에 자동으로 호출된다.

- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
 if(interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) 
 {
  button1.frame = CGRectMake(20, 20, 125, 125);
  button2.frame = CGRectMake(175, 20, 125, 125);
  button3.frame = CGRectMake(20, 168, 125, 125);
  button4.frame = CGRectMake(175, 168, 125, 125);
  button5.frame = CGRectMake(20, 315, 125, 125);
  button6.frame = CGRectMake(175, 315, 125, 125);
 } else {
  button1.frame = CGRectMake(20, 20, 125, 125);
  button2.frame = CGRectMake(20, 155, 125, 125);
  button3.frame = CGRectMake(177, 20, 125, 125);
  button4.frame = CGRectMake(177, 155, 125, 125);
  button5.frame = CGRectMake(328, 20, 125, 125);
  button6.frame = CGRectMake(328, 155, 125, 125);
 }
}

- 버튼과 같은 컨트롤들을 포함해서, 모든 뷰의 크기와 위치가 CGRect타입의 구조체인 frame이라는 속성에 명시되어 있다. CGRectMake는 CGRect를 쉽게 만들기 위해서 애플이 제공하는 함수로서, 너비와 높이와 함께 x,y위치를 명시하기만 하면된다.

* 뷰 전환하기
- 템플릿을 통해 제공되는 뷰는 크기를 조절 할수 없다.

---------------------------------------------------------------
P113 작업중  자꾸 아래와 같은 메세지가 console에 출력되었다.
당연히 Simulator에서도 작동하지 않았고.. 그래서 다음 이미지와 같이 해결하였다.


*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIViewController _loadViewFromNibNamed:bundle:] loaded the "SwapViewController" nib but the view outlet was not set.'

1. 새롭게 추가된 view중 portrait에 UIView portrait도 연결하였지만 기본 view도 같이 연결해 주었다.
:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 4. 사용자 인터페이스, 좀 더 재미있게 만들기

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:05

* 인터페이스 빌더 중 Library 항목에 Image View
-- 드로잉(Drawing) 체크 박스
--- Opaque :  불투명 항목 - 아이폰 OS에게 이 뷰 밑에 있는 것은 그릴 필요가 없고 컨트롤을 그릴때 몇 가지 방법으로 최적화를 해서 속도를 높일 수 있게 한다.
--- Hidden : 숨김 - 사용자는 그 컨트롤을 볼 수 없다.
--- Clear Context before Drawing - 실제 컨트롤을 그리기 전에 컨트롤러의 전체 영역을 모두 지운 뒤에 그릴게 될 것이다.
--- Clip Subviews : 하위뷰 자르기 - 체크하면 부모 영역 안의 하위뷰만 그린다. 체크 하지 않는 것이 기본값
--- Autoresize Subviews - 뷰의 크기가 변경되면 하위뷰의 크기도 변경되도록 설정한는 기능

-- Interaction : 상호작용
--- User Interaction Enabled : 사용자가 이객체와 무엇인가를 할 수 있는지를 설정한다. 체크 하지 않으면 그 컨트롤은 어떤 액션 메서드도 유발할 수 없기다.
--- Multiple Touch : 컨트롤이 멀티터치 이벤트를 받을 수 있는지를 결정한다.

* 인터페이스 빌더 중 Library 항목에서 Text Field
-- Text Field
--- Placeholder : 텍스트 필드 안에서 회색으로 표시할 텍스트를 설정
--- Clear When Editing Begins :  텍스트를 입력하려고 테스트 필드를 선택했을때 기존에 있던 값을 지우고 새롭게 입력을 시작
--- Text Input Traits
------ Capitalize : 입력되는 순서에 따른 대문자 표시여부
------ Auto-enable Return Key : 텍스트 필드에 한 글자 이상을 입력할 때까지 리턴 키는 비활성화 상태를 유지
------ Secure :텍스트 필드의 글자를 보여줄 것인지를 결정하는 옵션

* Done을 누르면 키보드 사라지게 만들기
- 아이폰의 키보드는 물리적인 키보드가 아닌 소프트웨어 기반의 키보드 이다.  사용자가 Done버튼을 누르면 "Did end on Exit" 이벤트가 생성된다. 그때 텍스트 필드가 제어를 포기하면 키보드가 사라진다.
- 퍼스트 리스폰더(first responder)의 개념은 '사용자가 현재 상호작용 중인 컨트롤'. 이 메서드에서 어떤 컨트롤이든 이 액션을 유발하면 퍼스트 리스폰더 상태를 포기하게 만든다. 텍스트 필드가 퍼스트 리스폰더임일 포기하면 키보드는 사라진다.
- .h 파일에 아래 추가
-(IBAction) textFieldDoneEditing: (id) sender

- .m 파일에 아래 추가
-(IBAction) textFieldDoneEditing: (id) sender
{
  [sender resignFirstResponder];
}

* 액션 메서드를 호출할 수 있는 모든 컨트롤 들은 UIControl의 하위클래스이다.

* UISegmentedControl의 selectedSegmentIndex 속성은 현재 선택된 세그먼트의 인덱스를 정수로 알려준다. 분할 컨트롤의 첫 번째 세크먼트인 Switches의 인덱스 값은 0이다.

* 액션 시트는 사용자에게 2개 이상의 항목에 대해 강제적인 선택을 받고자 할 때 사용된다.
-- 액션 시트는 화면의 아래 쪽에서 나타나며 사용자의 선택을 받기 위한 여러 버튼들로 구성된다. 사용자는 버튼 중 하나를 선택하기 전까지는 더 이상 진행할 수 없다.
-- 액션 시트를 사용하기 위해서는 컨트롤러 클래스가 델리케이트를 사용하면서 클래스가 UIActionSheetDelegate프로토콜을 따르게 해야 한다.

 UIActionSheet *actionSheet = [[UIActionSheet alloc]
          initWithTitle:@"Are you sure ?"
          delegate:self
          cancelButtonTitle:@"No Way!"
          destructiveButtonTitle:@"Yes, I'm Sure!"
          otherButtonTitles:nil];
 [actionSheet showInView: self.view];
 [actionSheet release];

+ 첫번째 인자(initWithTitle) : 액션 시트의 제목
+ 두번째 인자(delegate) : 액션 시트의 델리게이트는 시트에 있는 버튼을 눌려졌을때 알려주는 역할을 한다. 좀더 구체적으로 actionSheet:didDismissWithButtonIndex: 메소드가 호출
+ 세번째 인자(cancelButtonTitle) : 사용자가 더 이상 진행을 원치 않을 때 사용될 버튼에 제목을 설정.
+ 네번째 인자(destructiveButtonTitle) : 종료 버튼에 사용될 제목
+ [actionSheet showInView:self.view] = 액션 시트 생성 후에 화면 상에 액션 시트를 출력하는 코드


* 경고창은 화면의 중앙에 나타나며 파란 색의 둥그스름한 사각형 모양을 하고 있다.
--경고창도 액션 시트처럼 애플리케이션을 계속해서 진행하기 전에 사용자의 응답을 받는 용도로 사용된다.

-(void) actionSheet: (UIActionSheet *) actionSheet didDismissWithButtonIndex: (NSInteger) buttonIndex
{
 if(buttonIndex != [actionSheet cancelButtonIndex])
 {
  NSString *msg = nil;
  
  if(nameField.text.length > 0)
   msg = [[NSString alloc] initWithFormat:@"You can breathe easy, %@, everything went OK.",nameField.text];
  else 
   msg = @"You can breathe easy, everything went OK.";
  
  UIAlertView *alert = [[UIAlertView alloc] 
         initWithTitle:@"Something was done"
         message:msg
         delegate:self
         cancelButtonTitle:@"Phew!"
         otherButtonTitles:nil];
  [alert show];
  [alert release];
  [msg release];
 }
}
:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 3. 기본적인 상호작용 다루기

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:05

시작하세요~~~!! 아이폰3 프로그래밍 - Part3 [기본적인 상호작용 다루기]
+ 아웃렛
+ 액션

* 컨트롤러 클래스는 아웃렛 (Outlet)이라 불리는 특별한 인스턴스 변수를 사용하여 nib(xib) 파일 안의 객체를 참조 할수 있다. 아웃렛은 nib파일 내부의 객체를 가리키는 포인터라 생각하면 된다.
-- 예를 들어 인터페이스 빌더에서 텍스트 레이블 하나를 생성하였고 코드 내부에서 레이블의 텍스를 변경하려 한다고 한다면 아웃렛을 하나 선언하고 아웃렛이 레이블 객체를 가리키게 함으로써 코드상에서 아웃렛을 사용하여 레이블의 텍스트를 변경할 수 있다.

* nib 파일 안의 인터페이스 객체는 컨트롤러 클래스의 특정 메서드가 호출되도록 설정할수 있다. 이것은 액션(Action) 메서드
-- 예를 들어 사용자가 버튼을 눌렀을때 코드 안에 특정 액션 메서드가 호출될 것이라고 인터페이스 빌더에게 알려 줄수 있다.

* 아웃렛은 IBOutlet 키워드를 사용하여 선언하는 인스턴스 변수들이다.
@property (nonatomic, retain) IBOutlet UIButton *myButton;
-- IBOutlet의 목적은 인터페이스 빌더에게 이것은 nib 파일 안에 있는 객체와 연결되는 인스턴스 별수라고 알려주는 힌트를 제공하는 것뿐. 컴파일러와 관련해서는 아무 일도 하지 않는다. 인터페이스 빌더를 열었을때, 인터페이스 빌더는 프로젝트의 헤더 파일로부터 이 키워드를 검색하여 IBOutlet이 붙은 변수들을 nib 안의 객체와 연결되게끔 도와준다.

* 액션은 IBAction이라는 특별한 키워드로 선언. 이 키워드는 메서드가 액션 메서드라는 것을 인터페이스 빌더에게 알려주고, 컨트롤이 메서드를 호출하게뜸 도와준다.
-- '-(IBAction) doSomething: (id) sender; '
-- IBAction은 void 리턴 타입을 선언한 것과 같다고 할수 있다. 액션 메서드는 변수 값을 리턴하지 않는다는 말. 액션 메서드를 호출하는 버튼 컨트롤은 sender인자를 통해 자기 자신의 포인터를 넘겨준다. 이때 sender인자는 방금 눌러진 버튼의 포인터 값을 가지고 있다.
-- sender라는 이름의 변수를 하나를 인자로 취하지만 이 sender는 특별한 어떤 컨트롤을 가리키는 것은 아니다. 어떤 컨트롤이라도 이 sender를 통하여 전달 될 수 있다.

* @property / @synthesize
-- @property 선언은 구현부 파일 안의 또 @synthesize와 결합하여 컴파일러에게 게터와 세터 메서드를 컴파일 시에 생성하도록 알려준다.
-- @property (nonatomic, retain) IBOutlet UILabel *statusText;
-- 속성중 retain은 컴파일러에게 이 속성을 통해 할당한는 모든 객체에게 retain 메세지를 보내라고 알려준다. 이것은 프로터피로 선언된 인스턴스 변수가 여전히 사용되고 있는 중에는 메모리에서 삭제되는것을 막아준다. int, float혹은 다른 기본 데이터 타입을 선언할 때에는 어떠한 선택적인 속성들을 명시할 필요가 없다.
-- 속성중 nonatomic은 기본적으로 접근자와 변경자는 멀티스레드 프로그맹을 작성할 때 도움이 될 수 있는 추가적인 코드들과 함께 생성된다. 추가적인 오버헤드들은 비록 크지는 않지만 하나의 사용자 인터페이스 객체를 가리키는 포인터를 선언하는 경우에는 불필용한 것들이므로 오버헤드를 줄이기 위해 nonatomic를 선언

* IBAction sender 인자에서 부터 할당된 값 참조 - 클릭된 버튼의 제목을 얻는 코드
-- NSString *title = [sender titleForState:UIControlStateNormal];

** 자동으로 릴리지 되는 객체
-- NSString *newText = [NSString stringWithFormat:@"%@ button pressed.", title];
-- NSString 클래스의 이름과 같은 이름의 메서드를 명시하는 것은 컨비니언스 메서드나 팩토리 메서드를 호출하는 것과 같으며 이 메서드들은 자동으로 릴리즈되는 객체를 반환한다.
-- "만약 여러분이 메모리를 할당하거나 리테인 하지 않는다면 그 메모리를 릴리즈해서는 않된다."

* 델리게이트
-- 델리게이트라는 것은 다른 객체를 대신해서 책임지고 어떤한 일을 해주는 클래스를 말한다.
-- 모든 아이폰 애플리케이션은 델리케이트 메서드와 단 하나의 UIApplication 인스턴스를 가진다. UIApplication 인스턴스는 애플리케이션의 메인 루프를 책임지며 입력된 데이터를 적합한 컨트롤러 클래스에게 전달해즈는 것과 같은 애플리케이션 수준의 기능을 처리.
-- 예를 들어, 프로그램이 종료하기 직전에 동작시키려는 코드가 있다면, 애플리케이션 델리케이트 안의 ApplicationWillTerminate메서드를 구현하고 그 안에 종료 코드를 작성해야 한다.
:

[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 2. 티키신 달래기

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 7. 12. 22:05

아직 해당 책이 내가 구매한것이 아니라서..ㅋㅋ
난 원래 낙서를 많이 하면서 책을 보는데.. 그러지 못하니 여기에다가 중요한것만 대략적으로 정리..

Part1은 대략적인 개념이라 패스.. 정리 할게 없다... 바로 Part2로 고고

Part2는 모든 프로그램에 기본.. "Hello World"출력하기...
- 아이폰 기본 아이콘은 57 * 57 png파일이 기본이다.
- 아이콘 파일을 Xcode에서 왼쪽 Group & File중 Resources에 아래에 복사를 해 놓고.. 이것이 기본 아이콘 파일 이라고 설정해 줘야 한다.
- Resources 디렉토리에 있는 .plist파일을 열어서 그중 Icon File TextInput에 해당 파일명을 지정해 주면된다.

:

[iPhone] 2번째 교재.. 시작하세요! 아이폰3 프로그래밍 - Beginning Iphone 3 Development

iPhone 2010. 7. 12. 22:04

인사이트에 프로그래밍 오브젝티브(Objective) C 2.0교재를 한번 보고 나서 Objective-C에 대한
대략적인 개념은 찾았다.. (^^)

그런데 개념 책만 보다 보니 너무 너무 재미가 없다..ㅋㅋ

그래서 회사에 있는 책중 한가지를 골라서 보기로 시작했다..ㅎㅎ

그래도 눈에 뭐 보이는게 있어야지 신나서 공부를 하지.. 맨날 comment창만 보면 재미가 없다.

회사에 여러가지에 책이 있었지만 그래도 나는 아래에 책을 선택하였다.

시작하세요! 아이폰 3 프로그래밍 
위키북스에 시작하세요! 아이폰 3 프로그래밍 - Beginning iPhone 3 Development

아직 구매를 하지는 않았지만.. 조금 더 읽어보고 내 맘에 들면 바로 구매해야지..
책값은 아깝지 않다..ㅋㅋ (나의 생각) 나중에 신혼집에 책상사면 전시용으로 사용될지 모르지만.ㅠ.ㅠ

다시 열공에 세계로..

'iPhone' 카테고리의 다른 글

[iPhone]Objective-C 시작  (0) 2010.07.12
:

[iPhone]Objective-C 시작

iPhone 2010. 7. 12. 22:04

그동안 정신 없던 일로 책장에 넣어 두웠던 Objective-C를 시작...ㅋㅋ (2010/06/01)

처음에 아이폰이 나오면서 예판으로 구매 하였던
Objective-C 오브젝티브 C 
한빛 미디어의 Objective-C : 맥과 아이폰 애플리케이션 프로그래밍 이라는 책
'오키하라 타케시 저 / 신상재 역 '

나 자신을 너무 과대 평가 했나 보다.. 처음 부터 동적바인딩 / 메모리 관리등등이 나오는데... 솔직히 무슨 말인지 하나도 모르겠다.. ㅠㅠ
책을 읽으면 읽을수록 점점 암흑에 세계로.....허거덩

그래서 과감히 책을 바꿔 버렸다.. 단계를 완전히 낮춰서리...ㅋㅋ

다음으로 구입한 책이... (책 구입 하는것은 아깝지 않다고 생각 하는 사람이라.. 그래서 보지도 않는 책이 책장에 쌓여있다는)

프로그래밍 오브젝티브 (Objective) C 2.0 
인사이트에 프로그매밍 오브젝티브 (Objective) C 2.0 이다.
'스티븐 코찬 저 / 박세현 역'

이 책은 C를 알고 있다고 전제 하지 않고 시작하기 때문에 조금은 쉽다는 느낌이 들지만 그래도 책을 읽으면서
암흑에 세계로 빠지지는 않는거 같다..ㅎㅎㅎ

이제 부터 다시 열공에 세계로 고고씽~~~~
: