본문 바로가기
프로그래밍 놀이터/iOS

[iOS Study] 뷰 컨트롤러

by 돼지왕 왕돼지 2016. 2. 18.
반응형


 [iOS Study] 뷰 컨트롤러


출처 : 아론 힐리가스의 iOS 프로그래밍


1회, 2X, @2x, alloc init, Anti-aliasing, application:didFinishLaunchingWithOptions, application:didFinishLaunchingWithOptions:, averaging, back-end, background, custom class, DatePicker, error, example code, file's owner, Foreground, identity inspector, IMAGE, imageNamed:, Images.xcassets, initwithnibname, initWithNibName:bundle:, instancetype, Interface builder, IOS, iOS 프로그래밍, key value coding, key-value coding, KVC, lazy loading, loadView, local notification, mainbundle, name convention, nib, nib 파일, nib 파일명, nil, nsbundle, nsobject, NSString, outlet, outlet weak, override, property, Push Notification, rootviewcontroller, Sample Code, setRootViewController, setValue:forkey:, setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key KEY., setvalueforkey, Study, TAB, tabbaritem, Title, tutorial, uitabbarcontroller, UIView, UIViewController, uiviewcontroller tutorial, valueForKey, Vector, View, view hierarchy, viewcontroller, viewcontrollers, viewdidappear, viewdidappear:, viewdiddisappear, viewdiddisappear:, viewDidLoad, viewwillappear, viewwillappear:, viewwilldisappear, viewwilldisapper, viewwilldisapper:, weak, XIB, [iOS Study] 뷰 컨트롤러, [UIViewController _loadViewFromNibNamed:bundle:] loaded the “ViewController” nib but the view outlet was not set., 객체, 격자, 관리, 디렉터리, 로컬 노티피케이션, 루트 뷰 컨트롤러, 리소스, 메모리, 메인 앱 번들, 배열, 백그라운드, 백엔드, 번들, 번들명, 범용 게터 메소드, 범용 세터 메소드, 뷰 계층, 뷰 계층구조, 뷰 컨트롤러, 비레티나, 샘플 코드, 성능, 시스템 메모리, 식별 인스펙터, 실행 파일, 아울렛, 아웃렛, 아카이브, 안티 앨리어싱, 알림, 앱, 앱 재시작, 에러 메시지, 연결 구멍, 예외, 예제 코드, 윈도우, 이름 규칙, 이미지, 인스턴스, 인스턴스화, 인터페이스 빌더, 장비, 재부팅, 재정의, 절약, 제목, 지연 로딩, 지정 초기화 메소드, 차이점, 초기화, 최적화, 추가, 커넥션, 코어 그래픽스 함수, 클래스 이름, 탭, 파일 시스템, 평균화, 포그라운드, 푸시 노티피케이션, 프로그래밍, 프로퍼티, 플레이스홀더, 하위 클래스, 하위뷰, 향상, 흐릿, 힐리가스


-

뷰 컨트롤러는 UIViewController 의 하위 클래스 인스턴스이다.

뷰 컨트롤러는 뷰 계층구조를 관리한다.

뷰 컨트롤러는 계층구조를 구성하는 뷰 객체들을 만들고 뷰 객체와 관련된 이벤트를 제어하고, 윈도우에 뷰 계층구조를 추가하는 역할을 한다.



-

UIViewController 의 하위 클래스는 중요한 프로퍼티를 하나 상속받는다.


@property (nonatomic, strong) UIView *view;


이 프로퍼티는 UIView 인스턴스를 가리킨다.

뷰 컨트롤러의 View 가 윈도우의 하위뷰로 추가되면, 뷰 컨트롤러의 뷰계층 전부가 윈도우에 추가된다.



-

뷰 컨트롤러의 view 는 화면에 보여줄 필요가 있을 때까지 만들어지지 않는다.

이러한 최적화를 지연 로딩(lazy loading) 이라 하고 종종 메모리를 절약하면서 성능을 향상시킬 수 있다.



-

뷰 컨트롤러가 뷰 계층구조를 만들 수 있는 두 가지 방법이 있다.


1. 프로그래밍으로 UIViewController 의 loadView 메소드를 재정의한다.

2. 인터페이스 빌더를 통해 NIB 파일을 읽어들이도록 한다. ( 프로그램에서 읽어들이는 파일이 NIB 파일이고,  XIB 파일은 인터페이스 빌더에서 편집할 수 있는 파일이라는 것을 상기한다. )



-

뷰 컨트롤러가 만들어질 때 그 컨트롤러의 view 프로퍼티는 nil 이다.

뷰 컨트롤러는 view 를 요청하여 그 view 가 nil 이면 뷰 컨트롤러는 loadView 메시지를 받는다.



-

뷰 컨트롤러의 뷰 계층구조를 윈도우에 추가하는 편리한 메소드가 있다.

UIWindow 의 setRootViewController: 메소드이다.

뷰 컨트롤러를 rootViewController 로 설정하면 뷰 컨트롤러의 view 가 윈도우의 하위뷰로 추가된다.

또한 자동으로 뷰의 크기를 윈도우와 동일하게 조절한다.



-

뷰 컨트롤러가 NIB 파일을 불러와 뷰 계층구조를 갖게 되면 loadView 를 재정의하지 않는다.

loadView 의 기본 구현은 NIB 파일을 불러와 처리하는 법을 알고 있다.


ViewController 는 불러올 NIB 파일이 어디에 있는지 알아야 한다.

UIViewController 의 지정 초기화 메소드에서 이를 처리할 수 있다.


-(instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBunble;


이 메소드에 불러올 NIB 파일명과 NIB 파일을 검색할 번들명을 전달한다.



-

mainBundle 메시지를 보내서 얻게 되는 번들은 앱 번들이다.

이 번들은 앱의 실행파일과 그 앱이 사용할 리소스(NIB 파일 등)을 포함한 파일시스템상의 디렉터리이다.

ViewController.xib 파일이 이 안에 있다.



-

-[UIViewController _loadViewFromNibNamed:bundle:] loaded the “ViewController” nib but the view outlet was not set.


위와 같은 에러 메시지에서는 실행 중인 앱에서 ViewController 와 인스턴스화된 객체들 간을 연결하지 않아서 나오는 에러이다.

뷰 컨트롤러의 view 프로퍼티도 포함된다.

뷰 컨트롤러가 화면에 추가할 view 를 얻고자 할 때 view 프로퍼티가 nil 이기 떄문에 예외를 던지는 것이다.







-

File’s Owner 는 플레이스홀더 객체이다.

이는 XIB 파일에서 의도적으로 둔 연결 구멍이다.

NIB 을 로딩하는 과정은 두 부분으로 나뉜다.

XIB 에 아카이브된 모든 객체를 인스턴스화하고 나서 NIB 을 로딩 중인 그 객체를 File’s Owner 구멍에 놓는다.

그리고 준비한 커넥션들을 설정한다.


실행 중에 NIB 을 로딩하는 객체에 연결하고자 한다면 XIB 에서 작업할 때 File’s Owner 를 연결해야 한다.

첫 단계는 XIB 파일에 ViewController 의 의 인스턴스가 File’s Owner 가 될 것이라고 알리는 것이다.



-

식별 인스펙터(identity inspector) 의 Custom Class 의 Class 부분에

ViewController 를 연결해주어야 한다.



-

view 에서부터 드래그하여 캔버스 위의 UIView 객체도 연결해주어야 한다.



-

datePicker 아웃렛을 weak 로 선언했다.

아웃렛을 weak 로 선언하는 것은 예전 iOS 버전부터 내려온 관례이다.

이들 버전에서 뷰 컨트롤러의 view 는 시스템 메모리가 부족한 경우 자동으로 소멸됐다.

그리고 나중에 필요한 경우 다시 생성됐다.

뷰 컨트롤러가 하위뷰들에 대해 단지 약한 소유권만을 가지는 것은 뷰 컨트롤러의 view 가 소멸될 때 모든 하위뷰들이 소멸되어 메모리 누수를 피한다는 것을 의미한다.



-

UITabBarController 는 뷰 컨트롤러들의 배열을 유지한다.

또한 이 배열의 각 뷰 컨트롤러용 탭(tab) 을 가진 화면 하단의 탭바(tab bar)를 관리한다.


UITabBarController* tabBarController = [[UITabBarController alloc] init];

tabBarController.viewControllers = @[hvc, rvc];



-

UITabBarController 는 UIViewController 의 하위 클래스이다.



-

탭바의 각 탭은 제목과 이미지를 표시할 수 있다.

각각의 뷰 컨트롤러는 이러한 용도로 tabBarItem 프로퍼티를 관리한다.

뷰 컨트롤러가 UITabBarController 에 포함되면 해당 탭과 항목이 탭바에 나타난다.


// self 는 view controller

self.tabBarItem.title = @"title";

self.tabBarItem.image = [UIImage imageNamed:@"TabImage.png"];



-

Images.xcassets 의 좌측 이미셋 목록에 이미지를 드래그하여 리소스를 추가할 수 있다.



-

UIViewController 의 init 메소드를 호출해도 되는 이유는

지정 초기화 메소드가 initWithNibName:bundle: 이기 때문이다.


뷰 컨트롤러에 init 메시지를 보내면 두 인자를 nil i로 initWithNibName:bundle: 메소드를 호출한다.



-

NIB 파일을 사용하는 뷰 컨트롤러에 init 메시지를 보내면 어떻게 될까?

이전과 동일하게 동작할 것이다.

뷰 컨트롤러가 초기화될 때 NIB 이름이 nil 이면 클래스의 이름으로 NIB 파일을 찾는다.

그리고 번들에 nil 을 전달하면 뷰 컨트롤러는 메인 앱 번들에서 찾는다.



-

로컬 노티피케이션(local notification) 은 현재 랩이 실행 중이 아니더라도 앱에서 사용자에게 알림을 보내는 방법이다.

( 앱은 백엔드 서버를 사용하여 구현된 푸시 노티피케이션(push notification)을 사용할 수 있다.)



-

알림을 보기 위해서는 앱이 포그라운드 상태가 아니어야 한다.



-

뷰 컨트롤러가 뷰의 로딩 작업을 완료할 때 viewDidLoad 메시지를 받는다.



-

지연 로딩의 이점을 유지하려면 initWithNibName:bundle: 에서 뷰 컨트롤러의 view 프로퍼티에 절대 접근해서는 안 된다.

초기화 메소드에서 view 를 요청하면 뷰 컨트롤러가 너무 이르게 view 를 불러오기 때문이다.



-

사용자에게 화면을 표시하기 전에, XIB 파일에 정의된 하위뷰들에 추가적인 초기화가 필요한 경우가 종종 있다.

하지만 뷰 컨트롤러의 초기화 메소드에서는 NIB 파일이 아직 로드되지 않았기 때문에 이러한 추가 작업을 할 수 없다.

만약 초기화 메소드에서 뷰 컨트롤러에 선언된 어떤 포인터를 사용한다면 결국 그 포인터는 nil 인 하위뷰들을 참조할 것이다.



-

하위뷰 접근은 필요에 따라 두 가지 방법이 있다.

첫 번째 방법은 지연 로딩을 설정하기 위해 재정의한 viewDidLoad 메소드를 사용하는 것이다.

뷰 컨트롤러는 NIB 파일이 로딩된 후 이 메시지를 받는다.

이 때 뷰 컨트롤러의 모든 포인터들은 유효한 객체를 가리키고 있다.


두 번째 방법은 UIViewController 의 또 다른 메소드 viewWillAppear: 를 사용하는 것이다.

뷰 컨트롤러는 view 가 윈도우에 추가되기 직전에 이 메시지를 받는다.


차이점은 viewDidLoad 는 앱을 실행할 때 한 번 설정해야 하는 경우에 재정의하고

viewWillAppear: 는 화면에 뷰 컨트롤러가 나타날 때마다 설정이 필요한 경우 재정의한다는 점이다.



-

때때로 뷰 컨트롤러의 view 는 소멸되고 다시 로드될 수 있다.

하지만 이는 최신 장비에서는 일반적인 방식이 아니다.



-

viewWillAppear: 메소드의 animated 플래그는 뷰가 나타나거나 사라질 때 애니메이션 효과를 적용할지 여부를 가리킨다.

UITabBarController 의 경우에는 전환시에 애니메이션을 사용하지 않는다.







-

뷰 컨트롤러와 뷰의 생존 주기 동안 호출되는 메소드들은 다음과 같다.


application:didFinishLaunchingWithOptions: 는 랩의 루트 뷰 컨트롤러를 인스턴스화하고 설정했던 곳이다.

이 메소드는 앱이 시작될 때 한 번만 호출된다.

다른 앱으로 이동했다가 돌아와도 이 메소드는 다시 불리지 않는다.

폰을 재부팅하거나 앱을 다시 시작해야만 application:didFinishLaunchingWithOptions: 가 다시 호출된다.


initWithNibName:bundle: 은 UIViewController 의 지정 초기화 메소드이다.

뷰 컨트롤러 인스턴스가 생성되면 initWithNibName:bundle: 메소드가 한 번 호출된다.

동일한 뷰 컨트롤러 클래스의 인스턴스를 여러 개 만들 수도 있다.

이 메소드는 인스턴스가 각각 만들어질 때마다 호출된다.


loadView: 는 뷰 컨트롤러의 뷰를 프로그래밍으로 만드는 경우에 재정의한다.


viewDidLoad 는 NIB 파일을 불러와 뷰들을 구성하는 경우에 재정의할 수 있다.

이 메소드는 뷰 컨트롤러의 뷰가 만들어진 후 호출된다.


viewWillAppear:  는 NIB 파일을 불러와 뷰들을 구성하는 경우에 재정의할 수 있다.

이 메소드와 viewDidAppear: 메소드는 뷰 컨트롤러가 화면에 나타날 때마다 호출된다.

반대로 viewWillDisappear: 와 viewDidDisappear: 는 뷰 컨트롤러가 화면에서 사라질 때마다 호출된다.



-

NIB 파일을 불러올 때 키 값 코딩(key-value coding, KVC) 이라는 기법을 사용하여 아웃렛들이 설정된다.

키-값 코딩은 NSObject 에 정의되고, 이름으로 프로퍼티의 값을 가져오거나 설정하는 메소드들의 집합이다.


-(id)valueForKey:(NSString *)k;

-(void)setValue:(id)v forKey:(NSString *)k;


valueForKey:는 범용 세터 메소드이다.

다음과 같이 어떤 객체의 fido 프로퍼티의 값을 요청할 수 있다.


id currentFido = [selectedObj valueForKey:@“fido”];


이 경우 fido 메소드(fido 게터)가 있다면, 그 메소드가 호출되고 그 반환값을 이용한다.

fido 메소드가 없다면, 시스템은 _fido나 fido 라는 이름의 인스턴스 변수를 찾을 것이다.

인스턴스 변수가 존재하면 그 값을 이용하고, 접근자나 인스턴스 변수가 없다면 예외를 던진다.


setValue:forKey: 는 범용 세터 메소드이다. 다음과 같이 객체의 fido 프로퍼티의 값을 설정한다.


[selectedObject setValue:userChoice forKey:@“fido”];


setFido: 메소드가 있다면, 그 메소드가 호출된다.

만약 그 메소드가 없다면, 시스템은 _fido 나 fido 라는 이름의 변수를 찾아 그 값을 직접 대입한다.

접근자나 인스턴스 변수가 없다면 예외를 던진다.


NIB 파일이 로드될 때 아웃렛은 setValue:forKey: 를 사용하여 설정된다.


따라서 제대로 매핑이 안 되면 아래와 같은 에러가 날 수 있다.


[<ViewController 0x1424234> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key KEY.



-

이 절에서 가장 중요한 교훈은 코드를 읽게 될 다른 사람들을 위해 멋진 이름을 짓는 것보다 접근자 메소드 이름 규칙을 사용하는 것이 더 중요하다는 것이다.



-

코어 그래픽스 함수를 사용했다면 그래픽 요소들이 장비마다 다르게 보일 수 있다.

( 선, 곡선 등은 vector 이고, image 는 raster 이다. )

이미지가 장비 화면 종류에 따라 제작되어 있지 않으면 비트맵 이미지(JPEG 이나 PNG 파일 등)는 그리 매력적이지 않다.

안티 엘리어싱(anti-aliasing)이 적용되며 이미지는 격자가 생기는 대신 흐릿흐릿해진다.


대신 큰 이미지를 사용할 수도 있지만 평균화(averaging)로 인해 비레티나 디스플레이에서 이미지가 줄어들 때 다른 문제가 생길 수 있다.

유일한 해법은 앱에 두 이미지 파일을 포함하는 것이다.

비레티나 디스플레이의 화면 포인트 수와 동일한 픽셀을 가진 이미지와 레티나 디스플레이의 포인트 수의 각각 두 배 크기의 픽셀을 가진 이미지가 필요하다.


다행히도 어느 이미지를 어느 장비에서 불러야 하는지 제어하는 코드를 작성할 필요가 없다.

모든 것은 고해상도 이미지 파일 이름에 @2x를 붙이면 해결된다.

그리고 나서 UIImage 의 imageNamed: 메소드를 사용하여 이미지를 불러오면 된다.

이 메소드는 번들을 검색해서 각 장비에 맞게 적절한 이미지 파일을 가져온다.



cf) datePicker.minimumDate = [NSDate dateWithTimeIntervalSInceNow:60]; // 현재로부터 60초 후의 시간만 정의할 수 있도록 한다.







반응형

댓글