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

자동회전과 자동크기 조절

by 백룡화검 2011. 5. 20.

* 세로보기 모드 (portrait - 길고 얇은) / 가로보기 모드 (landscape - 짧고 옆으로 넓은)

 

* 세로보기 모드에서 뷰는 너비 320 px / 높이 460 px (상태 표시줄이 없다면 높이는 480 px)

 

* 가로보기 모드에서 너비는 480 px / 높이 300 px (상태 표시줄이 없다면 높이는 320 px)

 

* 애플리케이션에서 회전 기능을 관리하는 3가지 기능

- autoSize 속성으로 회전 처리 하기

- 회전할 때 뷰 재구성하기

- 뷰 전환하기 ( 세로보기 모드 / 가로보기 모드 )

 

* autoSize 속성으로 회전 처리하기

- 아이폰에서 AutoSize 속서을 사용하면 중력센서를 사용하여 기기 회전에 따른 처리를 할 수 있다.

  클래스명 ViewController.m 에서 shouldAutorotateToInterfaceOrientation 이라는 메소드를 주석을 풀고

  오버라이딩 해주면 된다. 해당 메소드에는 이미 기본적인 처리방법을 제공하고 있다.

 

- 시스템은 특정방향으로 회전해도 좋은지를 이 메소드를 사용해서 뷰컨트롤에게 물어본다.

  아이폰을 들고 있는 방향에 따라 일반적으로 4가지 방식으로 응답할 수 있는데,

  그에 맞게 방향 또한 4가지로 정의되어 있다.

 

UIInterfaceOrientationPortrait

UIInterfaceOrientationPortraitUpsideDown

UIInterfaceOrientationLandscapeLeft

UIInterfaceOrientatonLandscapeRight

 

- 아이폰 방향이 새방향으로 바뀌면 현재 뷰 컨트롤러의 이 메소드를 호출한다.

  interfaceOrientation 매개변수는 위의 목록 4가지 중 1개의 값을 갖는다.

  이 메소드는 새 방향에 맞게 애플리케이션 윈도우가 회전해야 하는 지를 알려주기 위해 YES or NO 를 리턴한다.

  모든 뷰 컨트롤러의 하위 클래스가 이 방식을 각기 다르게 구현하기 때문에 한 애플리케이션에서 몇 개의 뷰에서

  자동회전을 지원할 수는 있지만 모든 뷰에 적용 할 수는 없다.

 

- 이 메소드의 기본구현은 interfaceOrientation 을 바라보게 되고 기본적으로 UIInterfaceOrientationPortrait 방향에서만

  YES를 리턴하게 된다.

  이러한 방식으로 애플리케이션 방향을 제한하고 나머지 방향에서는 회전을 사용하지 않는다.

  모든 방향에서 회전을 허용하려면 어떤 값이 전달되든지 YES 를 리턴하도록 메소드를 변경하면 된다.

 

예제) 모든방향에서 회전 허용

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{

            return YES;

}

 

- 모든 방향을 지원하지 않고 특정 방향만 지원하고 싶다면 interfaceOrientation 의 값을 조사해서 지원하고 싶은

  방향에서만 YES를 아니라면 NO를 리턴하면 된다.

  예를 들어 가로보기 모두를 지원하고 세로보기 거꾸로 뒤집기를 지원하고 싶지 않다면 다음과 같이 구현한다.

 

예제) 가로보기 모두 지원, 세로보기 거꾸로 뒤집기는 미지원

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{

     return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

}

 

연습) 회전할때 뷰를 재구성 해보도록하자

 

  View-based Application 으로 새로운 프로젝트를 시작한다.

  프로젝트 명은 Autosize로 하고 프로젝트를 시작하게 되면 기본적인 세팅이 완료되어있다.

  이제 resource 를 클릭하여 AutosizeViewController.xib 을 인터페이스 빌더로 실행한다.

 

 

 

위와 같이 화면 구성을 만들어 둔다.

 

 

 

기본적인 사이즈는 다음과 같다. 가로 125, 세로 125 로 적당한 위치에 가이드에 맞춰서 배치해준다.

 

AutosizeViewController.h

 

#import <UIKit/UIKit.h>

 

@interface AutosizeViewController : UIViewController {

  UIButton *button1;

  UIButton *button2;

  UIButton *button3;

  UIButton *button4;

  UIButton *button5;

  UIButton *button6;

}

@property (nonatomic, retain) IBOutlet UIButton *button1;
@property (nonatomic, retain) IBOutlet UIButton *button2;
@property (nonatomic, retain) IBOutlet UIButton *button3;
@property (nonatomic, retain) IBOutlet UIButton *button4;
@property (nonatomic, retain) IBOutlet UIButton *button5;
@property (nonatomic, retain) IBOutlet UIButton *button6;

 

@end

 

헤더파일에서 UIButton 을 선언해 준 다음에 각각의 아웃렛을 설정해 준다. 이는 인터페이스 빌더에서 각각의 버튼에 연결될 것 이다.

 

AutosizeViewController.m

 

#import "AutosizeViewController.h"

 

@implementation AutosizeViewController

 

@synthesize button1;
@synthesize button2;
@synthesize button3;
@synthesize button4;
@synthesize button5;
@synthesize button6;

 

-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)tointerfaeOrientation duration:(NSTimeInterval)duration{

if(toInterfaceOrientation == UIInterfaceOrientationPortrait || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown){

 
button1.frame = CGRectMake(20, 20, 125, 125);
button2.frame = CGRectMake(175, 20, 125, 125);
button3.frame = CGRectMake(20, 168, 125, 125);
button4.frame = CGRectMake(175, 168, 125, 125);
button5.frame = CGRectMake(20, 315, 125, 125);
button6.frame = CGRectMake(175, 315, 125, 125);
}else {
button1.frame = CGRectMake(20, 20, 125, 125);
button2.frame = CGRectMake(20, 155, 125, 125);
button3.frame = CGRectMake(177, 20, 125, 125);
button4.frame = CGRectMake(177, 155, 125, 125);
button5.frame = CGRectMake(328, 20, 125, 125);
button6.frame = CGRectMake(328, 155, 125, 125)

}

 

}

 

-(BOOL)shouldAutototateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{

//Return YES for supported orientations

 return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

}

 

-(void)didReceiveMemoryWarning{

[super didReceiveMemoryWarning];

}

 

-(void)viewDidUnload{

//Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

self.button1 = nil;
self.button2 = nil;
self.button3 = nil;
self.button4 = nil;
self.button5 = nil;
self.button6 = nil;

[super viewDidUnload];

}

 

- (void)dealloc {
[button1 release];
[button2 release];
[button3 release];
[button4 release];
[button5 release];
[button6 release];
    [super dealloc];
}

@end

 

실제적인 구현부로 버튼들에 대한 synthesize 가 버튼 6개 모두 할당 되었다.

회전 시에 버튼을 이동시키려면 willAnimateSecondHalfOfRotationFrominterfaceOrientation:duration 메소드를 재정의 해야 한다.

이 메소드는 회전이 일어나면 마지막 애니메이션 동작 전에 호출된다.

버튼과 같은 모든 컨트롤들을 포함해서, 모든 뷰의 크기와 위치가 CGRect 타입의 구조체인 frame 이라는 속성에 명시되어 있다.

CGRectmake 는 CGRect 를 쉽게 만들기 위한 애플이 제공하는 함수로서 x, y, 넓이, 높이를 명시하면 CGRect 구조체를 만들어 준다.

CG는 코어 그래픽스 프레임 워크라는 이름에서 따온 CG 이다.

이름에서 유추할 수 있듯이 코어 그래픽스 프레임워크는 그래픽스와 드로잉에 관련된 코드들을 제공한다.

 

이제 인터페이스 빌더에서 각각의 아웃렛을 연결해 준다.

 

 

 

 

 

해당하는 버튼의 아웃렛으로 연결하여 주면 모든것이 끝나게 된다. 이제 시뮬레이터로 실행해보면 애니메이션 되는 것을 볼 수 있다.

 

 

 

 

 

 

 

시뮬레이터를 회전하는 방법은 Command + 좌우 방향키를 사용하거나

시뮬레이터 메뉴에서 Hardware 메뉴의 Rotate Left 또는 Rotate Right로 할 수 있다.

 

 

 

 

* 회전할 때 뷰 재구성하기

- 이 방법은 매우 복잡한 인터페이스를 사용하는 경우에 어울리는 방법이다.

- 가로보기와 세로보기 뷰를 따로 설계하고 하드웨어가 회전햇을 경우 뷰를 전환하게 하는 것이다.

- 양족 뷰의 컨트롤 들에게 동일한 액션을 설계해 주어야 하며, 이렇게 될 경우 중복 코드들이 생기게 된다.

 

예제를 통해 시험해보자

 

일단 View-Based 프로젝트를 Swap 이라는 이름으로 생성한다.

이 애플리케이션에서 만들려는 인터페이스는 기술의 복잡성을 제대로 파악할 수 있을만큼 복잡하진 않다.

과정을 분명히 하기 위해서 매우 간단한 인터페이스를 사용할 것이다.

이 애플리케이션은 시작할 때 세로보기모드로 동작한다.

두 개의 버튼이 있는데 Foo, Bar 버튼이다. 각 버튼을 탭하게 되면 해당 버튼은 사라지게 된다.

폰을 회전시키면 가로보기 모드방향에서 완전히 다른 뷰로 전환된다.

같은 이름을 가진 2개의 버튼이 있기 때문에 사용자는 서로 다른 두 종류의 뷰를 볼 수 있다는 사실을 알아차리지 못한다.

2개의 버튼도 탭하면 사라지게 된다.

각 뷰에 대해 모두 2개씩의 버튼을 만들어야 한다. 또한 아웃렛이 하나 이상의 객체를 가리킬 수 없기 때문에

총 4개의 아웃렛을 별도로 정의해야 한다.

버튼에 쓸 아웃렛 외에도, 다른 두 가지 버전의 뷰를 가리키는 데 필요한 아웃렛이 두 개 더 필요하다.

뷰를 하나만 사용하는 경우라면, 부모 클래스의 뷰 속성만 있으면 된다.

하지만 런타임에 뷰의 값을 변경해야 하기 때문에, 두가지 뷰를 모두 얻을 수 있는 방법이 확실히 있어야 한다.

이런 이유로, 두개의 UIView 아웃렛이 필요한 것이다.

버튼을 누르면 특정한 액션을 수행해야 한다.

따라서 버튼에는 적어도 하나의 액션 매서드가 필요하다.

어떤 버튼이 눌리든지 그 동작을 모두 처리할 액션 매서드를 하나 설계해 보겠다.

 

SwapViewController.h

 

#import <UIKit/UIKit.h>

#define degressToradians(x)(M_PI*(x)/180.0)  //정의

 

@interface SwapViewController : UIViewController {

UIView *landscape;

UIView *portrait;

UIButton *landscapeFooButton;

UIButton *portraitFooButton;

UIButton *landscapeBarButton;

UIButton *portraitBarButton;

}

 

@property(nonatomic, retain) IBOutlet UIView *landscape;

@property(nonatomic, retain) IBOutlet UIView *portrait;

@property(nonatomic, retain) IBOutlet UIButton *landscapeFooButton;

@property (nonatomic, retain) IBOutlet UIButton *portraitFooButton;
@property (nonatomic, retain) IBOutlet UIButton *landscapeBarButton;
@property (nonatomic, retain) IBOutlet UIButton *portraitBarButton;

 

-(IBAction) buttonPressed:(id)sender;

 

@end

 

 

degressToRadians() 메소드의 경우 각도를 라디안으로 변환하는 단순 매크로이다.

나머지 부분들은 이미 친숙한 것들이다.

이제 아웃렛 구현이 끝난 샘이니 인터페이스 빌더로 가서 필요한 두개의 뷰를 빌드해 보자.

 

2개의 뷰가 필요하다. 템플릿을 통해 제공되는 뷰는 크기를 조절할 수 없기 대문에 사용하지 않을 것이다.

대신에 템플릿이 제공하는 뷰 2개를 삭제하고 새로운 2개의 뷰를 생성하자.

 

 

2개의 뷰를 추가로 생성해 준다.

 

 

뷰는 라이브러리 창 하단부에서 찾을 수 있다.

 

 

FIle's Owner 에서 Control 드래그 하면 각각의 뷰를 아웃렛에 연결해 준다.

또한 Portrait의 경우 기본적으로 보여야할 view 에도 연결해 준다.

그렇게 되면 처음에 보여지게 될 뷰로 설정된다.

 


LandScape 역시 같은 방식으로 연결해 준다.

 

 

 

LandScape의 경우 뷰 사이즈는 320 * 460 인데 이를 480 * 300 으로 변경해야 한다.

 

 

 

이제 각각의 뷰에 버튼 2개씩을 드래그하여 놓아보자.
125 * 125 로 적당한 위치로 배치하고 이름을 정해 준다

 

 

 

 

버튼 역시 위에서 정해둔 아웃렛에 다 연결해 주게 된다.

이제 각각의 버튼에 Connection 창을 열어서 Touch Up Inside 에 buttonPressed IBAction을 할당해 준다.

 

SwapViewController.m

 

 

#import "SwapViewController.h"

 

@implementation SwapViewController

@synthesize landscape;
@synthesize portrait;
@synthesize landscapeFooButton;
@synthesize landscapeBarButton;
@synthesize portraitFooButton;
@synthesize portraitBarButton

 

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)tointerfaceOrientation duration:(NSTimeInterval)duration{

if(toInterfaceOrientation == UIInterfaceOrientationPortrait){

self.view = self.portrait;

self.view.transform = CGAffineTransformIdentity;

self.view.transform = CGAffineTransformMakeRotation(degressToRadians(0));

self.view.bounds = CGRectMake(0.0,0.0,300.0,480.0);

}else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft){

self.view = self.landscape;

self.view.transform = CGAffineTransformIdentity;

self.view.transform = CGAffineTransformMakeRotation(degressToRadians(-90));

slef.view.bounds = CGRectMake(0.0,0.0,460.0,320.0);

}else if(toInterface

Orientation == UIInterfaceOrientationPortraitUpsideDown){
self.view = self.portrait;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degressToRadians(180));
self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
}else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight){
self.view = self.landscape;
self.view.transform = CGAffineTransformIdentity;
self.view.transform = CGAffineTransformMakeRotation(degressToRadians(90));
self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
}

 

}

 

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{

return YES;

}

 

-(void)didReceiveMemoryWarning{

[super didReceiveMemoryWarning];

}

 

-(void)viewDidUnload{

self.landscape = nil;

self.portrait = nil;

self.landscapeFooButton = nil;

self.landscapeBarButton = nil;
self.portraitFooButton = nil;
self.portraitBarButton = nil;
[super viewDidUnload];

}

 

-(void)dealloc{

[landscape release];
[landscapeFooButton release];
[portraitFooButton release];
[landscapeBarButton release];
[portraitBarButton release];
    [super dealloc];
}

@end

 

willRotateToInterfaceOrientation 메소드는 회전이 일어나기 직전에 호출되는 메소드이다.

이 메소드를 통해 회전 방향을 알아내어 뷰 속성을 새로운 방향에 적합한 가로보기 또는 세로보기 모드로 정하게 된다.

그 후 코어 그래픽스 프레임워크에 포함된 CGAffineTransformMakeRotation 을 사용해 회전변환을 생성했다.

여기서의 변환은 객체의 크기, 위치, 각도의 변경에 대한 수학적인 표현이다.

일반적으로 아이폰은 회전하면 자동으로 변환된 값을 설정해 준다.

그러나 여기서는 새로운 뷰로 교체가 일어났기 때문에 아이폰이 혼동을 일으키지 않도록 값이 정확한지 확인할 필요가 있다.

뷰는 변환 속성이 설정될때마다 willRotateToInterfaceOrientation 메소드가 이러한 역할을 한다.

뷰가 회전되면 프레임을 조정하여 윈도우가 현재 방향에 어울리게 설정될 수 있도록 조정된다.

shouldAutorotateToInterfaceOrientation 메소드의 경우 우리가 모든 방향에 대한 회전을 제공한다는 사실을 아이폰에게

알려주기 위해서 무조건 YES를 리턴하는 메소드가 되었다.

또한 간단한 메모리 클린업을 위해 dealloc 메소드도 수정되었다.

 



출처 : http://ebcban.tistory.com/tag/%EC%9E%90%EB%8F%99%ED%9A%8C%EC%A0%84