[iPhone] - 시작하세요! 아이폰 3 프로그래밍 - Part 12. 쿼츠와 OpenGL을 이용한 그리기

iPhone/[위키북스]시작하세요! 아이폰3 프로그래밍 2010. 8. 23. 16:04

* 쿼츠와 OpenGL을 이용한 그리기
- 다른 라이브러리를 사용하지 않고, UIKit 프레임워크에서 제공하는 라이브러리들만으로도 다양한 애플리케이션 인터페이스를 만들 수 있다. 그러나 일부 애플케이션들은 또 다른 라이브러리를 사용하지 않으면 구현할 수 없는것들이 있다. 예를 들면 드로잉(Drawing) 애플리케이션을 만들 경우다. 다행히 드로잉을 구현할 때 도움을 받을 수 있는 두 개의 독립적인 라이브러리가 있다.
- 하나는 쿼츠(Quartz)2D라는 코어 그래픽스 프레임워크에 속하는 라이브러리이다.
- 다른 하나는 OpenGL ES이라는 크로스 플랫폼 그래픽스 라이브러리 이다. OpenGL ES는 OpenGL이라 불리는 또 다른 크로스 플랫폼 라이브러리의 경량화 버전이다. 'ES'라는 글자는 Embedded System에서 따온 것이다.

* 그래픽 세계의 두 뷰
- 쿼츠 라이브러리는 메모리상의 뷰나 이미지에 직접 그릴 수 있도록 설계된 함수, 자료형, 객체로 구성되어 있다.
- 쿼츠 라이브러리는 뷰나 이미지를 가상 캔버스 위에 그리며, 페인터 모델이라는 드로잉 방식을 사용한다. 페인터 모델은 물감을 가지고 종이에 그림을 그리는 것과 같은 과정으로 드로잉 명령을 가장 캔버스에 적용시키는 방식이다. 드로잉이 발생하면 새로 그려질 뷰나 이미지는 이전 드로잉 때 그려진 뷰나 이미지 위에 겹쳐져 그려지게 된다.
- OpenGL ES는 상태 기계의 구조로 되어 있다. OpenGL ES는 사용자가 어떠한 행동을 하였을 때 뷰나 윈도우 혹은 이미지에 직접적으로 변화를 주는 것이 아니라 3차원의 가상 세계를 통해서 사용자의 행동을 반영한다. 이 가상 세계에 새로운 객체를 추가하면, OpenGL은 가상 세계 안에 있는 모든 객체의 정보를 기록한다. OpenGL은 가상 캔버스 대신에 가상 세계를 보여주는 가상 윈도우를 제공한다. 추가된 객체의 정보를 기록하고 나면 OpenGL은 초기화할 때 설정한 값과 객체들의 상대적인 위치 값을 기반으로 가상 윈도우에 가상 세계를 그린다.
- 쿼츠는 OpenGL에 비해 사용하기 쉬우면, 다양한 선, 도형, 이미지를 그리는 함수를 제공한다. 쿼츠 2D라이브러리가 사용하기 쉬워도 2차원 드로잉만 지원한다는 한계가 있다. 그리고 드로잉을 할 때마다 가속된다는 보장은 없다.
- OpenGL은 쿼츠에 비해 상당히 복잡하고 더 어려운 개념으로 구성되었지만, 2차원과 3차원 드로잉이 모두 가능하며 하드웨어 가속의 이점을 충분히 사용할 수 있도록 설계되었다.

* 드로잉 애플리케이션
- 예제 애플리케이션의 모습을 보면 위쪽과 아래쪽에 세그먼트 컨트롤을 가진 바로 구성되어 있다. 위쪽 바에 배치된 컨트롤들은 그리기를 할때 사용할 색을 변경하는 데 사용한다. 아래쪽 바에 있는 컨트롤들은 드로잉을 할 도형을 선택할 때 사용한다. 색과 도형을 선택하고 뷰위에서 손가락으로 드래깅하면, 선택한 도형은 선택한 색으로 그려질것이다.

* 드로잉을 위한 쿼츠 사용법
- 드로잉을 위해 쿼츠 라이브러리를 사용할 때는 일반적으로 드로잉 코드를 뷰 클래스 안에 추가한다. UIView의 하위클래스를 하나 만들고 UIView클래스의 drawRect:메서드에 쿼츠 라이브러리를 호출하는 코드를 추가하였다고 가정하자. drawRect:메서드는 UIView클래스에 정의된 멤버 함수이며, 뷰가 스스로를 화면에 그릴 때마다 호출된다. 만일 drawRect:함수 안에 쿼츠 라이브러리를 사용하는 코드를 넣었다면 쿼츠 라이브러리 함수를 호출한 후에 UIView의 뷰를 그릴것이다.

* 쿼츠 2D 그래픽스 컨텍스트
- 쿼츠 2D는 코어 그래픽스 프레임워크에서 제공하며, 쿼츠 2D를 사용할 때 드로잉은 보통 컨텍스트(Context)라고 일컫는 그래픽스 컨텍스트 안에서 일어난다. 모든 뷰는 컨텍스트와 관련이 있다. 뷰 클래스 안에서 드로잉을 할 때는 현재 컨텍스트의 값을 가져온 뒤에 컨텍스트에 드로잉을 한다. 그리고이 컨텍스트 변수는 쿼츠 라이브러리들을 호출할 때 호출할 함수의 인자로 사용하며, 뷰에 드로잉을 할때 컨텍스트가 렌더링 처리를 할 수 있게 해준다.
- 아래는 현재 컨텍스트를 가져온다.
CGContextRef context = UIGraphicsGetCurrentContext();

- 그래픽스 컨텍스트 변수를 정의하였다면, 코어 그래픽스 함수에 컨텍스트 변수를 인자로 넘겨서 드로잉을 할수 있다. 아래의 순서대로 코드가 실행되면 컨텍스트에는 2픽셀의 넓은 선이 그려진다.
// 2픽셀의 선을 생성하라고 정의한다.
CGContextSetLineWidth(context, 2.0);
// 테두리를 빨간색으로 칠하도록 함수를 호출한다.
// 코어 그래픽스 라이브러리로 드로잉을 할 때 고려해야 할 색은 2가지가 있다.
// 그것은 테두리 색과 채우기 색이다.
// 테두리 색은 선을 그릴 때와 도형의 외곽을 그릴 때 사용된다.
// 채우기 색은 도형의 내부를 채울때 사용된다.
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// CGContextMoveToPoint()를 호출하면 아무것도 그리지 않고 새로운 위치로 보이지 않는 펜을 이동한다. 
//이 함수를 사용하여 (100,100)에서 그리기를 시작하라고 지정한다.
CGContextMoveToPoint(context, 100.0f, 100.0f);
// CGContextAddLineToPoint()는 실제로 현재 펜의 위치에서 인자에 지정된 위치로 선을 그린다.
// 아직은 실제로 보이는 선을 그리는것은 아니다. 보이지 않는 선을 그린다.
CGContextAddLineToPoint(context, 200.0f, 200.0f);
// CGContextStrokePath()함수를 사용하여 쿼츠 라이브러리에게 선을 실제 보이게 긋도록 알려준다.
CGContextStrokePath(context);

* 좌표계
 - CGContextMoveToPoint()와 CGContextAddLineToPoint()함수에 한쌍의 부동 소수점을 인자로 넘겨 준다. 이 부동소수점들은 코어 그래픽스 좌표계상의 위치를 나타낸다. 좌표계상의 위치는 우리가 흔히 표현하는 (x,y)처럼 X좌표와 Y좌표로 사용된다. (0,0)은 컨텍스트의 왼쪽 상단 모서리의 좌표이다. 화면상의 커서를 아래로 내리면 y값이 증가하며, 커서를 오른쪽으로 움직이면 x값이 증가한다.
