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

[iOS Study] 자동 회전, 팝오버 컨트롤러, 모달 뷰 컨트롤러

by 돼지왕 왕돼지 2016. 3. 5.
반응형

 [iOS Study] 자동 회전, 팝오버 컨트롤러, 모달 뷰 컨트롤러


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



-

이 장에서는 아래의 주제를 다룬다.

     장치 의존적인 코드를 작성하는 방법과 장치의 종류에 따라 테스트하는 방법

     회전, 팝오버 컨트롤러, 모달 뷰 컨트롤러



-

iOS 에서는 방향을 장치방향 (device orientation) 과 인터페이스 방향(interface orientation) 두 가지로 구분한다.



-

장치 방향은 장치 표면이나 후면에서 정방향, 뒤집힌 상태, 왼쪽 회전, 오른쪽 회전인지에 따른 물리적 방향을 나타낸다.

UIDevice  클래스의 orientation 프로퍼티를 통해 장치의 방향에 접근할 수 있다.



-

인터페이스 방향은 실행 중인 프로그램의 프로퍼티이다.


UIInterfaceOrientationPortrait     홈 버튼의 화면 아래쪽에 있다.

UIInterfaceOrientationPortraitUpsideDown     홈 버튼이 화면 위쪽에 있다.

UIInterfaceOrientationLandscapeLeft     장치가 옆으로 뉘여 있고 홈 버튼은 화면 오른쪽으로 향한다.

UIInterfaceOrientationLandscapeRight     장치가 옆으로 뉘여 있고 홈 버튼은 화면 왼쪽으로 향한다.



-

앱의 인터페이스 방향이 바뀌면 앱의 윈도우 크기 또한 바뀐다.

윈도우는 새로운 크기를 얻고 자신의 뷰 계층구조를 회전시킬 것이다.

계층구조상의 뷰들은 그들의 제약조건에 따라 다시 놓이게 된다.



-

장치 방향이 바뀌면 앱은 새로운 방향에 대해 알게 된다.

앱은 자신의 인터페이스 방향을 장치의 새 방향과 일치시키는 것을 허용할지 정할 수 있다.



-

Target 정보의 General 탭에서 Deployment Info 의 Device Orientation 섹션에서 지원할 방향을 설정할 수 있다.

기본으로는 Upside Down 외에는 체크(활성화)가 되어 있다.



-

아이패드 앱은 네 방향 모두로 회전할 수 있는 것이 일반적이다.

반면 아이폰 앱은 뒤집힌 상태를 제외한 나머지 방향으로만 회전할 수 있다.

iPhone/iPod Deployment Info 섹션을 통해 설정할 수 있다.


일부 앱들은 특정 방향에 대해 사용자가 접근할 수 없도록 잠그길 원한다.

예를 들어, 많은 게임들은 두 가지 가로 방향만 허용하고, 많은 아이폰 앱들은 세로 방향만 허용한다.

Device Orientation 버튼을 토글하여 자신의 앱에서 어떤 방향을 유효하게 할지 선택할 수 있다.



-

각 뷰 컨트롤러는 지원하는 모든 인터페이스 방향을 반환하는 메소드를 구현한다.

인터페이스 방향을 변경하기 위해서는 앱(각 info 프로퍼티 리스트의 Supported Interface Orientation 섹션마다)과 앱의 rootViewController 둘 다 세 방향에 반드시 동의해야 한다.



-

방향 허용을 바꾸고 싶다면 뷰 컨트롤러에서 아래 supportedInterfaceOrientations 를 재정의해주어야 한다.

아래는 supportedInterfaceOrientations 의 기본 구현.


- (NSUInteger) supportedInterfaceOrientations{

     if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad){

          return UIInterfaceOrientationMaskAll;

     } else{

          return UIInterfaceOrientationMaskAllButUpsideDown;

     }

}


UIUserInterfaceIdiomPhone 와 UIUserInterfaceIdiomPad 가 있다.


return 값에는 UIInterfaceOrientationMaskXxx 를 | operation 으로 묶어서 return 하면 된다.



-

UINavigationController 에서 회전 마스크를 결정하려면 UINavigationController 의 하위 클래스를 만들어 재정의해야 한다.


@implementation MyNavigationController

- (NSUInteger)supportedInterfaceOrientations{

     return self.topViewController.supportedInterfaceOrientations;

}

@end



-

UITabViewController 는 각 탭의 뷰 컨트롤러에게 지원 인터페이스 방향을 요청하고,

그 교집합을 반환한다.

즉, UITabViewController 는 모든 탭이 지원하는 방향만을 지원한다.



-

cf)

     Option + Click 은 보조 편집기를 연다.

     xib 파일에서 Control + Drag 를 하면 손쉽게 outlet 이나 action 을 만들 수 있다.



-

방향 변경이 되었을 때 어떤 Action 을 하려면, UIViewController 의 willAnimateRotationToInterfaceOrientation:duration: 메소드를 재정의해아 한다.

첫번째 인자가 새 인터페이스 방향 값이며,

인터페이스 방향이 성공적으로 변경되면 뷰 컨트롤러가 이 callback 을 받는다.


이 메소드에서 뷰의 뭔가를 변경하는 코드(frame  을 hidden 시킨다던지)를 작성하면

이러한 변경은 애니메이션화된다.

duration 인자는 그 애니메이션이 얼마나 지속될지를 알려준다.



cf) UIInterfaceOrientationIsLandscape( orientation ) 을 통해 orientation 이 landscape 인지 알 수 있다.

[UIApplication sharedApplication] statusBarOrientation]; 을 통해 현재 orientation 값을 얻어올 수 있다.



-

뷰에 대한 작업이 아닌 다른 작업을 하는 코드가 이곳에 들어간다면,

혹은 뷰의 애니메이션화를 원하지 않는다면

willRotateToInterfaceOrientation:duration: 메소드를 재정의하면 된다.


인자에 들어오는 값은 willAnimateRotationToInterfaceOrientation:duration: 와 동일하지만

자동 animation 화 하지 않는다.



-

회전이 끝난 후 뭔가를 하고 싶다면 didRotateFromInterfaceOrientation: 메소드를 재정의하면 된다.

전달되는 인자는 회전이 발생하기 전의 예전 인터페이스 방향.



-

callback 형태가 아닌 현재 방향을 가져오는 또 다른 방법은


UIInterfaceOrientation io = [[UIApplication sharedApplication] statusBarOrientation];

ViewController.interfaceOrientation



-

팝오버 컨트롤러(popover controller)는 앱의 다른 인터페이스 위에 경계선을 가진 윈도우를 띄워 또 다른 뷰 컨트롤러의 뷰를 표시한다.

이는 아이패드에서만 가능하다. ( 아이폰에서 만들려고 하면 예외를 던진다. )

UIPopoverController 를 만들 때 다른 뷰 컨트롤러를 이 팝오버 컨트롤러의 contentViewController 로 설정한다.

팝오버 컨트롤러는 화면에서 사용자에게 선택 목록을 제시할 때(사진 보관함에서 사진을 고르는 경우 등)나 요약된 것에 관한 추가적인 정보를 줄 때 유용한다.



-

UIPopoverController 를 사용하는 경우 UIPopoverControllerDelegate 를 사용하는 UIViewController 에 프로토콜 지정해준다.



-

아래의 코드로 UIPopoverController 를 launch 시킨다.


UIPopoverController *controller = [[UIPopoverController alloc] initWithContentViewController:viewController];

controller.delegate = self;

[controller presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];



-

PopoverController 가 떠 있을 때 해당 controller 바깥의 영역을 누르면 dismiss 되며

다음 delegate callback 을 부른다.  popoverControllerDidDismissPopover:

PopoverController 에 isPopoverVisible 을 통해 이미 pop over 가 떠 있는지 확인할 수도 있다.



-

팝오버 컨트롤러에 명시적으로 dismissPopoverAnimated: 메시지를 보내면 팝오버 컨트롤러는 popoverControllerDidDismissPopover: 메시지를 자신의 델리게이트에 보내지 않는다.



-

Modal ViewController 를 띄울 때, 네비게이션용으로 사용하지 않을지라도 UINavigationController 의 인스턴스를 만들면, 모든 뷰들의 상단에 있는 것과 같은 타이틀바를 뷰에 제공할 수 있다.



-

모달로 표시된 뷰 컨트롤러를 닫으려면 dismissViewControllerAnimated:completion: 메시지를 반드시 그 뷰 컨트롤러에 보내야 한다.



-

모든 UIViewController 는 자신을 띄운 뷰 컨트롤러를 가리키는 presentingViewController 프로퍼티를 가진다.

( 엄밀히 이야기하면 자신을 띄운 뷰 컨트롤러의 Container 를 가르킨다. )



-

model 은 기본적으로 아래서부터 animation 되어 나타난다.

pushViewController:animated: 가 아닌 presentViewController:animated:completion: 함수를 통해 호출한다.



-

아이폰이나 아이팟 터치에서 모달 뷰 컨트롤러는 화면 전체를 차지한다.



-

아이패드에는 모달뷰 컨트롤러에 대한 두 가지 추가 옵션이 있다.

폼 시트 스타일(UIModalPresentationFromSheet) 나 페이지 시트 스타일 (UIModalPresentationPageSheet) 이다.


modalPresentationStyle 프로퍼티에 assign 하면 된다.



-

폼 시트 스타일은 아이패드 화면 중앙의 사각 영역에서 모달 뷰 컨트롤러를 보여주고, 기존 뷰 컨트롤러의 뷰는 어둡게 한다.



-

페이지 시트 스타일은 세로 모드에서 기본 풀 스크린 스타일과 동일하다.

반면에 가로 모드에서는 세로 모드와 같은 너비를 유지하고 그 뒤에 표시된 뷰 컨트롤러의 뷰의 왼쪽과 오른족 여백을 어둡게 한다.



-

modal 이 전체화면을 덮지 않으면, 호출한 viewController 의 viewWillAppear: 와 viewDidAppear: 함수는 불리지 않는다.



-

@property (nonatomic, copy) void (^dismissBlock)(void);


위의 코드를 통해 재사용 가능한 block 을 정의할 수 있다.



-

block 은 ^{ ... } 의 형태로 정의된다.



-

모달 뷰 컨트롤러의 화면에 나타나는 애니메이션도 변경할 수 있다.

뷰 컨트롤러 프로퍼티 modalTransitionStyle 에 미리 정의된 상수를 지정해주면 된다.

기본 애니메이션은 모달 뷰 컨트롤러를 화면 아래에서부터 위로 올라오도록 한다.

뷰 컨트롤러에 fade in, flip in, page curl 등의 효과를 줄 수 있다.


아래는 상수이다.


UIModalTransitionStyleCoverVertical

UIModalTransitionStyleCrossDissolve

UIModalTransitionStyleFlipHorizontal

UIModalTransitionStylePartialCurl



-

MultiThread 환경에서 안전한 singleton 을 만들기 위해서는..


static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

     // singleton initialization

});



-

뷰 컨트롤러들 간의 관계는 뷰 컨트롤러의 뷰가 화면 어디에 어떻게 나타나는지 이해하는데 중요하다.

전체적으로 뷰 컨트롤러들 간의 관계는 두 종류가 있다.

부모-자식 관계와 프리젠팅-프레젠터 관계이다.



-

부모-자식 관계는 뷰 컨트롤러 컨테이너를 사용할 때 형성된다.

뷰 컨트롤러 컨테이너의 예로 UINavigationController, UITabBarController, UISplitViewController 가 있다.

뷰 컨트롤러 컨테이너는 자신이 포함한 뷰 컨트롤러들의 배열인 viewControllers 프로퍼티를 가지고 있다.



-

뷰 컨트롤러 컨테이너는 항상 UIViewController 의 하위 클래스이다.

따라서 뷰를 가진다.

뷰 컨트롤러 컨테이너는 viewControllers 의 뷰들을 선별적으로 자신이 소유한 뷰의 하위뷰로 추가하는 동작을 한다.

컨테이너는 내장된 인터페이스도 소유한다.

예를 들어, UINavigationController 의 view 는 내비게이션바와 topViewController 뷰를 보여준다.



-


부모-자식 관계의 뷰 컨트롤러들은 집단(family)을 형성한다.

그리고 집단은 여러 단계를 가질 수 있다.

예를 들어, UITabBarController 가 UIViewController 를 가진 UINavigationController 를 포함할 수 있다.

이들 세 뷰 컨트롤러는 같은 집단이다.



-

컨테이너 클래스는 그들의 viewControllers 배열을 통해 그 자식들에게 접근하고

자식들은 UIViewController 의 네 개의 프로퍼티를 통해 그 조상들에게 접근한다.

parentViewController 는 해당 집단에서 가장 가까운 조상 뷰 컨트롤러를 가르킨다.

집단 트리의 구성에 따라 UINavigationController, UITabBarController 또는 UISplitViewController 를 반환할 수 있다.



-

navigationController, tabBarController, splitViewController 메소드를 통해 조상에 접근할 수 있다.

뷰 컨트롤러가 이들 메시지 중 하나를 받으면 적절한 타입의 뷰 컨트롤러 컨테이너를 찾을 때까지 집단 트리를 검색한다. ( parentViewController 이용 )

적절한 타입의 조상이 없으면 nil 을 return 한다.



-

뷰 컨트롤러가 모달로 나타날 때는 부모-자식 관계가 아닌 표시자-피표시자의 관계가 된다.

뷰 컨트롤러가 모달로 표시될 때 표시된 뷰 컨트롤러의 view 는 그것을 표시한 뷰 컨트롤러의 view 맨 위에 추가된다.

이것은 뷰 컨트롤러 컨테이너와는 다르다.

모든 UIViewController 는 다른 뷰 컨트롤러를 모달로 나타낼 수 있다.



-

표시자와 피표시자 간의 관계를 관리하는 두 개의 내부 프로퍼티가 있다.

모달로 나타낸 뷰 컨트롤러의 presentingViewController 는 표시자 뷰 컨트롤러를 가리키고,

표시자의 presentedViewController 는 피표시자 뷰 컨트롤러를 가르킨다.



-

표시된 뷰 컨트롤러와 표시자는 같은 뷰 컨트롤러 집단이 아니다.

대신에 표시된 뷰 컨트롤러는 자기 소유의 집단을 가진다.

때때로 이 집단은 하나의 UIViewController 로 이뤄지고, 그렇지 않으면 여러 뷰 컨트롤러로 이뤄진다.



-

부모-자식 관계의 프로퍼티들은 절대 집단 경계를 넘어갈 수 없다.



-

뷰 컨트롤러가 모달로 나타나면 실제 표시자는 표시자 집단의 가장 오래된 멤버이다.

presentViewController:animated:completion: 를 부른 ViewController 가 반드시 표시자가 되지는 않는다.

이 관계가 UINavigationController 의 스택에서 일반적으로 나타날 때가 아닌 모달로 나타날 때 UINavigationBar 를 가리는 이유를 설명한다.



-

presentingViewController 와 presentedViewController 는 각 집단의 모든 뷰 컨트롤러에 유효하고 항상 상대 집단의 선조를 가리킨다.


실제로 이러한 선조를 가리키는 동작은 "아이패드에서만" 재정의할 수 있다.

이렇게 해서 화면에 나타낼 뷰 컨트롤러 집단의 뷰들을 어디에서 나타낼지 지정할 수 있다.


예를 들어 UINavigationBar 가 아닌 topViewController 부분만 가리면서 modal viewController 를 띄울 수 있다.



-

모든 UIViewController 는 definesPresentationContext 프로퍼티를 가진다.

기본적으로 이 프로퍼티는 NO 이다.

즉, 뷰 컨트롤러는 항상 남은 조상이 없을 때까지 다음 조상에게 계속 프레젠테이션을 전달한다.


이 프로퍼티가 YES 로 설정되면 선조를 검색하는 것을 중단하고

뷰 컨트롤러에 자신이 소유한 뷰에서 모달 뷰 컨트롤러를 표시하도록 허용한다.

추가로, 표시될 뷰 컨트롤러를 위해 modalPresentationStyle 을 UIModalPresentationCurrentContext 로 반드시 설정해야 한다.








반응형

댓글