블로그 이미지
'무른모'란 부드럽다라는 뜻을 가진 '무르다'라는 말과 도구, 연장을 뜻하는 '연모'라는 순 우리말의 합성어로 소프트웨어를 말합니다. seanhigher

카테고리

분류 전체보기 (161)
Blog srart (16)
Dev Center (94)
Real Life (13)
Mac life (21)
Naver life (17)
Total662,016
Today11
Yesterday42
모바일 환경이라함은 한정된 공간적 제약을 가지고 있다. 그렇기 때문에 컨트롤러 하나하나를 배치하거나, 레이아웃을 작성하는데 있어서 신중을 기해야 한다. 분할 컨트롤은 이러한 공간적 제약을 조금이나마 벗어날 수 있는 기능을 제공해 준다. 백개의 버튼을 한 화면에 담을 수는 없지만, 열개씩 나누어서 볼 수 있다면 충분히 넉넉하게 볼 수가 있다.


New project.
새로운 프로젝트를 시작한다. 'View-based Application' 을 선택하고 프로젝트 이름은 'roomControl' 로 하였다. 이번에 만들게 될 애플리케이션은 방의 불을 On/Off 하는기능, 온도를 조절하는 기능, 스텝을 호출하는 기능을 분할컨트롤 버튼을 이용하여 선택적으로 보여지게 한다. 우선은 프로그램에 대한 구상을 한다.


그림1. 프로젝트 구상

애플리케이션을 구상하는데 있어서 가장 먼저 해야 할 것은 아웃렛과 액션을 선언해야 하는 부분은 어떤 부분인가이다. 이 프로젝트에서는 분할 컨트롤을 선택하여 모든 객체에 hidden 메소드를 이용해야 하기때문에 표현되는 모든 라벨, 버튼, 스위치, 슬라이더 객체가 아웃렛으로 선언되어야 한다. 또한 액션으로는 어떠한 액션이 정의되어야 하는지 계획해야 한다.


첫번째 segmented Control
모든 화면을 한꺼번에 설명하기엔 양이 조금 많을 수 있기 때문에 한 화면씩 구체화 하도록 한다. 분할 컨트롤은 슬라이더와 마찬가지로 이미 아웃렛이 선언되어 있기 때문에 임의로 선언할 필요는 없다. 하지만, 분할 컨트롤의 선택에 대한 액션은 구현해 주어야 한다. 액션을 구현해 주지 않으면 분할컨트롤 객체가 무엇을 해야 할찌 모르기 때문이다. 첫 분할컨트롤은 방의 불을 조절하는 스위치와 라벨이 자리하고 있다. 세개의 스위치와 세개의 라벨이 모두 아웃렛으로 선언되어야 한다. 그리고 스위치는 On/Off 를 표시할 수 있도록 액션이 선언되어야 한다. 분할컨트롤와 첫번재 컨트롤에 들어가는 아웃렛은 다음과 같이 선언할 수 있다.

<roomControlViewController.h>
@interface roomControlViewController : UIViewController {
    UISwitch *roomLight1_Switch;
    UISwitch *roomLight2_Switch;
    UISwitch *indoorLight_Switch;
    UILabel *roomLight1_Label;
    UILabel *roomLight2_Label;
    UILabel *indoorLight_Label;
}

@property (nonatomic, retain) IBOutlet UISwitch *roomLight1_Switch;
@property (nonatomic, retain) IBOutlet UISwitch *roomLight2_Switch;
@property (nonatomic, retain) IBOutlet UISwitch *indoorLight_Switch;
@property (nonatomic, retain) IBOutlet UILabel *roomLight1_Label;
@property (nonatomic, retain) IBOutlet UILabel *roomLight2_Label;
@property (nonatomic, retain) IBOutlet UILabel *indoorLight_Label;

- (IBAction) toggleControls: (id) sender;
- (IBAction) switchChange: (id)sender;


<roomControlViewController.m>
@implementation roomControlViewController

@synthesize roomLight1_Switch;
@synthesize roomLight2_Switch;
@synthesize indoorLight_Switch;
@synthesize roomLight1_Label;
@synthesize roomLight2_Label;
@synthesize indoorLight_Label;

- (IBAction) toggleControls: (id)sender {
    if([sender selectedSegmentIndex] == 0) {
        roomLight1_Switch.hidden = NO;
        roomLight2_Switch.hidden = NO;
        indoorLight_Switch.hidden = NO;
        roomLight1_Label.hidden = NO;
        roomLight2_Label.hidden = NO;
        indoorLight_Label.hidden = NO;
    } else if([sender selectedSegmentIndex] == 1) {
        roomLight1_Switch.hidden = YES;
        roomLight2_Switch.hidden = YES;
        indoorLight_Switch.hidden = YES;
        roomLight1_Label.hidden = YES;
        roomLight2_Label.hidden = YES;
        indoorLight_Label.hidden = YES;
    } else if([sender selectedSegmentIndex] == 2) {
        roomLight1_Switch.hidden = YES;
        roomLight2_Switch.hidden = YES;
        indoorLight_Switch.hidden = YES;
        roomLight1_Label.hidden = YES;
        roomLight2_Label.hidden = YES;
        indoorLight_Label.hidden = YES;
    }
}

- (IBAction) switchChange: (id)sender {
    UISwitch *whichSwitch = (UISwitch *)sender;
    BOOL setting = whichSwitch.isOn;
    [roomLight1_Switch setOn:setting animated:YES];
    [roomLight2_Switch setOn:setting animated:YES];
    [indoorLight_Switch setOn:setting animated:YES];
}

... ... ...

- (void)dealloc {
    [roomLight1_Switch release];
    [roomLight2_Switch release];
    [indoorLight_Switch release];
    [super dealloc];
}


위 소스코드에서 toggleControls 액션은 분할컨트롤에 대한 액션이다. 액션 개체는 sender 라는 인자를 통해 값을 전달받을 수 있는데, 분할컨트롤은 선택영역에 따라 인덱스값을 받을 수 있다. 분할컨트롤의 분할컨트롤의 첫번째는 '0', 두번째는 '1', 세번째는 '2' 와 숫자의 배열 형식을 갖는다. 이 값은 'selectedSgmentedIndex' 라는 변수값으로 구분지을 수 있다. 이 값을 비교함으로써 분할컨트롤이 어떤 값을 선택하였는지 알 수 있게 된다. 분할컨트롤이 선택되고 '{ }' 안의 내용을 보면 hidden 이라는 메소드가 호출되는 것을 볼 수 있다. UIview 를 상속받는 모든 객체의 기본적인 메소드이며, 화면에 표시할것인지를 결정하는 요소가 된다. 'NO' 일경우는 숨기지 않겠다는 뜻으로 화면에 표시가 되고, 'YES' 값일 경우는 숨기겠다는 뜻으로 화면에 표시하지 않겠다는 뜻이 된다. switchChange 액션은 스위치가 On / Off 되는 액션을 표현한다. 중간에 setting animated 옵션은 스위치가 순식간에 변하게, 또는 애니메니션이 적용되어 변하게 되는지 설정할 수 있다.


첫번째 레이아웃 작성.
레이아웃을 작성하기 위해서는 인터페이스 빌더를 실행해야 한다. 'Resources' 폴더의 'roomControlViewController.xib' 파일을 더블클릭하여 인터페이스 빌더를 실행한다. 분할 컨트롤러를 이용한 첫번째 화면의 레이아웃을 작성한다. 라이브러리 윈도우에서 분할 컨트롤 객체, 스위치 객체를 드래그하여 뷰 윈도우의 적당한 위치에 배치시킨다. 그리고 라벨을 위치시키고 텍스트 내용을 변경한다.


그림2. 첫번째 레이아웃 작성

분할 컨트롤러는 기본적으로 두개의 선택 버튼을 기본으로 제공한다. 하지만, 위의 그림처럼 세개의 메뉴로 늘리기 위해서는 Attibute 윈도우의 segments 항목을 '2' 에서 '3'으로 늘려주면 항목수가 하나 늘어나게 된다.


액션과 아웃렛 연결하기.
액션과 아웃렛은 인터페이스 빌더를 통해 연결하지 않으면 아무 소용이 없다. 아웃렛을 연결하는 방법에는 두가지가 있다. 첫번째는 'ctrl' 키를 누른채로 main 윈도우의 File's Owner 아이콘을 클릭하고, 아웃렛을 연결하고자 하는 객체로 드래그하여 선택하는것이다. 두번째는 File's Owner 아이콘을 선택한 후 Connections 윈도우에서 아웃렛을 먼저 선택하여 원하는 객체에 연결 시키는 방법이다. 방법의 차이만 있을 뿐 다른 차이점은 없기 때문에 선호하거나, 편한 방법으로 세개의 스위치와, 라벨의 아웃렛을 연결한다. 액션을 연결하는 방법도 아웃렛을 연결하는 것과 비슷한 방식으로 이루어진다. 액션을 연결하는 방법 역시 두가지가 있다. 첫번째는 아웃렛을 연결하는 방법과 반대의 순서로 하면 된다. 'ctrl' 키를 누르는 것은 동일하고, File's Owner 에서 객체로 드래그 하는 것이 아니라, 객체를 먼저 선택한 후 File's Owner 아이콘으로 드래그하는 것이다. 그러면 Events 항목이 팝업으로 나오게 되고, 적절한 항목을 선택하면 된다.


그림3. 액션 연결하기

액션을 연결하는 두번째 방법은 File's Owner 아이콘을 선택한 후 Connections 윈도우를 이용하는 것이다. 아웃렛을 연결하는 것과 마찬가지로 Connections 윈도우를 보면 아랫부분에 Received Actions 항목에 이미 선언한 액션들을 볼 수가 있다. 원하는 액션의 항목 오른쪽으로 마우스 커서를 가져가면 원안에 십자가 모양이 생기게 된다. 이것을 액션을 연결하고자 하는 객체에 끌어다가 놓으면 팝업으로 액션 항목이 나오는데, 가장 적절한 액션을 선택하도록 한다. 스위치와 분할 컨트롤러에는 Value Changed 값이 가장 적당하다. togleControls 액션도 분할컨트롤러에 연결하도록 한다. 첫번째로 작성한 컨트롤러가 잘 작동하는지 시뮬레이션을 해보도록 한다. 분할컨트롤의 첫번째 버튼에만 화면이 나오는 것을 볼 수 있을 것이다. 만약 오류가 있어가, 생각했던대로 실행이 되지 않는다면 오류를 확인해 보고, 위에서부터 차근차근 다시 해보길 바란다.


그림4. 첫번째 분할컨트롤 작성 후 시뮬레이션 화면


두번째 Segmented Control
두번째 분할컨트롤 화면은 위와 같은 순서를 반복하면 된다. 두번째 화면에서는 슬라이더에 의한 제어를 하기 때문에 세개의 슬라이더와 세개의 라벨이 필요하다. 다음과 같이 코드를 입력한다.

<roomControlViewController.h>
@interface roomControlViewController : UIViewController {
    ... ...

    UILabel *roomTmep1_SliderLabel;
    UILabel *roomTemp2_SliderLabel;
    UILabel *indoorTemp_SliderLabel;
    UISlider *roomTemp1_Slider;
    UISlider *roomTemp2_Slider;
    UISlider *indoorTemp_Slider;

}
... ...

@property (nonatomic, retain) IBOutlet UILabel *roomTmep1_SliderLabel;
@property (nonatomic, retain) IBOutlet UILabel *roomTemp2_SliderLabel;
@property (nonatomic, retain) IBOutlet UILabel *indoorTemp_SliderLabel;
@property (nonatomic, retain) IBOutlet UISlider *roomTemp1_Slider;
@property (nonatomic, retain) IBOutlet UISlider *roomTemp2_Slider;
@property (nonatomic, retain) IBOutlet UISlider *indoorTemp_Slider;

... ...

- (IBAction) SliderChangeRoom1: (id)sender;
- (IBAction) SliderChangeRoom2: (id)sender;
- (IBAction) SliderChangeIndoor: (id)sender;


<roomControlViewController.m>
... ...
@synthesize roomTmep1_SliderLabel;
@synthesize roomTemp2_SliderLabel;
@synthesize indoorTemp_SliderLabel;
@synthesize roomTemp1_Slider;
@synthesize roomTemp2_Slider;
@synthesize indoorTemp_Slider;
... ...

- (IBAction) toggleControls: (id)sender {
    if([sender selectedSegmentIndex] == 0) {
        ... ...

        roomTmep1_SliderLabel.hidden = YES;
        roomTemp2_SliderLabel.hidden = YES;
        indoorTemp_SliderLabel.hidden = YES;
        roomTemp1_Slider.hidden = YES;
        roomTemp2_Slider.hidden = YES;
        indoorTemp_Slider.hidden = YES;
    } else if([sender selectedSegmentIndex] == 1) {
        ... ...

        roomTmep1_SliderLabel.hidden = NO;
        roomTemp2_SliderLabel.hidden = NO;
        indoorTemp_SliderLabel.hidden = NO;
        roomTemp1_Slider.hidden = NO;
        roomTemp2_Slider.hidden = NO;
        indoorTemp_Slider.hidden = NO;
    } else if([sender selectedSegmentIndex] == 2) {
        ... ...

        roomTmep1_SliderLabel.hidden = YES;
        roomTemp2_SliderLabel.hidden = YES;
        indoorTemp_SliderLabel.hidden = YES;
        roomTemp1_Slider.hidden = YES;
        roomTemp2_Slider.hidden = YES;
        indoorTemp_Slider.hidden = YES;
    }
}
... ...

- (IBAction) SliderChangeRoom1: (id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat: @"Room1 temperature is %d degrees", progressAsInt];
    roomTmep1_SliderLabel.text = newText;
    [newText release];
}

- (IBAction) SliderChangeRoom2: (id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat: @"Room2 temperature is %d degrees", progressAsInt];
    roomTemp2_SliderLabel.text = newText;
    [newText release];
}

- (IBAction) SliderChangeIndoor: (id)sender {
    UISlider *slider = (UISlider *)sender;
    int progressAsInt = (int)(slider.value + 0.5f);
    NSString *newText = [[NSString alloc] initWithFormat: @"Indoor temperature is %d degrees", progressAsInt];
    indoorTemp_SliderLabel.text = newText;
    [newText release];
}
... ...

- (void)dealloc {
    ... ...

    [roomTmep1_SliderLabel release];
    [roomTemp2_SliderLabel release];
    [indoorTemp_SliderLabel release];
    [roomTemp1_Slider release];
    [roomTemp2_Slider releasd];
    [indoorTemp_Slider release];
    [super dealloc];
}

첫번째 화면을 작성하면서 작성했던 소스코드에 위의 내용을 추가하면 된다. 슬라이더의 경우 일반적으로 아웃렛을 선언하지 않아도 그 값을 받아 사용할 수 있지만, 이번 프로젝트에서는 분할컨트롤러를 이용할때 hidden 옵션을 사용해야 하기 때문에 아웃렛을 선언하여 hidden 옵션을 사용할 수 있도록 하였다. 화면의 작업을 마친 후에는 분할컨트롤의 항목도 신경을 써 주어야 한다. 어느 버튼을 선택했을때 보여야 할찌 각 객체별로 모두 선언을 해 주어야 한다. 각 슬라이더의 액션은 슬라이더의 인자값을 받아서 라벨에 출력하는 액션이다. 먼저 슬라이더의 인자값을 구하고, 정수형으로 변환한 후 텍스트 형식으로 바꾸어 준다. 그리고 아웃렛으로 선언된 라벨에 출력해준다. 코딩을 하면서 retain 으로 선언된 모든 객체들은 꼭 release를 해서 자원의 낭비를 막아야 한다는 것을 잊지 말기 바란다.


화면디자인하고, 아웃렛과 액션 연결하기.
다시 인터페이스 빌더로 이동하여 화면을 디자인하도록 한다. 분할 컨트롤을 사용하는 경우 이미 객체들이 배치되어 있는 상태에서 그 위에 또 다른 객체를 표현하는 것이기 때문에 화면 하나씩 순서대로 작성하는 것이 화면을 구성하는데 불편함을 덜 수 있다.


그림5. 화면디자인및 아웃렛, 액션 연결

첫번째 페이지를 만들때와 동일한 방법으로 뷰 윈도우의 객체들과 아웃렛을 이어주고, 액션을 연결해 주면 된다. 한가지 주의해야 할 점은 두번째 화면의 객체들은 Attribute 윈도우에서 View -> Drawing -> Hidden 을 체크해 주어야 한다. 그렇지 않으면 처음 화면에서 위의 화면과 같이 겹치는 화면을 보게 될 것이다.


세번째 Segmented Control
세번째는 버튼을 이용한 화면이다. 버튼을 이용한 액션을 가장 기초적인 액션이기 때문에 어렵지 않게 구현할 수 있어야 한다. 잘 모르겠다면 이전의 포스팅을 보면서 버튼을 구현하도록 한다. 이 프로젝트에 필요한 버튼은 여섯개이다. 버튼의 내용을 표현하기 위한 하나의 라벨이 필요하다. 또 버튼의 내용을 인자값으로 전달받아서 출력하기 위한 액션을 추가한다.

<roomControlViewController.h>
@interface roomControlViewController : UIViewController {
    ... ...

    UIButton *roomStaff1_Button;
    UIButton *roomStaff2_Button;
    UIButton *roomStaff3_Button;
    UIButton *roomStaff4_Button;
    UIButton *roomStaff5_Button;
    UIButton *roomStaffAll_Button;
    UILabel *callStaff;

}
... ...

@property (nonatomic, retain) IBOutlet UIButton *roomStaff1_Button;
@property (nonatomic, retain) IBOutlet UIButton *roomStaff2_Button;
@property (nonatomic, retain) IBOutlet UIButton *roomStaff3_Button;
@property (nonatomic, retain) IBOutlet UIButton *roomStaff4_Button;
@property (nonatomic, retain) IBOutlet UIButton *roomStaff5_Button;
@property (nonatomic, retain) IBOutlet UIButton *roomStaffAll_Button;
@property (nonatomic, retain) IBOutlet UILabel *callStaff;

... ...

- (IBAction) buttonPress: (id)sender;


<roomControlViewController.m>
... ...
@synthesize roomStaff1_Button;
@synthesize roomStaff2_Button;
@synthesize roomStaff3_Button;
@synthesize roomStaff4_Button;
@synthesize roomStaff5_Button;
@synthesize roomStaffAll_Button;
@synthesize callStaff;

... ...

- (IBAction) toggleControls: (id)sender {
    if([sender selectedSegmentIndex] == 0) {
        ... ...

        roomStaff1_Button.hidden = YES;
        roomStaff2_Button.hidden = YES;
        roomStaff3_Button.hidden = YES;
        roomStaff4_Button.hidden = YES;
        roomStaff5_Button.hidden = YES;
        roomStaffAll_Button.hidden = YES;
        callStaff.hidden = YES;

    } else if([sender selectedSegmentIndex] == 1) {
        ... ...

        roomStaff1_Button.hidden = YES;
        roomStaff2_Button.hidden = YES;
        roomStaff3_Button.hidden = YES;
        roomStaff4_Button.hidden = YES;
        roomStaff5_Button.hidden = YES;
        roomStaffAll_Button.hidden = YES;
        callStaff.hidden = YES;

    } else if([sender selectedSegmentIndex] == 2) {
        ... ...

        roomStaff1_Button.hidden = NO;
        roomStaff2_Button.hidden = NO;
        roomStaff3_Button.hidden = NO;
        roomStaff4_Button.hidden = NO;
        roomStaff5_Button.hidden = NO;
        roomStaffAll_Button.hidden = NO;
        callStaff.hidden = NO;

    }
}
... ...

- (IBAction) buttonPress: (id)sender {
    NSString *title = [sender titleForState:UIControlStateNormal];
    NSString *newText = [[NSString alloc] initWithFormat: @"%@ called", title];
    callStaff.text = newText;
    [newText release];
}

... ...

- (void)dealloc {
    ... ...

    [roomStaff1_Button release];
    [roomStaff2_Button release];
    [roomStaff3_Button release];
    [roomStaff4_Button release];
    [roomStaff5_Button release];
    [roomStaffAll_Button release];
    [callStaff release];

    [super dealloc];
}

위에서 분할 컨트롤과, 스위치, 슬라이더에 대한 액션을 구현했다면 나머지 버튼을 구현하는 것은 크게 어렵지 않다. 위 소스 코드를 참조하여 코딩을 하도록 한다. buttonPress 액션은 각 버튼에 대한 값들을 받아와서 새로운 문자열을 만들고, 선언된 라벨에 출력하는 액션이다. 분할컨트롤에 항목을 추가하는 것과, 객체들을 release 하는것도 빠뜨리지 않고 하도록 한다. 모든 프로그래밍을 마친 후에는 인터페이스 빌더로 이동하여 인터페이스 디자인을 한다.


그림6. 세번째 분할컨트롤 화면 구현

이전의 인터페이스들이 그대로 보여지기 때문에 화면에 매우 복잡해지기 시작한다. 하나하나 잘 체크해가며 객체들을 배치하도록 한다. 객체를 배치한 후에는 아웃렛을 연결하고, 액션들을 연결하도록 한다. 어렵지 않게 마지막 화면을 마무리 할 수 있을 것이다.


그림7. 완성된 애플리케이션 시뮬레이션

위와 같이 분할컨트롤로 애플리케이션을 제작하면 더 많은 정보들을 좀더 효율적으로 보여줄 수 있다.

Posted by seanhigher

댓글을 달아 주세요

첫번째로 작성한 'Hello world!' 프로젝트는 사용자가 할 수 있는 것이 아무것도 없다. 단지 화면에 나와 있는 텍스트만 뚫어져라 쳐다보고 있는 것 외에는... 휴대용 기기에서의 애플리케이션이라고 하면, 우선은 사용자와 반응하는 것이 중요하다. 사용자의 생각을 읽어서 자동으로 반응해 주는것이 가장 좋은 것이지만, 아직까지 기술이 그렇게 많이 발달하지 않았기때문에... 사용자가 뭔가 표현을 해주어야 반응을 하게 된다. 아이폰에 버튼을 만들고 버튼을 누르면, 뭔가 반응을 보일 것이다. 개발자가 잘 만들었다면...


어떤 모양으로 만들까?
누르면 반응하는 프로젝트를 개발하는데 있어서 가장 먼저 해야 할 것은 어떤 프로젝트를 만드냐이다. '누르면 반응하는 App'은 이미 뭔가 반응하게끔 만들어야 겠다는 의도와 목적이 들어 있기 때문에 그부분에 대해서는 고민할 필요가 없다. 그 다음으로는 어떠한 방식으로 그것을 구현할 것인가이다. 어떠한 방식으로 누르는 것을 표현할 수 있을까? 가장 간단하게 생각이 되는 것은 몇개의 버튼이 있는데, 그중 하나의 버튼을 누르면 그 버튼에 맞는 '무언가'가 실행이 되는 것이다. 아직 별 다른 기술이 없으므로 반응한는 '그 무언가'는 버튼에 맞는 텍스트가 출력되는 것으로 한다. 다시 정리를 하면...

(1) 화면중앙 부분에 세개의 버튼이 있다.(세개의 버튼은 1,2,3 이든, top, middle, bottom등...)
(2) 각 버튼에 알맞는 텍스트가 화면 아래에 출력된다.

매우 간단한 애플리케이션이지만, 이렇게 구체적인 계획을 세워 놓으면 프로젝트를 할때, 불필요한 고민을 하는 시간을 줄일 수 있다. 계획이 세워졌으므로 Xcode를 실행하고 'View-based Application' 프로젝트를 생성한다. 어떠한 이름을 하여도 상관은 없지만, 이해를 쉽게 하기 위해서 'FirstInteraction'으로 하도록 한다.


디자인먼저? 코딩먼저?
결론부터 말하자면, 디자인을 먼저하든, 코딩을 먼저하든 크게 상관이 없다. 어떠한 애플리케이션을 개발하더라도 이미 계획했던 것에 대해서 수정이 이루어지는 것은 불가피 하다. 코딩을 다 해놓은 상태에서 뷰를 디자인 하게 되면, 코딩에 맞추어서 디자인을 할 수 있지만, 생각하지 못했던 화면 제약이나, 표현상의 문제로 인해 코딩을 바꾸어야 하는 상황이 있을 수 있다. 또 디자인을 다 해 놓은 상태에서 기술적인 문제 때문에 기능을 제거한다던지, 다른 기능을 추가하는 등의 변경 사항이 발행하게 된다. 때문에 무엇을 먼저 하던 Xcode와 IB를 왔다갔다 해야 하는 것은 당연한 일이다. 하지만, 개인적으로는 디자인적인 문제보다는 기술적인 문제가 더 많이 발생하므로, 코딩을 먼저 완성한 후 디자인을 하는 것을 권유하고 싶다.


반응을 개발하기 위한 준비 작업.
반응을 애플리케이션에 포함시키기 위해서는 액션과 아웃렛이 사용된다. 액션은 반응을 위해 하는 어떠한 행위를 통해서 애플리케이션이 어떻게 반응해야 하는가에 대해서 말해주고, 아웃렛은 그러한 액션이 있다는 것을 IB와 xcode가 서로 알 수 있게끔 만들어준다.


그림1. FirstInteractionViewController.h 파일 수정

프로젝트에 액션과 아웃렛을 추가하기 위해서는 '프로젝트명ViewController.h 헤더파일에 선언해 주어야 한다. 반응이 일어나는 텍스트와 액션이 이루어지는 버튼을 사용할 것이기 때문에 다음과 같이 입력해 주면 된다.(굵은 글씨 부분이 추가해야 할 부분)

#import <UIKit/UIKit.h>

@interface FirstInteractionViewController : UIViewController {
    UILabel *labelText;
}

@property (nonatomic, retain) IBOutlet UILabel *labelText;
-(IBAction)buttonAction : (id)sender;

@end

Label 변수를 선언하고, 프로퍼티를 선언하여 수정이 가능한 메소드를 생성하였으며, 액션을 추가하여, 특정 액션으로부터의 접근이 가능하다. 헤더 파일에서 선언한 액션은 FirstInsteraction.m 파일에서 선언한 액션이 어떠한 행동을 할 것인가에 대해서 구현해 주어야 한다.


그림2. FirstInteractionViewController.m 파일 수정

컨트롤러 구현을 위한 파일인 '프로젝트명ViewController.m' 파일에는 개발에 자주 사용되는 메소드가 이미 구현이 되어 있다. 일부 코드들은 주석으로 처리 되어 있는데(초록색으로 되어 있는 부분), 이것들은 필요에 따라 주석을 해제하여 사용하거나, 삭제를 하여 작성하기도 한다. 'FirstInteraction' 프로젝트에서는 복잡한 메소드가 필요하지 않기때문에 주석된 부분을 다 삭제하고 코드를 간략화한다. 그리고 다음의 코드를 삽입한다.

#import "FirstInteractionViewController.h"

@implementation FirstInteractionViewController
@synthesize labelText;

-(IBAction)buttonAction : (id)sender {
    NSString *title = [sender titleForState : UIControlStateNormal];
    NSString *newText = [[NSString alloc] initWithFormat: @"%@ button pressed.", title];
    labelText.text = newText;
    [newText release];
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];   
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.labelText = nil;
}


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

@end

기존에 생성되었던 코드보다 훨씬 더 간결해진것을 볼 수 있다. 굵은 글씨로 이루어진 부분의 소스코드를 살펴보면 생소한 것들이 많이 있다.

@synthesize labelText; - 헤더파일에서 'setText'변수를 프로퍼티로 선언하였다. 헤더파일에서의 선언은 접근자, 변경자 메소드에 대한 정의를 하고, 구현 파일에서의 sysnthesize 선언은 컴파일시에 컴파일러가 'setText'변수에 대한 접근자와 변경자 메소드를 생성해 준다. 개발자가 직접적으로 접근자와 변경자를 선언하지 않고, 구현하지 않아도 컴파일시에 자동으로 생성된다.
- (IBAction)buttonAction : (id) sender { } - 액션이 작용할때 하게 될 행동에 대해서 구현해 놓는 곳이다. 이 액션 메소드는 어떠한 버튼을 누르더라도 동일한 액션을 취하게 된다.
NSString *title = [sender titleForState: UIControlStateNormal]; - 액션이 작용하고 있는 버튼을 가르키는 sender에 할당된 값을 참조하여 어떤 버튼이 눌려지는지 알 수 있다. 이 메소드는 클릭된 버튼의 제목을 얻어 title 변수에 저장하기 위한, 미리 정의된 메소드를 사용하는 소스이다.
NSString *newText = [[NSString alloc] initWithFormat: @"%@ button pressed.", title]; - 이전에 저장된 title 변수에 'button pressed'라는 문자열을 추가한 새로운 문자열을 만든다. 새로운 변수의 이름은 'newText'가 된다.
labelText.text = newText; - Label로 선언된 변수에 입력을 받아 새롭게 정의된 newText의 값을 할당한다. 예문에서는 점표기법을 사용하였지만, [labelText setText:newText] 과 같은 방법으로도 표기할 수 있다.
[newText relese]; - 사용이 끝난 객체는 릴리즈시켜 자원의 낭비를 줄여야 한다. 모바일 프로그래밍의 경우, 일반적인 컴퓨터와는 다르게 자원의 규모나 성능이 작기때문에 최대한 효율적으로 사용해야 한다.
self.labelText = nil; - 클래스안의 아웃렛들은 viewDidUnload 메소드 안에서 nil 값으로 설정해 주어야 한다.
[labelText release]; - dealloc 메소드 안에서 아웃렛을 릴리즈 한다. 프로퍼티로 구현된 아웃렛이 retain의 속성을 가지기 때문에 릴리즈하여 자원의 낭비를 줄이는 일은 매우 중요하다.


외형 만들기.
소스코딩에 대한 부분을 마무리하였다면 멋들어진 외형을 만들어야 한다. 하지만, 아직은 그렇게 실력이 좋지 않으므로 계획에 있던데로 간단한 디자인으로 하도록 한다. 뷰를 편집하기 위해서는 'Groups & Files' 창에 있는 Resource 폴더의 'FirstInteractionViewController.xib' 파일을 더블클릭하면 나타나는 인터페이스빌더를 이용해야 한다.


그림3. 인터페이스 빌더가 실행된 화면

위 그림과 같이 인터페이스 빌더가 실행되면, 뷰 화면을 편집 한다. 왼쪽 라이브러리 윈도우에서 필요한 라이브러리 선택해서 오른쪽의 뷰 윈도우에 드래그를 해서 가져다 놓으면 된다. 각 객체는 크기를 마음대로 조절할 수 있으며, 객체들의 규칙적인 배치를 위해서 적절한 위치게 객체가 이동하게 되면, 파란점선의 가이드 라인이 자동으로 생성된다.


그림4. View 화면 편집

내용을 표시하게 될 label과 반응을 주게 될 세개의 버튼은 위의 그림과 같이 나란히 배치한다.(파란점선의 가이드 라인은 객체를 드래그 할때만 생겼다가 사라진다.) 객체를 나타내는 이름을 바꿔주기 위해서는 객체의 Label, button 등의 이름을 더블클릭하면 수정이 가능하다. button 객체의 이름을 각각 Top, Middle, Bottom 으로 바꾸어 주고 Label은 이름만 삭제한다. 맨처음 아무것도 누르지 않았을 때는 아무것도 나오지 않은 상태여야 하기 때문이다.


IB에서 아웃렛 선언해주기.
코딩과 디자인을 마무리 하였다면 이것을 서로에게 이어주는 작업이 필요하다. 각각의 객체들이 가지고 있는 작용들이 어떤 객체를 참조해야 하는지 직접적으로 이어주는 것이다.


그림5. 객체에 아웃렛 이어주기

객체에 아웃렛을 연결하기 위해서는 'FirstInteractionViewController.xib' 윈도우에 있는 'File's Owner' 아이콘에서부터 시작을 해야 한다. 컨트롤 키를 누른 상태에서 'File's Owner'아이콘을 클릭하여 드래그를 하면 위의 그림처럼 파란색의 선이 생기는 것을 보게 된다. 파란색의 선을 끌어당겨 Label이 있는 자리 근처로 가게 되면 Label 영역을 나타대는 사각형의 박스가 나타날 것이다. 이때 마우스의 버튼을 놓으면 작은 메뉴가 나타나게 되는데, 이전에 헤더파일에 지정하였던 아웃렛의 이름들이 나오는 것이다. 'labelText'를 선택하여 아웃렛을 연결해 준다.


객체에 액션 지정해 주기.
반응을 위한 버튼 객체에 액션을 지정해 주는 것은 아웃렛을 이어주는 것과 비슷하다. 코코아 프로그래밍에서는 두개의 작업이 거의 동일하게 이루어 졌지만, 아이폰 프로그래밍에서는 조금 다르게 작업을 해야 한다. 하지만, 정말 거의 비슷하다!


그림6. 객체에 액션 지정하기

오른쪽의 인스펙터 윈도우는 두번째에 있는 'Connections' 메뉴를 선택한다. 그리고 메인 뷰에서 액션을 지정하고자 하는 버튼(Top)을 선택하면 인스펙터 윈도우에 위와 같은 이벤트들이 나오게 된다. 다양한 이벤트들 중에서 버튼을 누르는 것과 가장 관련이 깊은 이벤트를 선택해야 한다. '반응하는 App' 은 버튼을 누르고 난 후 그 버튼의 종류에 따라 결과를 반환하게 되는것이기 때문에 'Touch up inside' 이벤트가 적절하다고 생각할 수 있다. 왜냐하면 선택하여 눌렀는데, 그 버튼이 마음에 들지 않아 손가락을 드래그하여 화면에서 땐다면 그것을 선택하지 않은 것이기 때문이다. 화면에서 가장 마지막에 손가락을 때었을때 버튼의 영역에 포함되어 있다면 그 버튼을 실행하고자 누른것으로 간주하는 것이다. 적절한 이벤트인 'Touch up inside'의 오른쪽에 있는 작은 원에 마우스를 갔다 놓으면 십자가 형태로 바뀌는 것을 볼 수 있다. 이 십자가를 끌어서 'FirstInteractionViewController.xib' 윈도우의 'File's Owner' 에 가져다 놓는다. 그러면 헤더파일에 선언되어 있는 액션 메소드의 리스트가 나타나게 된다. 'buttonAction'을 선택하여 액션을 이어준다. 나머지 두개의 버튼들도 동일한 작업을 거치면 모든 작업이 마무리 된다.


프로젝트 실행.
다시 Xcode로 돌아가서 'Build and Run' 버튼으로 프로젝트를 빌드하고 시뮬레이션 한다.


그림7. 프로젝트 시뮬레이션 화면

제대로 프로젝트가 완성되었다면 위의 화면과 같이 나올 것이다. 버튼만 세개가 있는 화면이 나온후에 버튼을 클릭하면 버튼에 맞는 텍스트가 화면 아래에 나오게 된다. 이제 사용자들의 의견을 수렴할 줄 아는 준비된 애플리케이션을 개발할 준비가 된것이다.

Posted by seanhigher

댓글을 달아 주세요

  1. 2010.07.31 02:06 쉬인  댓글주소  수정/삭제  댓글쓰기

    안녕하세요? 포스팅 내용이 정말 많은 도움이 되고 있습니다.
    이번 상호작용 편에서... 모두 제대로 한것 같은데 (커멘드+B 로 오류검사도 통과)
    정작 아이폰 시뮬레이터에서 버튼을 클릭하는 순간 어플이 종료되어 버립니다.

    혹시 이번에 업데이트 된 SDK를 사용해서 그런가요? 야심한 밤에 머리 쥐어뜯다가 글 남겨봅니다.
    알려주세요^^;;

    • 2010.08.02 09:54 신고 seanhigher  댓글주소  수정/삭제

      음... 그것만으로는 정확한 답을 드리기가... ^^;;
      하지만 제 경우에도 분명 똑같이 했음에도 불구하고 어플리케이션이 실행과 동시에 종료되는 경우가 있었는데... 뭔가 오타가 있거나, 연결을 제대로 해주지 않아서인 경우가 많더라구요... ^^;

  2. 2010.08.05 19:21 쉬인  댓글주소  수정/삭제  댓글쓰기

    앗 그렇군요
    한번 더 오타를 검사해봐야 겠습니다^^;
    좋은 정보 잘 보고있습니다.
    감사합니다 ㅎ

Objective-C 프로그래밍을 경험해 봤다면 어색하게 느껴지지 않겠지만, 그렇지 않은 사람들에게는 IBOutlet과 IBAction이 무슨 역할을 하며 왜 필요한지에 대해서 의아해 할 것이다. 간단해 보이지만, 아이폰 프로그래밍에 있어서 없어서는 안될 중요한 존재이다. 이것들이 없다면 아무리 멋지게 프로그램을 작성하여도 연결선이 끊어진 조이스틱을 두드리는 것과 마찬가지가 되는 것이다.



아웃렛(IBOutlet)
아웃렛은 IBOutlet 키워드를 사용하여 선언하는 인스턴스 변수들이다. 아웃렛이 하는 역할은 정말로 단순하다. 컨트롤러 헤더 파일에 선언한 객체를 인터페이스 빌더가 알아 볼 수 있도록 해준다. xib파일 안의 객체와 연결을 하고자 하는 모든 인스턴스 변수들은 IBOutlet 키워드로 다음과 같은 형식으로 선언이 되어야 한다.

@property (nonatomic, retain) IBOutlet UILabel *newLabel;

아웃렛으로 선언된 인스턴스 변수는 프로젝트가 빌드되면서 가장 먼저 전처리기에서 번역되고, 그것이 xib 파일과 연결된 객체라는 사실이 컴파일러에게 전달된다. 프로젝트의 실행과 관련된 컴파일에서는 아무런 영향을 미치지 않는것이다. 하지만, 이것이 없다면 인터페이스 빌더는 어떻게 객채들과 연결해야 할지 모른체 빌드를 실행하게 될 것이다. 마치 지금 당장 전화를 걸어야 하는 상황에서 어떤 전화기를 가지고 어디로 전화를 해야 할찌 모르는 상황과 비슷하다고 할 수 있다.


액션(IBAction)
액션은 IBOutlet과 마찬가지로 컨트롤러 헤더파일에서 하나의 메소드 형태로 선언되어 그 역할을 하게 된다. IBAction이 선언되면 이 메소드가 액션 메소드라는 것을 인터페이스 빌더에게 알려주게 되며, 컨트롤러를 통해서 호출이 가능해 진다. 액션 메소드는 헤더파일에서 다음과 같은 형식으로 선언된다.

 - (IBAction)newAction:(id)sender;

메소드의 형식을 갖는 IBAction는 void를 리턴 타입으로 가진다. 액션 메소드는 변수값을 리턴하지 않는다는 것이다. 액션 메소드는 하나의 인자값을 갖게 되는데, 이것은 sender라는 이름의 id 타입으로 정의되고, 포인터 값이 전달된다. 이것은 똑같은 액션 메소드를 호출하는데 있어서 어떤 액션을 통해서 메소드를 호출하였는지 구분하는 구분자의 역할을 하게 된다. 만약 버튼이 하나밖에 없는 것 처럼, 액션의 구분이 필요하지 않다면 뒷부분의 '(id)sender' 를 제거하고 작성하면 가능하다. 하지만 버튼이 여러게 있는데 '(id)sender' 를 제거하면 모든 버튼이 동일한 역할만을 하게 될 것이다.


생성자와 변경자를 자동으로 생성해주는 @property를 선언하자!
C++ 나 JAVA같은 객체지향 언어를 다루다 보면, 기능을 모듈화 하고, 데이터를 보호하기 위해 하나의 객체에 생성자와 변경자를 만드는 것을 보게 된다. 생성하는 클래스나 객체가 적을때는 그리 어렵지 않겠지만, 늘어나는 클래스 객체의 getter(생성자 메소드)와 setter(변경자 메소드)를 일일이 생성하는 것은 매우 지루한 일이 될 수 있다. Objective-C에서의 프로퍼티는 이러한 Getter 와 Setter를 자동으로 생성해 주어 개발자의 수고를 조금이나마 덜어준다. 오브젝티브 C의 프로퍼티에는 몇가지 속성들이 있는데 retain, nonatomic 의 두가지의 속성은 아이폰 애플리케이션을 작성하는데 있어서 많이 사용되는 속성이다.

retain : 리테인은 메모리에 할당된 특정 객체를 참조하는 것을 의미한다. 각 객체는 리테인 카운트라는 데이터를 가지게 되는데, 객체가 호출될때마다 카운트 값을 증가시키고, 릴리즈 될때에는 카운트 값을 하나 감소시키면서 그 객체가 사용되고 있는지의 여부를 판단하게 된다. 이것은 메모리를 효율적으로 관리하기 위한 하나의 수단으로 이용된다.

nonatomic : 접근자와 변경자 메소드가 생성되는 방법을 바꾼다. 이 옵션에 대해 디폴트로 설정되어 있는 atomic의 경우 멀티 스레딩이 가능한 코드들을 추가로 생성하게 된다. 하지만 명시적으로 nonatomic을 설정함으로 멀티 스레딩의 가능성을 줄이고, 불필요한 것들로 인한 오버헤드를 상당부분 줄일 수 있다. 어느정도 자원 사용에 있어서 한계가 있는 아이폰 프로그래밍의 경우, 메모리에 사용에 대한 치밀한 접근이 필요하다.



아웃렛, 액션, 프로퍼티는 아이폰 프로그래밍을 하는데 있어서 필수적인 요소들이다. 어렵지 않은 역할을 가지고 있지만, 그 기본을 탄탄히 함으로써 좀더 규모있는 애플리케이션을 다루게 될때 당황하지 않고 적절하게 대응할 수 있는 것이다.

Posted by seanhigher

댓글을 달아 주세요

  1. 2010.10.12 00:38 신고 금메달.아빠  댓글주소  수정/삭제  댓글쓰기

    좋은 정보 감사합니다. 구글에서 검색하였더니 http://blog.daum.net/yellowjini/5216139 에도 이글과 똑같은 글이 나오는 군요. 누가 원본인지 모르겠지만, 블로그 올리신 시각이 빠른 분께 이렇게 댓글을 남겨 둡니다.
    아이폰을 다루지 못해봐서 아직 이해하기에 먼 글입니다. 아이폰을 다룰 기회가 되면 다시 들러봐야 겠습니다.

    행복한 하루 되세요.

최근에 달린 댓글

글 보관함