- 좌표계는 쿼츠를 사용하여 드로잉할 때 나타나는 직관적이지 않은 특성 중 하나이다. 왜냐하면 쿼츠의 좌표계는 많은 그래픽스 라이브러리가 사용하는 것이나 기하학에서 일반적으로 표현하는 것과 다른게 뒤집혀 있기 때문이다. 예를 들면 OpenGL ES에서 (0,0)은 왼쪽 하단 모서리이고, Y좌표가 증가하면 아래 그림 오른쪽과 같이 컨텍스트나 뷰의 상단으로 이동한다. OpenGL 라이브러리를 사용할 때는 뷰 좌표계 기준에서 OpenGL의 좌표계 기준으로 좌표 값을 변환해야 한다.

- 좌표계에서 하나의 점을 만들기 위해 일부 쿼츠 라이브러리 함수들은 부동 소수점 한쌍을 인자로 취한다.
- 반면에 다른 쿼츠 라이브러리 함수들은 CGPoint 구조체 값을 인자로 취한다. CGPoint 구조체는 부동 소수점 값인 x와 y를 멤버 변수로 가지고 있다.
- 쿼츠 라이브러리는 뷰나 그 외의 객체들에 대한 크기를 표현하기 위해 CGSize 구조체를 사용하며, CGSize구조체는 width와 height 두개의 부동소수점 값을 멤버 변수로 가지고 있다. 또한 쿼츠 라이브러리는 좌표계에서 직사각형을 정의하는데 자주 사용되는 CGRect라는 이름의 자료형을 사용한다.
- CGRect는 2개의 멤버 변수를 가지고 있는데, 하나는 직사각형의 왼쪽 상단의 좌표를 나타내는 origin이라는 이름의 CGPoint 구조체 변수이고, 다른 하나는 직사각형의 width와 height를 나타내는 size라는 이름의 CGSize 구조체 변수이다.

* 색의 정의하기
- UIColor는 UIKit이 제공하는 오브젝티브C클래스 중 하나이다.
- 코어 그래픽스 라이브러리 함수를 사용할 때 UIColor 객체를 인자로 직접 사용하는 일은 없다. 하지만 UIColor는 CGColor를 포함하고 있으므로 UIColor 인스턴스의 프로퍼티인 CGColor를 통해 CGColor 페러런스 값을 가질 수 있다.
- redColor라는 이름의 컨비니언스 메서드(오토릴리즈 풀을 사용할 수 있는 메서드)를 사용하여 UIColor 인스턴스를 하나 생성했다. 그러고 나서 CGColor 프로퍼티 값을 받아서 함수의 인자로 넘겨 주었다.
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);

* 아이폰의 디스플레이를 위한 색상 이론 맛보기
- 현대 컴퓨터 그래픽스에서는 색을 표현하는 일반적인 방법으로 빨강(Red), 녹색(Green), 파랑(Blue) 그리고 알파(Alpha) 4가지 요소를 사용한다. 쿼츠 2D에서 이변수들은 CGFloat 타입이고 0.0과 1.0사이의 값을 가진다.

* 더 많은 색이 눈과 만나다
- 알파는 한 색이 다른 색 위에 그려질 때 최종적으로 화면에 나타낼 색을 결정하는 데 사용된다. 알파 값이 1.0이면 칠하는 색이 100퍼센트 불투명한 상태이고, 이전에 칠해진 색은 새로 칠하는 색에 의해 모두 가려진다. 알파가 사용될 때 색 모델은 가끔씩 RGBA 색상 모델을 참조한다.
- UIColor 객체에서 CGColor 값을 가져와 인자로 넘겨주기만 하면, 코어 그래픽스 라이브러리가 필요한 변환 작업을 처리 해준다.
-  OpenGL ES 라이브러리를 사용하여 작업을 할 때는 OpenGL ES가 쿼츠 라이브러리와 다른 색상 모델들을 지원하는 점을 명심하자. 왜냐하면 OpenGL ES 라이브러리는 RGBA 색상 모델에서 정의하는 색을 필요로 하기 때문이다.
- UIColor 클래스는 특정한 하나의 색으로 초기화 된 UIColor 색체를 반환하는 수많은 컨비언스 메서들을 가지고 있다. 컨비니언스 메서드는 RGBA 색상 모델을 사용하고, 컨비니언스 메서드를 통해 UIColor 인스턴스를 생성할 수 있다는 것이다.
- 만일 색상의 이름을 사용하는 컨비니언스 메서드를 사용하지 않고 좀 더 정교하게 색을 만들고 싶다면 아래와 같이 4개의 인자를 사용하여 하나의 색을 만들 수 있다.
return [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f];

* 컨텍스트에 이미지 그리기
- 쿼츠 2D는 컨텍스트에 직접 이미지를 그린다. 여기서는 코어 그래픽스 데이터 구조체인 CGImage를 사용하지 않고 대안으로 오브젝티브 C 클래스인 UIImage를 사용할 것이다.
- UIImage클래스는 이미지를 그려서 현재 컨텍스트에 나타내는 메서드들을 가지고 있다. 이미지가 컨텍스트의 어느 곳에 그려질것인지 지정하는 작업이 필요로 하므로, 이미지가 그려질 위치를 나타내기 위해 CGPointer를 사용하여 이미지의 왼쪽 상단의 꼭지점 좌표를 지정하거나 CGRect를 사용하여 이미지가 그려질 직사각형 틀을 지정할 수 있다.
- 사각형 틀이 이미지 크기보다 작으면, 이미지의 크기가 직사각형 틀에 맞게 바뀐다.
CGPoint drawPoint = CGPointMake(100.0f, 100.0f);
[image drawAtPoint:drawPoint];

* 도형 그리기 : 다각형,선,커브
- 직사각형이나 다각형을 그리기 위해 도형의 각을 계산하거나 선을 그리거나 수학적인 계산을 할 필요는 전혀 없다. 단지 쿼츠  라이브러리 함수만 호출하면 도형을 그리는것이 가능하다. 예를 들어 타원을 그리기 위해 타원의 크기를 표현하는 직사각형을 하나 정의하고 인자로 넘기면 코어 그래픽스 라이브러리가 직사각형 틀에 꼭 맞는 타원을 그릴 것이다.
CGRect theRect = CGMakeRect(0, 0, 100, 100);
CGContextAddEllipseInRect(context, theRect);
CGContextDrawPath(context, kCGPathFillStroke);

* 쿼츠 2D 툴 샘플러 : 패턴, 그레디언트, 대시 패턴
- 쿼츠 2D는 하나의 색이 아닌 그레디언트로 다각형을 채우는 기능을 지원한다. 단순한 실선이 아닌 대시 패턴의 선을 지원한다.

* QuartzFun 애플리케이션 만들기
- Xcode에서 뷰 기반의 애플리케이션 템플릿을 사용하여 새로운 프로젝트를 생성한다. 파일명은 QuartzFun으로 하자
- Classes폴더에 클래스를 추가 하자. 뷰 안에서 무언가를 그릴려면 UIView의 하위클래스를 하나 만들어야 한다. 그리고 이 하위클래스의 drawRect:메서드를 오버라이드하여 메서드 안에 드로잉 코드를 추가할 것이다.
- Cocoa Touch Class에서 Objective-C class를 선택하고 subclass of에서 UIView를 선택한다. 파일이름은 QuartzFunView.m이라고 하고 헤더 파일도 같이 생성한다.
- 오직 상수 정의만을 위한 헤더 파일을 만들어야 하므로, 파일 생성 창에서 Other 메뉴의 Empty File 템플릿을 선택하고 파일 이름은 Constants.h로 변경한다.
- UIColor 클래스는 랜덤 색을 반환하는 메서드를 가지고 있지 않기 때문에 랜덤 색을 제공하는 메서드를 하나 만들어야 한다. Empty File 템플릿을 사용하여 2개의 파일을 더 생성한다. 하나는 UIColor-Random.h와 UIColor-Random.m파일이다.

