[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 16. 아이폰 카메라와 포토 라이브러리

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 10. 4. 20:27

* 아이폰 카메라와 포토 라이브러리
- 아이폰은 샌드박스 메커니즘으로 설계되었기 때문에 애플리케이션에서 사진이나 샌드박스 외부에 저장된 데이터에 접근하는 것은 불가능하다. 하지만 이미지 피커(Image Picker)를 사용하면 카메라와 이미지 라이브러리에 접근할 수 있다.
- 피커에 설정된 이미지 소스 안의 이미지들을 선택하여 사용할 수 있게 해주는 객체이다. 일반적으로 이미지 피커는 이미지 리스트를 소스로 사용하지만, 카메라를 소스로 명시하면 카메라로 촬영한 이미지를 전달받는것도 가능한다.

* 이미지 피커와 UIImagePickerController 사용하기
- 이미지 피커의 인터페이스는 UIImagePickerController라는 모달 컨트롤러 클래스를 사용한다. 따라서 UIImagePickerController 클래스의 인스턴스를 생성하고 인스턴스에 델리게이트를 지정해야 한다. 그리고 이미지 소스 타입을 명시한 다음 모달 형태로 지정한다. 그러고 나면 이미지 피커는 아이폰에서 저장한 사진들 중 하나를 선택할 수 있게 모달 창을 띄우거나 카메라를 사용하여 새로운 사진을 촬영할 것이다.
- 사용자가 취소 버튼을 누르지 않으면 카메라로 촬영하거나 라이브러리에서 선택한 이미지는 델리게이트 메서드로 전달될 것이다.
- 이미지가 선택되거나 취소되는 것과는 별개로 델리게이트는 사용자가 애플리케이션의 뷰 화면으로 돌아갈 수 있도록 이미지 피커를 종료시켜야 한다.
- UIImagePickerController를 생성하는 것은 매우 간단하다. 단지 클래스의 인스턴스를 메모리에 할당하고 초기화하면 된다. 하지만 사용자가 카메라로 산진을 촬영하기 전에 프로그램이 실행되고 있는 장치가 카메라를 지원하는지 확인해야 한다. UIImagePickerController 클래스의 메서드를 사용하여 확인할 수 있다.
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {

- 이 예제는 UIImagePickerControllerSourceTypePhotoLibrary 인자를 사용하여 사용자가 포토 라이브러리의 사진을 선택할 수 있도록 만들었다. 포토 라이브러리가 사용 가능한 상태이면 isSourceTypeAvailable:메서드는 YES로 반환할 것이다.
  • UIImagePickerControllerSourceTypeCamera를 사용하면 내장된 카메라를 사용하여 사진을 촬영할 수 있다.
    촬영된 사진은 델리게이트에게 전달될 것이다.
  • UIImagePickerControllerSourceTypeSavedPhotosAlbum을 사용하면 카메라 룰의 사진들을 선택할 수 있다.

- 카메라가 지원되는 장치에서 애플리케이션이 실행중인지 확인하고 나면, 이미지 피커를 실행하는 것은 어렵지 않다.

UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentModalViewController:pircker animated:YES];
[picker release];

- UIImagePickerController의 인스턴스를 생성하고 설정한 다음 화면에 이미지 피커를 보여주기 위해 presentModalViewController:animated:메서드를 사용하였다.
- presentModalViewController:animated: 메서드는 현재 화면에 그려진 뷰에 대한 뷰 컨트롤러 객체의 presentModalViewController:animated: 메서드를 호출한다면, 뷰 컨트롤러는 모달 창의 형태로 사용자에게 보여질 것이다.

* 이미지 피커 컨트롤러의 델리게이트 구현하기

- 사용자가 이미지 피커 창을 닫았을 때 특정 객체로 노티피케이션이 전달되게 하려면 객체는 UIImagePickerControllerDelegate의 프로토콜을 따라야 한다.
- UIImagePickerControllerDelegate 프로토콜은 imagePickerController:didFinishPickingImage:editingInfo:와 imagePickerControllerDidCancel:메서드를 정의하고 있다.
- imagePickerController:didFinishPickingImage:editingInfo:메서드는 사용자가 사진 촬영에 성공했거나 포토 라이브러리로부터 사진 하나를 선택했을 때 호출된다. 첫번째 인자는 앞에서 생성한 UIImagePickerController의 포인터를 넘겨주는데 사용된다. 두번째 인자는 사용자가 실제 선택한 사진이 저장된 UIImage의 인스턴스이다. 마지막 인자는 이미지 편집기능이 설정되었을 때만 유효한 NSDictionary의 인스턴스이다. 이 딕셔너리의 UIImagePickerControllerOriginalImage키에는 편집되지 않은 원본 이미지가 들어 있다.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo
{
  UIImage *selectedImage = image;
  UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
  [picker dismissModalViewControllerAnimated:YES];
}

- editingInfo 딕셔너리의 UIImagePickerControllerCropRect 키에 저장된 NSValue 객체를 사용하여 이미지를 편집할 때 선택한 영역을 가져올수 있다.
- 다음과 같은 방법으로 UIImagePickerControllerCropRect키를 사용하여 NSValue를 CGRect으로 변환할수 있다.
NSValue *cropRect = [editingInfo objectForKey:UIImagePickerControllerCropRect];
CGRect theRect = [cropRect CGRectValue];

- NSValue를 CGRect로 변환하였다면 theRect에는 편집할 때 선택한 영역의 정보가 저장되어 있을 것이다.
- 델리게이트 메서드인 imagePickerControllerDidCancel:은 사진 촬영이나 사진 선택 창에서 취소 버튼을 눌렀을 때 호출된다. 이미지 피커가 imagePickerControllerDidCancel: 델리게이트 메서드를 호출하면 사용자가 이미지를 선택하지 않고 이미지 피커 창을 닫은 것이다.
- UIImagePickerControllerDelegate 프로토콜의 두 메서드는 모두 선택적 메서드이지만 실제로는 선택 사항이 아니라 반드시 구현해야만 하는 메서드이다. 왜냐하면 이미지 피커와 같은 모달 뷰는 사용이 끝나면 반드시 창을 닫아야 하기 때문에 사용자가 이미지 피커의 취소 번튼을 눌렀을 때 특별히 처리할 일이 없다고 하더라도 피커를 종료하는 코드는 반드시 실행되어야 한다. 따라서 프로그램이 정상 동작하려면 imagePickerControllerDidCancel:메서드는 적어도 다음과 같은 내용을 포함해야 한다.
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
  [picker dismissModalViewControllerAnimated:YES];
}

* 카메라와 라이브러리 길거리 테스트
- 카메라로 촬영한 사진이나 포토 라이브러리에서 선택한 사진을 이미지 뷰에서 보여주는 기능을 가진 애플리케이션을 만들것이다.
- 만일 카메라가 없는 장치를 사용한다면 "Take Picture"버튼과 "Pick from library"버튼은 숨겨질 것이고 오로지 포토 라이브러리로부터 사진을 선택하는 버튼만을 보여줄것이다.
- Xcode에서 뷰 기반의 애플리케이션 템플릿을 사용하여 새 프로젝트를 하나 생성하고, 프로젝트 이름을 Camera라고 입력한다.
- Camera애플리케이션은 3개의 아웃렛을 필요로 한다. 하나는 이미지 피커에서 선택한 이미지를 화면에 나타낼 때 사용할 이미지 뷰의 포인터이다. 그리고 나머지 2개는 Take New Picture 버튼과 Select from Camera Roll 버튼을 가리키기 위한 포인터이다. 따라서 우리가 사용할 장치가 커메라를 지원 하지 않는다면 아웃렛을 사용하여 이 버튼들을 숨길 수 있다. 또한 2개의 액션 메서드가 필요하다. 하나는 Take New Picture버튼과 Select from Camera Roll 버튼을 위해 사용될 것이다. 또 하나는 사용자가 포토라이브러리로 사진을 선택하는 데 사용될 것이다.

#### CameraViewController.h ####
#import 

// UIImagePickerController는 UINavigationController의 하위 클래스 이므로 두 프로토콜 모두를 따라야 한다.
// UINavigationControllerDelegate의 메서드들은 모두 선택적 메서드이며 애플리케이션이 이미지 피커를
// 사용할 때는 이 메서들이 필요하지 않다. 하지만 컴파일 할때 경고 메시지가 출력되지 않게 하려면 메서드가
// 필요하지 않아도 UINavigationControllerDelegate 프로토콜을 따라야만 한다.
@interface CameraViewController : UIViewController 
{
	// 이미지 피커에서 선택한 이미지를 화면에 나타낼 때 사용할 이미지 뷰의 포인터
	UIImageView *imageView;
	// Take New Picture 버튼을 가리키는 포인터
	UIButton *takePictureButton;
	// Select from Camera Roll 버튼을 가리키는 포인터
	UIButton *selectFromCameraRollButton;
}

@property (nonatomic, retain) IBOutlet UIImageView *imageView;
@property (nonatomic, retain) IBOutlet UIButton *takePictureButton;
@property (nonatomic, retain) IBOutlet UIButton *selectFromCameraRollButton;
-(IBAction)getCameraPicture:(id)sender;
-(IBAction)selectExistingPicture;
@end

* 인터페이스 설계하기
- 라이브러리 창에서 Round Rect Buttons 3개를 드래그하여 View윈도우 위에 놓는다. 그리고 난뒤 버튼을 세로로 하나씩 배치한다.
- 맨 위의 버튼을 더블클릭하고, 'Take New Picture'라고 입력한다.
- 중간의 버튼을 더블클릭하고 'Pick from Camera Roll'이라고 입력한다.
- 하단의 버튼을 더블클릭하고 'Pick from Library'라고 입력한다.
- 라이브러리로부터 이미지 뷰(Image View)를 드래그하여 버튼들의 위쪽에 배치한다. 뷰가 버튼 위의 전체 영역을 감싸도록 늘린다.
- File's Owner아이콘에서 이미지 뷰로 컨트롤-드래그하여 imageView아웃렛을 선택한다. 
- File's Owner아이콘에서 Take New Picture버튼으로 드래그하고 takePictureButton 아웃렛을 선택한다.
- File's Owner아이콘에서 Pick from Camera Roll 버튼으로 컨트롤-드래그 하고, selectFromCameraRollButton 아웃렛을 선택한다.
- Take New Picture버튼을 선택하고 Func + 2를 눌러서 커넥션 인스펙터 창을 띄운다. Touch Up Inside 이벤트로부터 File's Owner로 드래그 하고, getCameraPicture:액션을 선택한다.
- Pick from Camera Roll 버튼을 클릭하고 커넥션 인스펙터의 Touch Up Inside 이벤트로부터 File's Owner로 드래그 한 뒤에 getCameraPicture: 액션을 선택한다.
- Pick from Library 버튼을 선택하고 인스펙터의 Touch Up Inside 이벤트로부터 File's Owner로 드래그하고 selectExistingPicture 액션을 선택한다.

* 카메라 뷰 컨트롤러 구현하기
#### CameraViewController.m ####
#import "CameraViewController.h"

@implementation CameraViewController
@synthesize imageView;
@synthesize takePictureButton;
@synthesize selectFromCameraRollButton;

-(void)viewDidLoad
{
	// 애플리케이션이 실행되는 장치가 카메라를 지원하는지 확인한다.
	if(![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
	{
		// 만약 카메라가 없는 장치에서 실행되고 있다면 카메라를 사용하는 기능과 관련된
		// 2개의 버튼을 숨기도록 만들었다.
		takePictureButton.hidden = YES;
		selectFromCameraRollButton.hidden = YES;
	}
}

- (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 {
	self.imageView = nil;
	self.takePictureButton = nil;
	self.selectFromCameraRollButton = nil;
	[super viewDidUnload];
}

- (void)dealloc {
	[imageView release];
	[takePictureButton release];
	[selectFromCameraRollButton release];
    [super dealloc];
}

#pragma mark -
-(IBAction)getCameraPicture:(id)sender
{
	// UIImagePickerController 인스턴스를 할당하고 초기화
	UIImagePickerController *picker = [[UIImagePickerController alloc] init];
	// 이미지 피커의 델리게이트에 self를 할당한다.
	picker.delegate = self;
	// 사용자가 이미지를 촬영한 뒤에 편집할 수 있도록 allowsImageEditing에 YES를 할당한다.
	picker.allowsEditing = YES;
	// 사용자가 어떤 버튼을 눌렀는지 확인하여 sourceType을 설정한다.
	// 만일 사용자가 Take New Picture을 눌렀다면 피커가 카메라를 사용할수 있도록 UIImagePickerControllerSourceTypeCamera
	// 사용자가 Pick from camera Roll 버튼을 눌렀다면 UIImagePickerControllerSourceTypeSavedPhotosAlbum
	picker.sourceType = (sender == takePictureButton) ? 
							UIImagePickerControllerSourceTypeCamera :
							UIImagePickerControllerSourceTypeSavedPhotosAlbum;
	
	// 이미지 피커를 모달 윈도우 형태로 실행하고 인스턴스를 해제 한다.
	[self presentModalViewController:picker animated:YES];
	[picker release];
}

-(IBAction)selectExistingPicture
{
	// 만일 사용자가 포토 라이브러리에서 사진을 선택할 수 있는 기능을 제공한다면 이미지 피커를 생성한다.
	if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
	{
		UIImagePickerController *picker = [[UIImagePickerController alloc] init];
		picker.delegate = self;
		picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
		[self presentModalViewController:picker animated:YES];
		[picker release];		
	} else {
		// 사용자가 포토 라이브러리를 사용할수 없다면 경고 창을 띄어준다.
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error accessing photo library"
														message:@"Devie does not support a photo library"
													   delegate:nil
											 cancelButtonTitle:@"Drat!"
											  otherButtonTitles:nil];
		[alert show];
		[alert release];
	}
}

#pragma mark -
// 델리게이트 메서드
// imagePickerController:didFinishPickingImage:editingInfo: 메서드는 사용자가 이미지 피커의 사용을 마쳤을 때 호출된다.
// 또한, sourceType에 상관없이 사용자가 사진을 선택했을 때도 호출된다.
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo
{
	// imageView에 그려질 이미지가 할당될 것이다.
	imageView.image = image;
	// 사용자가 애플리케이션의 뷰 화면으로 돌아갈수 있도록 이미지 피커를 종료시킨다.
	[picker dismissModalViewControllerAnimated:YES];
}

// 사용자가 취소버튼을 누르면 그 밖의 다른 어떤 동작도 필요하지 않지만, 이미지 피커를 종료시키는 작업을 해야한다.
// 그렇게 되지 않으면 뷰를 가린 채로 이미지 피커가 여전히 화면상에 나타나 있을것이다.
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
	[picker dismissModalViewControllerAnimated:YES];
}

@end

: