[iOS Study] 뷰와 뷰 계층구조 |
출처 : 아론 힐리가스의 iOS 프로그래밍
뷰는 UIView 의 인스턴스이거나 그 하위 클래스의 인스턴스이다.
뷰는 자신을 그리는 법을 알고 있다.
뷰는 터치와 같은 이벤트를 처리한다.
뷰는 뷰 계층구조상에 존재한다. 뷰 계층구조의 루트는 앱의 윈도우이다.
-
iOS 앱은 앱의 모든 뷰의 컨테이너 역할을 하는 UIWindow 인스턴스를 하나 가진다.
윈도우는 앱이 실행될 때 만들어진다.
그리고 윈도우가 만들어지면 그 윈도우에 다른 뷰들을 추가 할 수 있다.
-
계층의 모든 뷰는 윈도우를 가지고 자신을 그린다.
뷰는 그 자체를 자신의 CALayer 인스턴스인 레이어에 나타낸다
( 뷰의 레이어는 비트맵 이미지로 생각할 수 있다. )
모든 뷰의 레이어들이 화면에 합성된다.
-
UIView 하위 클래스 템플릿은 두 개의 메소드 스텁(stub)을 제공한다.
첫 번째는 UIView 의 지정 초기화 메소드인 initWithFrame: 이다.
이 메소드는 CGRect 타입의 인자 하나를 가진다.
그 인자는 뷰의 프레임을 나타내는 UIView 의 frame 프로퍼티가 된다.
뷰의 frame 은 뷰의 크기와 상위뷰를 기준으로 상대적인 뷰의 위치를 명시한다.
뷰의 크기는 항상 frame 에 의해 지정되기 때문에 뷰는 항상 사각형이다.
CGRect 타입은 origin 과 size 를 맴버로 가진다.
origin 은 CGPoint 타입의 C 구조체이고 x 와 y 의 두 float 변수를 가진다.
size 는 CGSize 타입의 C 구조체로 width 와 height 의 두 float 변수를 가진다.
-
C 구조에는 Objective-C 객체가 아니기 때문에 CGRect 에 메시지를 보낼 수 없다.
CGRect 를 만들려면 CGRectMake() 함수를 사용하고 인자로 origin.x, origin.y, size.width, size.height 를 전달한다.
CGRect 는 다른 객체들에 비해 작기 때문에 구조체의 포인터를 전달하는 대신에 구조체 전체를 전달한다.
따라서 initWithFrame: 은 CGRect * 가 아닌 CGRect 를 요구한다.
-
값들은 기본적으로 픽셀 단위가 아니라 포인트 단위이다.
만약 이 값들이 픽셀 단위라면 다른 해상도를 가진 디스플레이들 간(레티나 vs 비레티나)에 일관될 수 없다.
포인트는 상대적인 측정 단위이다.
포인트의 픽셀 수는 디스플레이의 픽셀 수에 따라 달라질 것이다.
크기, 위치, 선, 곡선은 디스플레이 해상도에 따라 차이를 두기 위해 항상 포인트로 기술한다.
-
레티나 디스플레이에서 한 픽셀은 가로 1/2 포인트, 세로 1/2 포인트이다.
비레티나 디스플레이에서 한 픽셀은 가로 1 포인트, 세로 1포인트이다.
종이에 인쇄할 때 1인치는 72포인트에 해당하는 크기이다.
-
Xcode 콘솔에 “Application windows are expected to have a root view controller at the end of application launch.” 라는 메시지가 나타날 것이다.
뷰 컨트롤러는 앱 뷰 계층구조의 뷰들을 제어하는 객체이다.
그리고 대다수 iOS 앱들은 하나 이상의 뷰 컨트롤러를 가지고 있다.
-
UIView 의 각 인스턴스는 superview 프로퍼티를 가진다.
( 강한 참조 순환을 피하려면 superview 프로퍼티는 약한 참조여야 한다. )
-
drawRect: 메소드는 뷰가 자신을 레이어에 그리는 과정을 진행하는 곳이다.
UIView 의 하위 클래스들은 직접 그리기를 수행하기 위해 drawRect: 를 재정의한다.
-
보통 drawRect:를 재정의할 때 먼저 하는 것은 뷰의 bounds 사각 영역을 가져오는 것이다.
UIView 에서 상속된 bounds 프로퍼티는 뷰가 자신을 그릴 부분을 정의한 사각 영역이다.
각 뷰는 자신을 그릴 때 사용할 좌표계를 가지고 있다.
bounds 는 뷰가 소유한 좌표계 내의 사각 영역이다.
frame 은 상위뷰의 좌표계에 속한 bounds 와 동일한 사각 영역이다.
뷰의 frame 영역은 뷰 계층구조의 나머지 뷰들과 비례하여 해당 뷰의 레이어를 배치하기 위해 합성하는 중에 사용된다.
bounds 영역은 뷰의 레이어 경계 내에 세부 그림을 배치하기 위해 그리는 과정 중에 사용된다.
-
Xcode 메뉴에서 Help -> Documentation and API Reference 를 선택한다.
Shift + Command + O 단축키로도 가능하다.
-
개발자 문서는 SDK 가이드 / 샘플코드 를 제공한다.
-
developer.apple.com/library 페이지의 iOS Developer Library 에서 가이드와 샘플 코드를 볼 수 있다.
-
일반적으로 drawRect: 메소드는 이미지, 도형, 텍스트를 그리기 위해 각각 UIImage, UIBezierPath, NSString 인스턴스를 사용한다.
이 클래스들은 iOS 에서 드로잉을 간편하게 해준다.
하지만 프로그램 하부에서는 많은 일들이 벌어진다.
-
iOS 에서 이미지 그리기는 코어 그래픽스(Core Graphics) 프레임워크에서 담당한다.
JPEG 또는 PDF 파일로 저장할 이미지거나 UIView 를 나타낸 레이어든 상관없이 모두 코어 그래픽스에서 처리한다.
-
UIBezierPath 처럼 그리기에 사용되는 클래스들은 프로그래머가 그리기 쉽도록 코어 그래픽스 코드를 감싼 메소드를 제공한다.
이 클래스들이 동작하는 법과 이미지가 만들어지는 법을 정확히 이해하려면 코어 그래픽스가 어떻게 동작하는지 이해해야 한다.
-
코어 그래픽스는 C 언어로 작성된 2D 드로잉 API 이다.
그 자체에는 Objective-C 객체나 메소드가 없지만 C 구조체와 함수로 객체지향 기법을 흉내 냈다.
코어 그래픽스에서 가장 중요한 객체는 그래픽스 컨텍스트(graphics context) 이다.
그래픽 컨텍스트는 실제로 두 가지를 저장하고 있다.
펜의 색과 선 두께 등과 같은 드로잉 상태 정보와 그려질 메모리이다.
-
그래픽스 컨텍스트는 CGContextRef 의 인스턴스로 나타낸다.
UIView 인스턴스가 drawRect: 메시지를 받기 직전에 시스템은 그 뷰 레이어를 위해 CGContextRef 를 만든다.
레이어는 뷰와 동일한 bounds 와 드로잉 상태에 대한 기본값을 일부 가진다.
레이어 안의 픽셀들은 컨텍스트가 드로잉 명령을 받으면 변경된다.
drawRect: 메소드가 완료된 후 시스템은 레이어를 가지고 화면에 합성한다.
-
-
UIBezierPath 와 UIColor 를 통해 할 수 있는 것은 코어 그래픽스를 사용해서 직접 할 수 있다.
대게 Objective-C 클래스로 작업하는 편이 더 쉽다.
-
그라디언트(gradient)를 그리는 것처럼 코어 그래픽스를 통해서만 할 수 있는 것들이 있다.
프레임워크의 모든 타입들이 동일한 접두사를 가진다는 것을 알고 있기 때문에 필요한 것을 찾으려면 CG 로 시작하는 타입들의 문서를 검색하면 된다.
-
코어 그래픽스 타입 대다수에 Ref 가 왜 붙는지 궁금할 것이다.
모든 코어 그래픽스 타입은 구조체이지만 일부는 힙에 할당하여 객체의 동작을 흉내 낸다.
그러므로 이러한 코어 그래픽스 객체들 중 하나를 만들면 그 메모리 주소를 가진 포인터를 반환한다.
이러한 방식으로 할당된 코어 그래픽스 구조체는 각각 * 기호를 타입 그 자체에 통합하여 정의하고 있다.
CGColor 구조체가 존재하고 CGColorRef 타입 정의는 우리가 항상 사용하는 CGColor * 이다.
이러한 규약으로 코드를 대충 훑어보고도 그 변수가 Objective-C 객체나 객체인 척하는 C 구조체인지 아닌지 쉽게 확인할 수 있다.
-
코어 그래픽스에서 프로그래머들이 혼동하는 또 다른 점은 Ref 나 * 가지지 않은 CGRect 나 CGPoint 와 같은 타입이다.
이러한 타입들은 스택에 존재하는 작은 데이터 구조체이다.
따라서 그것에 대한 포인터를 전달할 필요가 없다.
-
일부 코어 그래픽스 타입들은 단순히 float 타입들을 가진 것보다 훨씬 더 많은 것을 가졌다.
실제로 다른 코어 그래픽스 객체들의 포인터들을 가진 경우도 있다.
이 객체들은 가리키고 있는 객체에 대해 강한 참조를 하지만 ARC 는 이 소유권을 따라갈 수 없다.
대신에 객체를 다 사용했을 때 반드시 수동으로 이 객체 타입의 소유권을 해제해야 한다.
만약 Create 나 Copy 라는 단어를 포함한 함수로 코어 그래픽스 객체를 만들었다면 그에 해당하는 Release 함수에 그 객체 포인터를 첫 번째 인자로 전달해 호출해야 한다.
-
마지막으로 알아야 할 것은 Mac 에도 코어 그래픽스가 존재한다는 것이다.
따라서 iOS 와 OS X 모두에서 작동하는 오픈소스 코어 프레임워크 등을 만들 수 있다.
-
그래픽 컨텍스트에 그림자를 설정하는 메소드의 선언은 다음과 같다.
그림자를 해제하는 함수는 없다.
따라서 그림자를 설정하기 전에 그래픽 상태를 저장해야 한다.
그래야 그림자를 설정한 뒤에 다시 복원할 수 있다.
CGContextSaveGState(currentContext);
CGContextSetShadow(currentContext, CGSizeMake(4,7), 3);
// 여기서 무언가 그리면 그림자와 함께 보일 것이다.
CGContextRestoreGState(currentContext);
// 여기서 무언가 그리면 그림자 없이 보일 것이다.
-
그라디언트는 다음과 같은 코드로 그릴 수 있다.
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[0] = { 1.0, 0.0, 0.0, 1.0, // 시작 색은 빨강
1.0, 1.0, 0.0, 1.0 }; // 끝 색은 노랑
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, 2);
CGPoint startPoint = ...;
CGPoint endPoint = ...;
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);
CGContextDrawLinearGradient() 의 마지막 인자는 시작점 이전과 끝점 이후를 어떻게 할지 결정한다.
만약 시작점 이전 공간을 시작색으로 채우고 싶다면 kCGGradientDrawsBeforeStartLocation 값을 사용하면 된다.
또한 끝점 이후 공간을 마지막 색으로 채우고 싶다면 kCGGradientDrawsAfterEndLocation 값을 사용하면 된다.
두 값을 다 사용하려면 비트연산을 하면 된다.
-
cf) hypot 은 Euclidian Distance 라 하여, width, height 를 넣어주면 대각선 길이를 구해주는 함수이다.
-
아래 코드를 통해 project 에 추가된 이미지를 불러와 화면에 그릴 수 있다.
UIImage* logoImage = [UIImage imageNamed:@"logo.png"];
[logoImage drawInRect:someRect];
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[iOS Study] 뷰 컨트롤러 (0) | 2016.02.18 |
---|---|
[iOS Study] 뷰 다시 그리기와 UIScrollView (0) | 2016.02.17 |
[iOS Study] ARC 를 통한 메모리 관리 (0) | 2016.02.15 |
[iOS Study] Objective-C (0) | 2016.02.13 |
[iOS Study] 간단한 iOS 앱 만들기 (0) | 2016.02.12 |
댓글