* 랜덤 색 만들기

#### UIColor-Random.h ####
#import 

@interface UIColor(Random)
+(UIColor *)randomColor;
@end

#### UIColor-Random.m ####
#import "UIColor-Random.h"

@implementation UIColor(Random)

+(UIColor *)randomColor
{
	// 메서드가 처음 실행되는지 구별하기 위해서 static 변수를 하나 선언
	// 애플리케이션이 실행되는 동안에 위의 메서드가 처음 실행되면 메서드는 랜덤 번호 생성기를 만들것이다.
	static BOOL seeded = NO;
	if(!seeded)
	{
		seeded = YES;
		srandom(time(NULL));
	}
	
	CGFloat red = (CGFloat)random()/(CGFloat)RAND_MAX;
	CGFloat blue = (CGFloat)random()/(CGFloat)RAND_MAX;
	CGFloat green = (CGFloat)random()/(CGFloat)RAND_MAX;
	return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
}
@end

* 애플리케이션 상수 정의하기
#### Constants.h ####
typedef enum {
	kLineShape = 0,
	kRectShape,
	kEllipseShape,
	kImageShape
} ShapeType;

typedef enum {
	kRedColorTab = 0,
	kBlueColorTab,
	kYellowColorTab,
	kGreenColorTab,
	kRandomColorTab
} ColorTabIndex;

#define degreesToRadian(x) (M_PI * (x) / 180.0)

* QuartzFunView 뼈대 구현하기
#### QuartzFunView.h ####
#import 
#import "Constants.h"

@interface QuartzFunView : UIView 
{
	// 화면을 드래그할 때 사용된 손가락의 위치를 추적하는데 사용된다.
	// firstTouch 변수에는 사용자가 화면상에 처음 터치한 위치를 저장
	CGPoint firstTouch;
	// lastTouch 변수에는 손가락이 드래그하며 움직이는 위치를 지속적으로 저장하고 있다가 드래그가
	// 끝 났을 때 마지막으로 터치한 위치를 저장
	CGPoint lastTouch;
	// 사용자가 선택한 색을 저장하기 위해 UIColor의 포인터를 선언
	UIColor *currentColor;
	// 사용자가 그리려고 하는 도형을 저장하기 위해 ShapeType 변수를 하나 선언
	ShapeType shapeType;
	// 아래쪽 툴바의 맨 오른쪽 메뉴를 선택하였을 때 화면에 그릴 이미지를 저장하기 위해 UIImage프로퍼티를 선언
	UIImage *drawImage;
	// 랜덤 색을 선택하였는지 판단하기 위해서.
	BOOL useRandomColor;
}

@property CGPoint firstTouch;
@property CGPoint lastTouch;
@property (nonatomic, retain) UIColor *currentColor;
@property ShapeType shapeType;
@property (nonatomic, retain) UIImage *drawImage;
@property BOOL useRandomColor;

@end

#### QuartzFunView.m ####
#import "QuartzFunView.h"
#import "UIColor-Random.h"

@implementation QuartzFunView
@synthesize firstTouch;
@synthesize lastTouch;
@synthesize currentColor;
@synthesize shapeType;
@synthesize drawImage;
@synthesize useRandomColor;

// 뷰가 nib 파일로부터 로딩되기 때문에 먼저 initWithCoder:메서드를 구현해야 한다.
// nib 파일 안의 객체 인스턴스는 아카이브 객체의 형태로 저장된다.
// 결국 객체 인스턴스가 하나의 nib 파일로부터 로딩될때, init:메서드나 initWithFrame:메서드가 호출되는것이 아니라
// initWithCoder:메서드가 호출된다. 그래서 우리는 initWithCoder:메서드 안에 애플리케이션의 초기화 코드를 추가해야 한다.
-(id)initWithCoder:(NSCoder *)coder
{
	if(( self = [super initWithCoder:coder] ))
	{
		// 색을 빨간색으로 초기화하기 위해 useRandomColor값을 NO로 설정
		// 화면에 그릴 이미지 파일을 로딩해 두었다.
		self.currentColor = [UIColor redColor];
		self.useRandomColor = NO;
		if(drawImage == nil)
			self.drawImage = [UIImage imageNamed:@"iphone.png"];		
	}	
	return self;	
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Initialization code
    }
    return self;
}

// drawRect:메서드 안에서 애플리케이션이 실제로 하는 일을 구현하게 될것이다.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}

// UIView로부터 상속 받았으며 오버라이드 될 수 있는 아래 3개의 메서드들은 사용자가 화면을
// 터치한 위치를 찾기 위해 오버라이드하여 사용하는 메서드 들이다.
// touchesBegan:withEvent 메서드는 사용자의 손가락이 화면을 처음 터치 할때 호출된다.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	// 사용자가 랜덤 색을 선택하였을 때 UIColor 클래스에 추가했던 randomColor 메서드를 
	// 호출하여 색을 랜덤 색으로 설정할 수 있게 하였다.
	if(useRandomColor)
		self.currentColor = [UIColor randomColor];
	
	// 처음 터치한 위치를 참조하기 위해 현재 위치를 저장..
	UITouch *touch = [touches anyObject];
	firstTouch = [touch locationInView:self];
	lastTouch = [touch locationInView:self];
	// setNeedsDisplay를 호출하여 화면을 다시 그리게 설정
	[self setNeedsDisplay];
}

// 사용자가 화면에 손가락을 드래그하는 동안에 지속적으로 호출하는 메서드
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];	
	[self setNeedsDisplay];
}

// 사용자가 화면에서 손가락을 떼는 순간 호출된다. 
// 이 메서드는 손가락의 마지막 위치를 lastTouch에 저장하고 뷰를 다시 그리게 요청한다.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];
	[self setNeedsDisplay];
}

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

@end

* 뷰 컨트롤러에 아웃렛과 액션 추가하기
- 애플리케이션의 인터페이스가 2개의 세그먼트 컨트롤러로 이루어졌다는 것을 기억하자.
- 하나는 화면 위쪽에 위치하며 다른 하나는 화면 아래쪽에 위치한다.
- 위쪽의 컨트롤러는 사용자가 색을 선택할 때 사용된다. 아래쪽의 컨트롤러는 도형 선택을 할 때 사용한다.
- 위쪽의 세그먼트 컨트롤러를 가리킬 아웃렛을 만들어야 하며, 사용하지 않을 때는 위쪽의 컨트롤러 메뉴를 숨길수 있어야 한다.
- 2개의 메서드를 추가로 구현할 것인데, 하나는 색을 선택했을 때 호출될 메서드이고 다른 하나는 도형이 선택되었을 때 호출될 메서드 인다.

#### QuartzFunViewController.h ####
#import 

@interface QuartzFunViewController : UIViewController 
{
	UISegmentedControl *colorControl;	
}

@property (nonatomic, retain) IBOutlet UISegmentedControl *colorControl;

-(IBAction)changeColor:(id)sender;
-(IBAction)changeShape:(id)sender;

@end

#### QuartzFunViewController.m ####
#import "QuartzFunViewController.h"
#import "QuartzFunView.h"
#import "Constants.h"

@implementation QuartzFunViewController
@synthesize colorControl;

// 사용자가 선택한 색을 확인하고 선택한 색을 객체로 생성
-(IBAction)changeColor:(id)sender
{
	UISegmentedControl *control = sender;
	NSInteger index = [control selectedSegmentIndex];
	
	// 현재 뷰를 QuartzFunView 타입으로 캐스팅
	QuartzFunView *quartzView = (QuartzFunView *)self.view;
	
	switch(index)
	{
		case kRedColorTab :
			quartzView.currentColor = [UIColor redColor];
			quartzView.useRandomColor = NO;
			break;
			
		case kBlueColorTab :
			quartzView.currentColor = [UIColor blueColor];
			quartzView.useRandomColor = NO;
			break;
			
		case kYellowColorTab :
			quartzView.currentColor = [UIColor yellowColor];
			quartzView.useRandomColor = NO;
			break;
			
		case kGreenColorTab :
			quartzView.currentColor = [UIColor greenColor];
			quartzView.useRandomColor = NO;
			break;
			
		case kRandomColorTab :
			quartzView.useRandomColor = YES;
			break;
			
		default :
			break;
			
	}
	
}

-(IBAction)changeShape:(id)sender
{
	UISegmentedControl *control = sender;
	
	[(QuartzFunView *)self.view setShapeType:[control selectedSegmentIndex]];
	
	if([control selectedSegmentIndex] == kImageShape)
		colorControl.hidden = YES;
	else
		colorControl.hidden = NO;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

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

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

@end

* QuartzFunViewController.xib 업데이트 하기
- QuartzFunViewController.xib 파일을 더블클릭하여 인터페이스 빌더를 실행한다.
- 맨 먼저 해야 할 일은 뷰의 클래스를 변경하는 일이다. QuartzFunViewController.xib라는 제목의 창 안의 View 아이콘을 클릭하고 Func+4를 눌러 아이텐터티 인스펙터 창을 열게 한다. 그리고 나서 클래스를 UIView에서 QuartzFunView로 변경한다.
- 라이브러리 창에서 Navigation Bar를 찾아서 상단에 추가 한다. Navigation Controller가 아니다.
- 다음으로 라이브러리 창에서 Segmented Control을 찾아서 네비게이션 바의 바로 위에 드래그하여 배치한다.컨트롤을 네비게이션 바의 왼쪽이나 오른쪽이 아닌 중앙에 배치한다. 세그먼트 컨트롤의 양쪽에 붙어 있는 크기 조절 점중에 하나를 잡아서 네비게이션 바의 너비와 같아지도록 늘려준다. 세그먼트 컨트롤이 선택된 상태에서 Func+1를 눌러서 속성 인스펙터 창이 나타나게 하고 세그먼트 수를 2에서 5로 늘린다. 레이블은 순서대로 Red, Blue, Yellow, Green, Random으로 변경한다.
- File's Owner 아이콘 위에서 컨트롤-드래그 하여 세그먼트 컨트롤에 끌어 놓은 뒤, colorControl 아웃렛과 연결한다. Func+2를 누르면 커넥션 인스펙터창이 나타나는데 Value Changed 이벤트를 드래그하여 File's Owner 아이콘위에 글어 놓고 ChangeColor: 액션을 선택한다.
- 라이브러리 창에서 Toolbar를 찾아서, 뷰 윈도우의 하단에 끌어다 놓는다. 툴바는 피요 없는 버튼이 있기 때문에 버튼을 삭제 한다. 또 다른 세그먼트 컨트롤 하나를 드래그 하여, 툴바 위에 붙여 올려놓는다. 세그먼트 컨트롤을 클릭하고 툴바의 양쪽과의 공간이 조금 남아 있도록 크기를 변경한다.
- 분할 컨트롤을 선택한 채로 Func+1을 눌러 속성 인스펙처 창을 띄우고, 분할 컨트롤의 수를 2에서 4로 변경한다 레이블은 차례로 Line, Rect, Ellipse, Image로 변경한다. 작업 찾을 커넥션 인스펙터로 바꾸고, Value Changed이벤트를 File's Owner의 changeShape: 액션에 연결한다.

* 선 그리기
- xcode에서 QuartzFunView.m에서 drawRect:메서드를 아래와 같이 변경한다.
#### QuartzFunView.m ####
// drawRect:메서드 안에서 애플리케이션이 실제로 하는 일을 구현하게 될것이다.
- (void)drawRect:(CGRect)rect 
{
	// 선을 그릴 위치를 정하기 위해 현재 컨텍스트의 레퍼런스를 가져온다.
    CGContextRef context = UIGraphicsGetCurrentContext();	
	// 선의 너비를 2 픽셀로 설정한다.
	CGContextSetLineWidth(context, 2.0f);
	// 선을 그릴때 사용할 색을 설정한다. CGColor 프로퍼티 인자를 필요로 한다.	
	CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
	
	switch (shapeType)
	{
		case kLineShape :
			// 사용자가 처음 터치한 부분으로 보이지 않는 펜을 옮긴다.
			CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
			// 사용자가 마지막으로 터치한 위치로 선을 그린다. 아직은 보이지 않는 펜으로
			CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
			// 눈에 보이는 선으로 그린다.
			CGContextStrokePath(context);
			break;
		case kRectShape :
			break;
		case kEllipseShape :
			break;
		case kImageShape :
			break;
		default :
			break;
	}	
}


* 직사각형과 타원 그리기
- draw:메서드를 다시 수정
#### QuartzFunView.m ####

// drawRect:메서드 안에서 애플리케이션이 실제로 하는 일을 구현하게 될것이다.
- (void)drawRect:(CGRect)rect 
{
	// 선을 그릴 위치를 정하기 위해 현재 컨텍스트의 레퍼런스를 가져온다.
    CGContextRef context = UIGraphicsGetCurrentContext();	
	// 선의 너비를 2 픽셀로 설정한다.
	CGContextSetLineWidth(context, 2.0f);
	// 선을 그릴때 사용할 색을 설정한다. CGColor 프로퍼티 인자를 필요로 한다.	
	CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
	
	// 타원과 직사각형에 불투명 색을 채워야 하므로 currentColor를 사용하여 색을 채우는 함수를 추가
	CGContextSetFillColorWithColor(context, currentColor.CGColor);
	// 사용자의 드래그를 기준으로 직사각형의 정보를 저장하기 위해 currentRect변수를 선언
	// CGRect는 origin과 size라는 두 개의 멤버 변수를 가지고 있다는것을 기억.
	// x,y 좌표와 너비, 높이 값을 인자로 사용하여 CGRectMake()를 호출하면 CGRect를 생성할 수 있고
	// CGRect를 사용하여 직사각형을 그릴수 있다.
	// 사용자가 특정 방향으로 드래그 하면, origin값은 드래그 방향에 따라서 달라질것이다. 
	// origin값을 결정하기 위해 2개의 좌표의 x와 y를 각각 비교하여 작은 쪽의 값을 사용하였다.
	// x좌표 사이의 절대값과 y좌료 사이의 절대값을 구하여 size 값을 결정..
	CGRect currentRect = CGRectMake(
									(firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x,
									(firstTouch.y > lastTouch.y) ? lastTouch.y : firstTouch.y,
									fabsf(firstTouch.x - lastTouch.y),
									fabsf(firstTouch.y - lastTouch.y));
	
	switch (shapeType)
	{
		case kLineShape :
			// 사용자가 처음 터치한 부분으로 보이지 않는 펜을 옮긴다.
			CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
			// 사용자가 마지막으로 터치한 위치로 선을 그린다. 아직은 보이지 않는 펜으로
			CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
			// 눈에 보이는 선으로 그린다.
			CGContextStrokePath(context);
			break;
		case kRectShape :
			// 직사각형을 추가
			CGContextAddRect(context, currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kEllipseShape :
			// 타원을 추가
			CGContextAddEllipseInRect(context, currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kImageShape :
			break;
		default :
			break;
	}	
}



* 이미지 그리기
#### QuartzFunView.m ####

// drawRect:메서드 안에서 애플리케이션이 실제로 하는 일을 구현하게 될것이다.
- (void)drawRect:(CGRect)rect 
{
	// 선을 그릴 위치를 정하기 위해 현재 컨텍스트의 레퍼런스를 가져온다.
    CGContextRef context = UIGraphicsGetCurrentContext();	
	// 선의 너비를 2 픽셀로 설정한다.
	CGContextSetLineWidth(context, 2.0f);
	// 선을 그릴때 사용할 색을 설정한다. CGColor 프로퍼티 인자를 필요로 한다.	
	CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
	
	// 타원과 직사각형에 불투명 색을 채워야 하므로 currentColor를 사용하여 색을 채우는 함수를 추가
	CGContextSetFillColorWithColor(context, currentColor.CGColor);
	// 사용자의 드래그를 기준으로 직사각형의 정보를 저장하기 위해 currentRect변수를 선언
	// CGRect는 origin과 size라는 두 개의 멤버 변수를 가지고 있다는것을 기억.
	// x,y 좌표와 너비, 높이 값을 인자로 사용하여 CGRectMake()를 호출하면 CGRect를 생성할 수 있고
	// CGRect를 사용하여 직사각형을 그릴수 있다.
	// 사용자가 특정 방향으로 드래그 하면, origin값은 드래그 방향에 따라서 달라질것이다. 
	// origin값을 결정하기 위해 2개의 좌표의 x와 y를 각각 비교하여 작은 쪽의 값을 사용하였다.
	// x좌표 사이의 절대값과 y좌료 사이의 절대값을 구하여 size 값을 결정..
	CGRect currentRect = CGRectMake(
									(firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x,
									(firstTouch.y > lastTouch.y) ? lastTouch.y : firstTouch.y,
									fabsf(firstTouch.x - lastTouch.y),
									fabsf(firstTouch.y - lastTouch.y));
	
	switch (shapeType)
	{
		case kLineShape :
			// 사용자가 처음 터치한 부분으로 보이지 않는 펜을 옮긴다.
			CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
			// 사용자가 마지막으로 터치한 위치로 선을 그린다. 아직은 보이지 않는 펜으로
			CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
			// 눈에 보이는 선으로 그린다.
			CGContextStrokePath(context);
			break;
		case kRectShape :
			// 직사각형을 추가
			CGContextAddRect(context, currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kEllipseShape :
			// 타원을 추가
			CGContextAddEllipseInRect(context, currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kImageShape : {
			// 위에 보면 중괄호가 있다. GCC는 case문의 다음에 변수를 선언하면 컴파일 에러를 발생한다.
			// 이 중괄호는 GCC에게 불평하지 말라고 알려주는 표시 이다.
			// 이미지의 중심 좌표를 계산하였다.
			CGFloat horizontalOffSet = drawImage.size.width / 2;
			CGFloat verticalOffSet = drawImage.size.height / 2;
			CGPoint drawPoint = CGPointMake(lastTouch.x - horizontalOffSet, lastTouch.y - verticalOffSet);
			// 이미지를 화면에 그리게 한다.
			[drawImage drawAtPoint:drawPoint];
			break;
		}
		default :
			break;
	}	
}


* QuartzFun 애플리케이션 최적화하기
- 애플리케이션이 좀 더 복잡해지고 상대적으로 느린 프로세서상에서 실행된다면 애플리케이션이 느리다고 느낄 것이다. 이문제는 QuartzFunView.m파일의 touchesMoved:메서드와 touchesEnded:메서드 안에서 발생한다.
[self setNeedsDisplay];

- 분명 위 코드는 뷰의 내용이 변경되었으니 뷰를 다시 그리도록 호출하는 것이다. 동작하는 데 문제는 없지만 화면상에 매우 작은 변화만 있어도 뷰의 전 영역을 지우고 다시 그린다.
- 드래그하는 동안 계속해서 뷰의 전체를 지우고 다시 그리는 대신 setNeedsDisplayInRect:메서드를 사용하여 필요한 부분만을 지우고 다시 그릴수 있다. setNeedsDisplayInRect:메서드는 NSView 클래스의 멤버이고, 이 메서드는 지우고 다시 그리려는 영역을 직사각형으로 표시한다. 이 메서드를 사용하여 현재 드로잉 동작에 의해 다시 그려져야만 하는 뷰의 영역을 표시해 두는 것으로 좀 더 효율적으로 드로잉을 처리할 수 있다.
- setNeedsDisplayInRect:메서드를 사용하여 단순히 firstTouch와 lastTouch 사이의 직사각형 영역만을 다시 그리는 것이 아니라 계속되는 드래그로 사용자가 그리려는 도형의 영역에 포함되었다가 제외되서 흰 배경으로 보여야 하는 영역들도 다시 그려져야 한다. 사용자가 화면을 터치하고 이리저리 드래그 할때 firstTouch와 lastTouch 사이의 부분만을 다시 그린다면, 우리가 제공하려는 기능과는 달리 흰 배경으로 다시 초기화되어야 하는 화면들이 다시 그려지지 않은 채 그대로 남게 될 것이다.
- CGRect 인스턴스 변수 하나에 새롭게 도형이 그려질 영역과 흰색으로 다시 그려져야 할 영역 모두를 저장하는 것이다. tochesBegan:메서드 안에서 인스턴스 변수를 사용자가 터치한 위치로 재 설정한다. 그런다음 tochesMoved:메서드와 touchesEnded:메서드에서 CGRectUnion:메서드를 호출하여 현재 사용자의 드래그로 인해 그려진 직사각형 영역과 이미 그려진 직사각형의 영역을 합한 새로운 직사각형의 영역을 저장한다. 이 새 직사각형 영역을 setNeedsDisplayInRect:에게 넘겨 주어 새로 그려질 뷰의 영역을 지정할수 있다.

#### QuartzFunView.h ####
#import 
#import "Constants.h"

@interface QuartzFunView : UIView 
{
	// 화면을 드래그할 때 사용된 손가락의 위치를 추적하는데 사용된다.
	// firstTouch 변수에는 사용자가 화면상에 처음 터치한 위치를 저장한다.
	CGPoint firstTouch;
	// lastTouch 변수에는 손가락이 드래그하며 움직이는 위치를 지속적으로 저장하고 있다가 드래그가
	// 끝 났을 때 마지막으로 터치한 위치를 저장한다.
	CGPoint lastTouch;
	// 사용자가 선택한 색을 저장하기 위해 UIColor의 포인터를 선언
	UIColor *currentColor;
	// 사용자가 그리려고 하는 도형을 저장하기 위해 ShapeType 변수를 하나 선언
	ShapeType shapeType;
	// 아래쪽 툴바의 맨 오른쪽 메뉴를 선택하였을 때 화면에 그릴 이미지를 저장하기 
	// 위해 UIImage 프로퍼티를 선언
	UIImage *drawImage;
	// 랜덤 색을 선택하였는지 판단하기 위해서..
	BOOL useRandomColor;
	// 다시 그려질 영역을 저장하는데 사용..
	CGRect redrawRect;
}

@property CGPoint firstTouch;
@property CGPoint lastTouch;
@property (nonatomic, retain) UIColor *currentColor;
@property ShapeType shapeType;
@property (nonatomic, retain) UIImage *drawImage;
@property BOOL useRandomColor;
@property (readonly) CGRect currentRect;
@property CGRect redrawRect;

@end

#### QuartzFunView.m ####
#import "QuartzFunView.h"
#import "UIColor-Random.h"

@implementation QuartzFunView
@synthesize firstTouch;
@synthesize lastTouch;
@synthesize currentColor;
@synthesize shapeType;
@synthesize drawImage;
@synthesize useRandomColor;
@synthesize redrawRect;
@synthesize currentRect;

-(CGRect)currentRect
{
	return CGRectMake(
					  (firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x,
					  (firstTouch.y > lastTouch.y) ? lastTouch.y : firstTouch.y,
					  fabsf(firstTouch.x - lastTouch.x),
					  fabsf(firstTouch.y - lastTouch.y));
}

// 뷰가 nib 파일로부터 로딩되기 때문에 먼저 initWithCoder:메서드를 구현해야 한다.
// nib 파일 안의 객체 인스턴스는 아카이브 객체의 형태로 저장된다.
// 결국 객체 인스턴스가 하나의 nib 파일로부터 로딩될때, init:메서드나 initWithFrame:메서드가 호출되는것이 아니라
// initWithCoder:메서드가 호출된다. 그래서 우리는 initWithCoder:메서드 안에 애플리케이션의 초기화 코드를 추가해야 한다.
-(id)initWithCoder:(NSCoder *)coder
{
	if(( self = [super initWithCoder:coder] ))
	{
		// 색을 빨간색으로 초기화하기 위해 useRandomColor값을 NO로 설정
		// 화면에 그릴 이미지 파일을 로딩해 두었다.
		self.currentColor = [UIColor redColor];
		self.useRandomColor = NO;
		if(drawImage == nil)
			self.drawImage = [UIImage imageNamed:@"iphone.png"];		
	}	
	return self;	
}

- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Initialization code
    }
    return self;
}

// drawRect:메서드 안에서 애플리케이션이 실제로 하는 일을 구현하게 될것이다.
- (void)drawRect:(CGRect)rect 
{
	// 선을 그릴 위치를 정하기 위해 현재 컨텍스트의 레퍼런스를 가져온다.
    CGContextRef context = UIGraphicsGetCurrentContext();	
	// 선의 너비를 2 픽셀로 설정한다.
	CGContextSetLineWidth(context, 2.0f);
	// 선을 그릴때 사용할 색을 설정한다. CGColor 프로퍼티 인자를 필요로 한다.	
	CGContextSetStrokeColorWithColor(context, currentColor.CGColor);
	
	// 타원과 직사각형에 불투명 색을 채워야 하므로 currentColor를 사용하여 색을 채우는 함수를 추가
	CGContextSetFillColorWithColor(context, currentColor.CGColor);
	// 사용자의 드래그를 기준으로 직사각형의 정보를 저장하기 위해 currentRect변수를 선언
	// CGRect는 origin과 size라는 두 개의 멤버 변수를 가지고 있다는것을 기억.
	// x,y 좌표와 너비, 높이 값을 인자로 사용하여 CGRectMake()를 호출하면 CGRect를 생성할 수 있고
	// CGRect를 사용하여 직사각형을 그릴수 있다.
	// 사용자가 특정 방향으로 드래그 하면, origin값은 드래그 방향에 따라서 달라질것이다. 
	// origin값을 결정하기 위해 2개의 좌표의 x와 y를 각각 비교하여 작은 쪽의 값을 사용하였다.
	// x좌표 사이의 절대값과 y좌료 사이의 절대값을 구하여 size 값을 결정..
	//CGRect currentRect = CGRectMake(
	//								(firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x,
	//								(firstTouch.y > lastTouch.y) ? lastTouch.y : firstTouch.y,
	//								fabsf(firstTouch.x - lastTouch.y),
	//								fabsf(firstTouch.y - lastTouch.y));
	
	switch (shapeType)
	{
		case kLineShape :
			// 사용자가 처음 터치한 부분으로 보이지 않는 펜을 옮긴다.
			CGContextMoveToPoint(context, firstTouch.x, firstTouch.y);
			// 사용자가 마지막으로 터치한 위치로 선을 그린다. 아직은 보이지 않는 펜으로
			CGContextAddLineToPoint(context, lastTouch.x, lastTouch.y);
			// 눈에 보이는 선으로 그린다.
			CGContextStrokePath(context);
			break;
		case kRectShape :
			// 직사각형을 추가
			//CGContextAddRect(context, currentRect);
			CGContextAddRect(context, self.currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kEllipseShape :
			// 타원을 추가
			//CGContextAddEllipseInRect(context, currentRect);			
			CGContextAddEllipseInRect(context, self.currentRect);
			// 색을 칠한다.
			CGContextDrawPath(context, kCGPathFillStroke);
			break;
		case kImageShape : {
			// 위에 보면 중괄호가 있다. GCC는 case문의 다음에 변수를 선언하면 컴파일 에러를 발생한다.
			// 이 중괄호는 GCC에게 불평하지 말라고 알려주는 표시 이다.
			// 이미지의 중심 좌표를 계산하였다.
			CGFloat horizontalOffSet = drawImage.size.width / 2;
			CGFloat verticalOffSet = drawImage.size.height / 2;
			CGPoint drawPoint = CGPointMake(lastTouch.x - horizontalOffSet, lastTouch.y - verticalOffSet);
			// 이미지를 화면에 그리게 한다.
			[drawImage drawAtPoint:drawPoint];
			break;
		}
		default :
			break;
	}
	
	
}

// UIView로부터 상속 받았으며 오버라이드 될 수 있는 아래 3개의 메서드들은 사용자가 화면을
// 터치한 위치를 찾기 위해 오버라이드하여 사용하는 메서드 들이다.
// touchesBegan:withEvent 메서드는 사용자의 손가락이 화면을 처음 터치 할때 호출된다.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	// 사용자가 랜덤 색을 선택하였을 때 UIColor 클래스에 추가했던 randomColor 메서드를 
	// 호출하여 색을 랜덤 색으로 설정할 수 있게 하였다.
	if(useRandomColor)
		self.currentColor = [UIColor randomColor];
	
	// 처음 터치한 위치를 참조하기 위해 현재 위치를 저장..
	UITouch *touch = [touches anyObject];
	firstTouch = [touch locationInView:self];
	lastTouch = [touch locationInView:self];
	// setNeedsDisplay를 호출하여 화면을 다시 그리게 설정
	[self setNeedsDisplay];
}

// 사용자가 화면에 손가락을 드래그하는 동안에 지속적으로 호출하는 메서드
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];	
	//[self setNeedsDisplay];
	
	if(shapeType == kImageShape)
	{
		CGFloat horizontalOffset = drawImage.size.width / 2;
		CGFloat verticalOffset = drawImage.size.height / 2;
		redrawRect = CGRectUnion(redrawRect,
								 CGRectMake(lastTouch.x - horizontalOffset, lastTouch.y - verticalOffset,
											drawImage.size.width, drawImage.size.height));
	}
	// 그려져 있는 영역과 사용자가 드래그 한 영역을 계속 누적해서 저장해야 한다.
	redrawRect = CGRectUnion(redrawRect, self.currentRect);
	// rect영역 부분만을 지우고 다시 그린다.
	[self setNeedsDisplayInRect:redrawRect];
}

// 사용자가 화면에서 손가락을 떼는 순간 호출된다. 
// 이 메서드는 손가락의 마지막 위치를 lastTouch에 저장하고 뷰를 다시 그리게 요청한다.
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];
	//[self setNeedsDisplay];
	
	if(shapeType == kImageShape)
	{
		CGFloat horizontalOffset = drawImage.size.width / 2;
		CGFloat verticalOffset = drawImage.size.height / 2;
		redrawRect = CGRectUnion(redrawRect, 
								 CGRectMake(lastTouch.x - horizontalOffset, lastTouch.y - verticalOffset,
														drawImage.size.width, drawImage.size.height));
	} else {
		redrawRect = CGRectUnion(redrawRect, self.currentRect);
	}
	// 그려져 있는 영역과 사용자가 드래그 한 영역을 계속 누적해서 저장해야 한다.
	redrawRect = CGRectInset(redrawRect, -2.0f, -2.0f);
	
	[self setNeedsDisplayInRect:redrawRect];
	
}

- (void)dealloc {
	[currentColor release];
	[drawImage release];
    [super dealloc];
}
@end


- 그려져 있는 영역과 사용자가 드래그한 영역을 계속해서 누적하지 않으면 아래와 같은 결과가 나온다.


* OpenGL의 기본
- OpenGL ES와 쿼츠 2D는 드로잉에 있어서 근본적으로 다른 방법으로 접근한다.
- http://www.khronos.org/opengles를 둘러보는것도 좋다.
- 아니면 다음 URL에서 "tutorial"이라는 단어로 검색하는 것이 더 나을 수 있따. http://khronos.org/developers/resources/opengles/
- 아니면 제프 라마키의 아이폰 블로그의 OpenGL 튜토리얼도 참고 . http://iphonedevelopment.blogspot.com/

* GLFun 애플리케이션 만들기

- Xcode에서 뷰 기반의 새 애플리케이션을 하나 만들고, GLFun라고 부르자. QuartzFun프로젝트로부터 Constants.h, UIColor-Random.h, UIColor-Random.m, iPhone.png 파일을 새 프로젝트로 복사한다.

#### GLFunViewController.h ####
#import 

@interface GLFunViewController : UIViewController 
{
	UISegmentedControl *colorControl;
}

@property (nonatomic, retain) IBOutlet UISegmentedControl *colorControl;
-(IBAction)changeColor:(id)sender;
-(IBAction)changeShape:(id)sender;
@end

#### GLFunViewController.m ####
#import "GLFunViewController.h"
#import "Constants.h"
#import "GLFunView.h"
#import "UIColor-Random.h"

@implementation GLFunViewController
@synthesize colorControl;

-(IBAction)changeColor:(id)sender
{
	UISegmentedControl *control = sender;
	NSInteger index = [control selectedSegmentIndex];
	
	GLFunView *glView = (GLFunView *)self.view;
	
	switch (index)
	{
		case kRedColorTab:
			glView.currentColor = [UIColor redColor];
			glView.useRandomColor = NO;
			break;
		
		case kBlueColorTab:
			glView.currentColor = [UIColor blueColor];
			glView.useRandomColor = NO;
			break;
			
		case kYellowColorTab:
			glView.currentColor = [UIColor yellowColor];
			glView.useRandomColor = NO;
			break;
			
		case kGreenColorTab:
			glView.currentColor = [UIColor greenColor];
			glView.useRandomColor = NO;
			break;
			
		case kRandomColorTab:
			glView.useRandomColor = YES;
			break;
			
		default :
			break;
	}
}

-(IBAction)changeShape:(id)sender
{
	UISegmentedControl *control = sender;
	[(GLFunView *)self.view setShapeType:[control selectedSegmentIndex]];
	if([control selectedSegmentIndex] == kImageShape)
		[colorControl setHidden:YES];
	else
		[colorControl setHidden:NO];
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
}

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


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

@end

- 시작하기에 앞서 프로젝트에 몇 개의 파일을 더 추가하자. 12 GLFun 폴더 안을 보면, Texture2D.h, Texture2D.m, OpenGLES2DView.h, OpenGLES2DView.m라는 이름의 파일 4개를 프로젝트에 추가 한다.
- Texture2D.h와 Texture2D.m에 들어 있는 코드들은 애플에서 작성한 것이며, OpenGL ES를 사용하여 쉽게 이미지를 그릴 수 있게 해주는 코드들이 들어 있다.
- 두번째 파일은 애플에서 기본적으로 제공하는 샘플코드를 토대로 만들어진 파일이다. 이 파일에는 OpenGL을 사용하여 2차워 드로잉을 하는데 필요한 설정에 관련된 코드들이 들어 있다. OpenGL을 설정하는 일은 매우 복잡한 작업이므로 이미 필요한 설정이 구현 된 코드를 제공하는 것이다.
- OpenGL ES는 본질적으로 스프라이트나 이미지를 그리는 메서드를 가지고 있지 않다. 그것은 이미지의 한 종류인 텍스처만을 가지고 있으며 텍스처는 반드시 도형이나 객체 위에 그려진다. OpenGL ES상에서 이미지를 그리는 방법은 사각형을 그리는 것이고, 사각형을 그리고 나면 텍스처를 사각형과 매핑하여 폴리곤의 크기와 정확히 일치하도록 만들어야 한다. Texture2D는 이와 같은 비교적 복잡한 과정을 캡슐화하여 사용자에게 제공한다.
- OpenGLES2DView는 UIView의 하위클래스이고 드로잉을 하기 위해 OpenGL을 사용한다. OpenGLES의 좌표계와 뷰의 좌표계를 1:1로 매핑하기 위해 뷰를 설정할 것이다. OpenGL ES는 3차원 시스템이다. OpenGLES2DView는 OpenGL의 3D 세계를 2D 뷰의 픽셀로 매핑한다. 뷰와 OpenGL의 컨텍스트를 1:1로 매핑하였다고 해도, y좌표는 여전히 뒤집혀서 보이므로 뷰 좌표계로부터 y좌표를 변환해야 한다. 뷰 좌표계에서는 y값이 증가하면 아래로 이동하고, OpenGL 좌표계에서는 y값이 증가하면 위로 이동한다.
- OpenGLES2DView를 사용하기 위해, OpenGLES2DView의 하위클래스로 변경한 뒤 조금 후에 보게 될 실제로 드로잉할 draw 메서드를 구현할 것이다.

#### GLFunView.h ####
#import 
#import "Constants.h"
#import "OpenGLES2DView.h"

@class Texture2D;

@interface GLFunView:OpenGLES2DView
{
	CGPoint firstTouch;
	CGPoint lastTouch;
	UIColor *currentColor;
	BOOL useRandomColor;
	ShapeType shapeType;
	Texture2D *sprite;
}

@property CGPoint firstTouch;
@property CGPoint lastTouch;
@property (nonatomic, retain) UIColor *currentColor;
@property BOOL useRandomColor;
@property ShapeType shapeType;
@property (nonatomic, retain) Texture2D *sprite;

@end

#### GLFunView.m ####
#import "GLFunView.h"
#import "UIColor-Random.h"
#import "Texture2D.h"

@implementation GLFunView
@synthesize firstTouch;
@synthesize lastTouch;
@synthesize currentColor;
@synthesize useRandomColor;
@synthesize shapeType;
@synthesize sprite;

-(id)initWithCoder:(NSCoder *)coder
{
	if(self = [super initWithCoder:coder])
	{
		self.currentColor = [UIColor redColor];
		self.useRandomColor = NO;
		self.sprite = [[Texture2D alloc] initWithImage:[UIImage imageNamed:@"iphone.png"]];
		
		// Texture 2D 객체 생성
		glBindTexture(GL_TEXTURE_2D, sprite.name);
	}
	
	return self;
}

-(void)draw
{
	// OpenGL은 드로잉 과정을 3단계로 구성하고 있다.
	// 첫번째 컨텍스트에 드로잉한다.
	// 둘째 드로잉을 마치면, 컨텍스트를 렌더링하여 버퍼에 담는다.
	// 세번째 실제로 픽셀들이 화면에 그려질 때 렌더링 버퍼의 내용을 화면에 보여준다.
	
	// 가상세계를 초기화
	glLoadIdentity();
	// 배경색을 흰색으로 초기화 한다.
	glClearColor(0.78f, 0.78f, 0.78f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	
	// UIColor 객체로부터 각각의 RGB값을 가져와서 glColor4f함수를 호출하여 OpenGL의 드로잉 색을 설정
	// 각각의 색을 가져올때 편리하게 사용할수 있는 UIColor객체를 사용하면된다.
	CGColorRef color = currentColor.CGColor;
	const CGFloat *components = CGColorGetComponents(color);
	CGFloat red = components[0];
	CGFloat green = components[1];
	CGFloat blue = components[2];	
	glColor4f(red, green, blue, 1.0f);
	
	switch (shapeType)
	{
		case kLineShape : {
			// OpenGL ES의 맵 텍스처 기능을 제거 한다.
			glDisable(GL_TEXTURE_2D);
			
			// 선을 그리려면 2개의 점 vertices가 필요하다. 
			// 2개의 점의 좌표를 저장하려면 길이가 4인 배열을 하나 만들어야 한다.
			// OpenGL에서 좌표는 CGPoint와 같은 구조체에 들어있지 않다. 
			// 대신에 화면에 그릴 도형을 구성할 모든 점의 좌표를 배열로 만들어야 한다.
			// OpenGL을 사용하여 (100,150)에서 (200,250)으로 선을 그릴려면 다음과 같은 버텍스 배열이 필요하다.
			// vertices[0] = 100;
			// vertices[1] = 150;
			// vertices[2] = 200;
			// vertices[3] = 250;
			GLfloat vertices[4];
			
			vertices[0] = firstTouch.x;
			vertices[1] = self.frame.size.height - firstTouch.y;
			vertices[2] = lastTouch.x;
			vertices[3] = self.frame.size.height - lastTouch.y;
			
			// 선의 너비를 2.0으로 설정
			glLineWidth(2.0);			
			// 버텍스 배열을 OpenGL ES에 넘겨준다.
			glVertexPointer(2, GL_FLOAT, 0, vertices);
			// OpenGL ES가 배열을 사용하여 선을 그리게 한다.
			glDrawArrays(GL_LINES, 0, 2);
			
			break;
		}
		case kRectShape : {
			glDisable(GL_TEXTURE_2D);
			
			GLfloat vertices[8];
			
			GLfloat minX = (firstTouch.x > lastTouch.x) ? lastTouch.x : firstTouch.x;
			GLfloat minY = (self.frame.size.height - firstTouch.y > self.frame.size.height - lastTouch.y) ? 
			self.frame.size.height - lastTouch.y : self.frame.size.height - firstTouch.y;
			
			GLfloat maxX = (firstTouch.x > lastTouch.x) ? firstTouch.x : lastTouch.x;
			GLfloat maxY = (self.frame.size.height - firstTouch.y > self.frame.size.height - lastTouch.y) ?
			self.frame.size.height - firstTouch.y : self.frame.size.height - lastTouch.y;
			
			vertices[0] = maxX;
			vertices[1] = maxY;
			vertices[2] = minX;
			vertices[3] = maxY;
			vertices[4] = minX;
			vertices[5] = minY;
			vertices[6] = maxX;
			vertices[7] = minY;
			
			glVertexPointer(2, GL_FLOAT, 0, vertices);
			glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
			break;
		}
		case kEllipseShape : {
			// OpenGL ES는 타원을 그리는 라이브러리를 제공하지 않고 있다.
			glDisable(GL_TEXTURE_2D);
			
			// 좌표를 구성하기 위해서 720개의 GLfloats 변수를 저장하기 위해 버텍스 배열을 하나 정의한다.
			// 360개의 좌표를 구성하기 위해서는 720개의 변수가 필요.. 2개가 한쌍.
			GLfloat vertices[720];
			
			// firstTouch와 lastTouch 변수안에 저장된 2개의 좌표 값으로 타원의 수평 반지름과 수직 반지름을 계산한다.
			GLfloat xradius = (firstTouch.x > lastTouch.x) ? (firstTouch.x - lastTouch.x)/2 : (lastTouch.x - firstTouch.x)/2;
			GLfloat yradius = (self.frame.size.height - firstTouch.y > self.frame.size.height - lastTouch.y) ?
			((self.frame.size.height - firstTouch.y) - (self.frame.size.height - lastTouch.y))/2 :
			((self.frame.size.height - lastTouch.y) - (self.frame.size.height - firstTouch.y))/2;
			
			// for문을 사용하여 원의 둘레를 구성하는 점들의 좌표 값을 계산한다.
			for(int i = 0; i < 720; i+= 2)
			{
				GLfloat xOffset = (firstTouch.x > lastTouch.x) ? lastTouch.x + xradius : firstTouch.x + xradius;
				GLfloat yOffset = (self.frame.size.height - firstTouch.y > self.frame.size.height - lastTouch.y) ?
				self.frame.size.height - lastTouch.y + yradius : self.frame.size.height - firstTouch.y + yradius;
				
				vertices[i] = (cos(degreesToRadian(i/2))*xradius) + xOffset;
				vertices[i+1] = (sin(degreesToRadian(i/2))*yradius) + yOffset;				
			}
			
			glVertexPointer(2, GL_FLOAT, 0, vertices);
			glDrawArrays(GL_TRIANGLE_FAN, 0, 360);
			break;
		}
		case kImageShape :
			glEnable(GL_TEXTURE_2D);
			[sprite drawAtPoint:CGPointMake(lastTouch.x, self.frame.size.height - lastTouch.y)];
			break;
		default :
			break;
	}
	
	// OpenGL ES를 사용하여 드로잉을 할 때는 언제나 버퍼의 데이터를 렌더링해야 한다.
	// 그리고 뷰의 컨텍스트에 새롭게 렌더링된 내용을 표시해야 한다.
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];	
}

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

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	if(useRandomColor)
		self.currentColor = [UIColor randomColor];
	
	UITouch *touch = [[event touchesForView:self] anyObject];
	firstTouch = [touch locationInView:self];
	lastTouch = [touch locationInView:self];
	[self draw];
}

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];
	
	[self draw];
}

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	UITouch *touch = [touches anyObject];
	lastTouch = [touch locationInView:self];
	
	[self draw];
}

@end

*  Nib를 설계하고 프레임워크을 추가한 뒤에 애플리케이션 실행하기
- QuartzFunViewController.xib업데이트 하기와 동일하게 진행하는데 QuartzFunView을 GLFunView클래스로 바꾸어 사용한다.
- 컴파일 하기 전에 프로젝트에 2개의 프레임워크를 링크해야만 한다. OpenGLES.framework와 QuartzCore.framework를 추가한다.
- Xcode에서 Frameworks에서 마우스 우클릭 후 Add > Existing Frameworks를 선택한다.
: