아이폰 3.0 SDK 부터는 accelerometer를 사용하지 않고도 UIResponder에 추가된 motion 이벤트 처리 메소드를 구현함으로써 간단하게 사용자의 흔들기 동작을 체크할 수 있습니다. 저도 처음 사용해 보면서 간단한 내용들을 정리해 보았습니다.

1. First responder 되기
사용자의 흔들기 이벤트를 처리할 ViewController는 그 자신이 First responder가 되어야 합니다. becomFirstResponder 메소드를 호출하고 canBecomeFirstResponder 메소드에서 YES를 반환합니다.

  1. - (void)viewDidAppear:(BOOL)animated {
  2.     [super viewDidAppear:animated];
  3.     [self becomeFirstResponder];
  4. }
  5.  
  6. - (BOOL)canBecomeFirstResponder {
  7.     return YES;
  8. }

viewDidAppear는 코드에서 서브뷰로 추가될 때만 호출됩니다. IB에서 바로 Window에 View를 추가하였으면 awakeFromNib등의 메소드에서 becomFirstResponder를 호출하셔야 합니다.

2. motion 메소드 구현
이후로는 간단합니다. 사용자의 흔들기가 시작되면 해당 motionBegan이 호출되고 종료될 때 motionEnded가 호출됩니다. 지나치게 많이 흔들거나 하여 유효하지 않은 흔들기로 판단될 때는 motionCancelled가 호출됩니다.

  1. - (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
  2.     NSLog(@"Shaking start");
  3. }
  4.  
  5. - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
  6.     NSLog(@"Shaking end");
  7. }
  8.  
  9. - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
  10.     NSLog(@"Shaking cancel");  
  11. }
  12.  

motionEnded 메소드에 사용자의 흔들기가 끝난 후 실행할 코드를 추가하면, 간단하게 흔들기를 지원할 수 있습니다.


보통 Table View의 Cell은 코드로 만드는 경우가 많습니다. 여기서는 인터페이스빌더에서 Table View Cell을 사용하는 간단한 샘플을 만들어 보겠습니다.





1. 프로젝트 생성과 AppDelegate 변경
Xcode를 실행하고 "New Project"를 클릭하여 "Window-based Application"을 선택하여 프로젝트를 생성합니다. 저는 그냥 IBCell이란 이름으로 프로젝트를 만들었습니다.

1) IBCellAppDelegate.h

@class TableViewController;


@interface IBCellAppDelegate : NSObject <UIApplicationDelegate> {


IBOutlet TableViewController *tableViewController;

    UIWindow *window;

}

2
인터페이스 빌더에서 연결을 위해 tableViewController의 아울렛 변수를 추가합니다.

2) IBCellAppDelegate.m

- (void)applicationDidFinishLaunching:(UIApplication *)application {

  

[window addSubview:[tableViewController view]];

    // Override point for customization after application launch

    [window makeKeyAndVisible];

}


메인 윈도우의 SubView로 tableViewController의 View를 추가합니다.

2. ViewController
1) 생성
UIViewController subclass로 TableViewController란 이름으로 클래스를 생성합니다. SDK 3 부터는 "With XIB for user interface"가 있어 선택할 경우 ViewController 오브젝트와 연결된 별도의 xib를 자동으로 생성하여 줍니다. 여기서는 편의를 위해 선택하지 않았습니다.
* 2009/06/26 추가
오늘 확인해 보니 Objective-C class 선택시에 하단에 Subclass of로 선택할 수 있는 클래스들이 나오네요. 버젼업되면서 왜 줄었나 했더니 여기로 들어 가 있었습니다. 위의 방법말고 아래에서 UITableViewController를 바로 선택하고 사용하시면 됩니다.
 

2) TableViewController.h

#import <UIKit/UIKit.h>



@interface TableViewController : UITableViewController {

IBOutlet UITableViewCell *myCell;

}


- (IBAction)goMyBlog:(id)sender;


@end


부모 클래스를 UIViewController에서 UITableViewController로 변경합니다. 그리고 인터페이스빌더에서 연결을 위해 myCell 아울렛 변수를 추가합니다. 그리고 버튼 클릭시 연결될 goMyBlog 액션 메소드를 선언합니다.

3) TableViewController.m

/* 버튼 클릭시 */

- (IBAction)goMyBlog:(id)sender {

NSURL *url = [NSURL URLWithString:@"http://www.cocoadev.co.kr/"];

[[UIApplication sharedApplication] openURL:url];

}


#pragma mark Table view methods


/* 색션 */

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return 1;

}


/* 색션 타이틀 */

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

return @"소개";

}


/* */

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

return myCell;

}


/* 높이 */

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

return [myCell frame].size.height;

}


버튼 클릭시 실행될 메소드와 tableView의 delegate, datasource 메소드를 구현합니다.



3. 인터페이스 빌더

1) Table View Controller

인터페이스 빌더를 실행하고 라이브러리의 Controlls의 Table View Controller를 MainWindow.xib로 드래그해서 가지고 옵니다. Class를 TableViewController로 지정합니다.



좌측과 같이 Table View의 Style 속성을 Grouped로 지정합니다.



2) Table View Cell

라이브러리에서 Table View Cell을 MainWindow.xib로 드래그해서 가지고 옵니다.




아래와 같이 TableViewController의 myCell 아울렛에 가져온 Table View Cell을 연결합니다.


사용자 삽입 이미지
라이브러리 윈도우에서 Image View, Label, Rounded Rect Button, Text View를 드래그 해서 Table View Cell에 가져다 놓습니다.



Cell의 높이를 늘리고 각각의 컨트롤들을 아래와 같이 배치합니다. 적당한 이미지를 Xcode 좌측의 Groups & Files 아래의 Resources 그룹으로 드래그해서 프로젝트로 가져옵니다. Image View의 Image 속성에서 해당 이미지를 선택합니다. 입력이 되지 않도록 Text View의 Editable 속성의 체크를 해제합니다.
사용자 삽입 이미지

버튼 클릭시 사파리에서 해당 URL로 이동하기 위해 Text View Controller의 goMyBlog IBAction과 연결합니다.


4. 확인
Xcode에서 Build & Go를 클릭하면 시뮬레이터에서 아래와 같이 확인하실 수 있습니다.
SDK 3을 대충 보기만 하고 처음 사용해 보았는데 곳곳에 바뀐 부분이 눈에 뛰네요. SDK 3도 처음이고 오랫만에 포스팅이라 틀린 부분이 있는지도 모르겠습니다.

'iOS' 카테고리의 다른 글

아이폰 OS 4  (8) 2010.04.09
NSXMLParser로 RSS 읽어오기  (21) 2009.08.05
인터페이스빌더 Table View Cell 사용하기  (0) 2009.06.25
cocos2d 개발환경 설정  (24) 2009.04.13
iPhone SDK 3.0 beta 2  (4) 2009.04.05
UITableView의 메모리 누수 현상  (4) 2009.02.05

iPhone 어플리케이션은 Xcode란 개발툴에서 코코아터치 프레임워크와 Objective-C 언어를 사용하여 개발합니다. 코코아 터치는 맥 OS X의 핵심 프레임워크인 코코아를 기본으로 아이폰/터치라는 모바일 기기의 특성에 맞추어진 iPhone 개발의 기본 API입니다.

그렇기 때문에 아이폰 어플리케이션 개발을 위해 기본적으로 알아야할 사항은 통합개발 환경인 Xcode와 인터페이스 빌더라는 툴의 사용법, Cocoa Touch 프레임워크, Objective-C 크게 세가지입니다. 이번에는 간단히 iPhone OS의 계층구조를 중심으로 iPhone 어플리케이션 제작을 위해 접근하는 방법에 대해서 알아 보겠습니다.

1. iPhone OS 계층 구조
아래의 그림은 애플에서 설명하는 아이폰 OS의 기술 계층입니다. 이에 대한 자세한 내용은 아이폰 개발자 센터iPhone OS Technology Overview란 문서에 잘 정리되어 있습니다.


가장 로우레벨의 Core OS부터 가장 상위단계인 코코아 터치까지의 계층구조입니다. 주로 하단은 API가 C로 제공되며 상위로 올라 오면서 Objective-C로 제공됩니다. 많이 사용되는 로우레벨의 API들은 상위단계에서 쉽고 편하게 사용할 수 있는 프레임워크로 제공됩니다. 간 계층의 간단한 설명은 아래와 같습니다.

1) Core OS
메모리/프로세서 관리, 파일 시스템, 네트워크, 각종 하드웨어 드라이버등, 운영체제 하단의 커널 레벨에서 제공하는 커널 API 입니다. 흔히 이야기하는 시스템 프로그래밍에 관련된 C를 기반으로 한 라이브러리를 제공합니다.

2) Core Services
Core Foundation, CFNetwork, SQLite, POSIX threads와 같은 파일입출력, 저수준 데이터 타입, 소켓등에 관련된 서비스입니다. Core OS 레벨에 비교적 쉽게 접근할 수 있는 API를 제공합며 대부분 C로된 프레임워크를 제공됩니다. 여기서 제공하는 많은 기능들은 상단 코코아터치의 Foundation Framework에서 Objective-C 프레임워크로 제공됩니다.

  • Core Foundation - 배열, 스트링, 날짜, URL, 로우레벨 데이터등 아이폰 어플리케이션을 위한 기본적인 C API를 제공합니다.
  • CFNetwork - BSD 소켓 및 HTTP, FTP 프로토콜등 네트워크에 쉽게 접근할 수 있는 API를 제공하는 프레임워크입니다.
  • Core Location - GPS, 주변검색등에 사용할 수 있는 사용자의 현재 위치(위도, 경도) 정보와 관련된 API를 제공하는 프레임워크입니다.
  • SQLite - 아이폰 어플리케이션에서 쉽게 사용할 수 있는 파일기반의 경량 데이터베이스 입니다. SQLite에 관련된 보다 자세한 내용은 이전 포스팅을 참조해 주세요.
  • XML - XML 파싱을 위한 libXML2 라이브러리를 제공합니다.

이외에 보안, 주소록등에 관련된 서비스를 제공합니다.

3) Media
비디오, 오디오, 2D/3D 그래픽, 에니메이션을 구현할 수 있는 API를 제공합니다. Objective-C 또는 C로된 API를 제공합니다.

  • Quartz - OS X의 벡터를 기반으로 한 그래픽 엔진입니다. 선과 도형을 그리고 이미지, 비트맵, PDF를 출력하고 색상, 위치에 관련된 C로된 API를 Core Graphic 프레임워크를 통해 제공합니다.
  • Core Animation - 각종 에니메이션과 시각효과를 제공하는 Objective-C로된 프레임워크입니다.
  • OpenGL ES - 게임등과 같은 고성능의 2D/3D 그래픽 출력을 위한 OpenGL ES 1.1에 기반한 C 프레임워크입니다. OpenGL ES를 사용한 게임은 이를 지원하는 다양한 모바일 플랫폼에서의 포팅을 쉽게 할 수 있습니다. 자세한 내용은 공식 홈페이지를 참조하시기 바랍니다.
  • Core Audio - 마이크를 통해 녹음하고 음악파일을 출력하고 각종 음향효과를 제공하는 C로된 오디오 관련 프레임워크입니다.
  • OpenAL - OpenGL ES와 유사한 개념의 크로스 플랫폼을 지원하는 게임등을 위한 고성능 3D 오디오 라이브러리입니다. 자세한 내용은 공식 홈페이지를 참조 하시기 바랍니다.
  • 동영상 - mov, mp4, 3gp와 같은 각종 동영상 파일을 출력을 지원합니다. 이는 Objective-C를 기반으로 한 Media Player framework를 이용하여 손쉽게 접근할 수 있습니다.

4) Cocoa Touch
iPhone개발의 기본이 되는 계층으로 Objective-C를 기반으로 한 핵심적인 두개의 프레임워크를 가지고 있습니다. 사용자 인터페이스, 이벤트 처리등과 함께 위에서 언급한 로우레벨의 기술들을 보다 쉽게 사용할 수 있는 프레임워크를 제공합니다.

  • Foundation Framework - 배열, 스트링, 날짜 로우레벨 데이터등에 관련된 클래스를 제공하는 기본적인 프레임워크입니다. 위의 Core Foundation에서 제공하는 기본 API들의 Objective-C 레퍼 클래스를 제공합니다.
  • UIKit Framework -각종 컨트롤, 윈도우등의 UI, 이벤트 처리등 iPhone 어플리케이션의 사용자 인터페이스와 관련된 API를 제공하는 프레임워크입니다.


2. Mac or iPhone
Mac의 Cocoa와 iPhone의 Cocoa touch는 이름에서 부터 알 수 있듯이 매우 비슷합니다. 가장 큰 차이점은 역시 인터페이스에 관련된 부분으로 이와 관련하여 Mac에서는 AppKit이란 프레임워크를 iPhone에서는 UIKit이란 프레임워크를 제공합니다.

Foundation Framework는 거의 유사하지만 아이폰의 특성상 제거 또는 변경된 부분이 있습니다. 아이폰은 코코아 바인딩, Objective-C 2.0의 가비지 컬렉션, 애플스크립트, NSUndoManage등은 지원하지 않습니다.

개인적으론 시간이 충분하다면 맥에서 코코아 프로그래밍으로 시작한 후에 코코아 터치로 넘어가도 큰 어려움은 없을 것 같습니다.


3. C or Objective-C
대표적인 코코아 프로그래밍 서적인 Cocoa Programming for MAC OS X (번역본:코코아 프로그래밍)의 저자 아론 힐리가스는 그의 저서에서 아래와 같이 이야기 했습니다.

"C와 Java/C++같은 객체지향 언어를 알고 있다면 두시간이면 Objective-C를 마스터할 수 있다"

OOP와 프로그래밍 언어에 대한 개념만 있다면 Objective-C를 익히는 것은 쉽다라는 의미인 것 같습니다.

사실 어느 언어나 기본지식만 있으면 언어 자체를 익히는데는 그다지 어려움이 없습니다. 하지만 해당 플랫폼에 대한 지식과 핵심 라이브러리나 프레임워크를 배우는데 더욱 많은 시간을 소비해야 합니다. 아이폰도 Xcode, 인터페이스빌더의 사용법, OS X에서의 프로그래밍에대한 이해와 코코아 API를 배우는데 Objective-C 언어 자체를 배우는 것보다 더 많은 시간이 필요합니다.

1) C의 선행학습이 필요한가?
개발경험이 있으신 분들은 그동안의 경험을 바탕으로 어떻게 공부해야 할지 나름대로 방법이 있을 것입니다. 하지만 처음 시작하시는 분들은 Objective-C가 C를 기반으로 하고 있기 때문에, 반드시 C를 공부한 후에 Objective-C를 공부해야하는 지에 대해 질문을 하시는 분들이 있습니다.

이 부분은 아마 많은 분들이 차이가 있을 것 같습니다. 제 생각은 "반드시 필요하지는 않다" 입니다. Objective-C는 C에서 확장된 슈퍼셋이라고 하지만 Objective-C를 공부하기 위해 반드시 C를 먼저 공부해야 할 필요는 없을 것 같습니다.

C를 이해한 후에 Objective-C를 시작하는 것이 이론상으로도 맞고, 정상적인 방법일 것입니다. 하지만 대부분의 Objective-C 서적이나 메뉴얼에는 기본적인 문법에 대한 설명이 있고, 전문 C 서적보다는 범위가 작습니다. Objective-C를 사용할 수 있을 만큼 최소한의 문법만 알고 시작하는 것이 더 접근이 쉬울 것 같습니다.

아래는 iPhone 어플케이션의 샘플 소스중에 한부분입니다. 기존에 C/C++ 개발자들도 이런 Objective-C의 문법을 처음 보게되면, 이것이 C와 관련이 있고 C에서 확장되었다는 사실이 잘 이해가 가지 않을 것입니다.
 

처음 시작하시는 분이라면 차라리 이런 혼란을 피하고 코코아 어플리케이션을 바로 제작할 수 있는 Objective-C로 시작하는 것도 한 방법이라고 생각됩니다.

2) 접근방법
사실 가장 좋은 것은 아래와 같이 가장 로우레벨단계 부터 이해하고 올라 가는 것이 기초도 탄탄하고 가장 좋은 방법일 것입니다.

  1. 메모리/CPU등 컴퓨터 하드웨어에 대한 이해
  2. OS에 대한 이해
  3. 컴파일러에 대한 이해
  4. C언어
  5. 시스템 프로그래밍
  6. 자료구조/알고리즘
  7. Objective-C
  8. OOP, 디자인 패턴
  9. Cocoa API

위의 단계대로 차례로 지식과 실력을 쌓아서 접근하면 좋겠지만, 당장 아이폰 어플리케이션을 만들고 싶은데 지루하고 많은 시간을 필요로 합니다. 흥미를 잃지 않고 접근하는 방법은 위의 순서와 반대로 접근하는 것입니다.

Objective-C에 대한 메뉴얼을 대충(?) 한번 읽어 보고 바로 책, 웹사이트, 동영상등의 간단한 튜토리얼등을 따라해 보면서 실제 실행되는 모습을 보면 계속 흥미를 유지할 수 있습니다.

그후에 어느정도 감이 생기면 직접 만들고 싶은 어플리케이션을 목표로 잡습니다. 첫 목표는 과한 욕심은 버리고 간단하고 쉬운 어플리케이션 부터 시작합니다. 아무리 간단해도 따라해 보며 만들었던 것과는 달리 원하는 기능을 직접 구현하는 것은 매우 어렵습니다. 자료도 많이 찾아 보아야되고, 다른 샘플 소스에서 복사해 와야 하는 경우도 있고, 관련 커뮤니티를 통해 도움을 받을 수도 있습니다. 관련자료들은 이전의 iPhone 어플리케이션 개발을 위한 준비 - 3. 관련 자료 포스팅을 참고하시기 바랍니다.

우여곡절끝에 만들어 내면 부족한면이나 필요한 부분이 무엇인지 조금씩 보이게 될 것 입니다. 필요에 의해 공부를 하다보면 아마 위의 순서를 거슬러 올라 접근하게 되는 경우가 많을 것 같습니다. 그러면 차차 복잡하고 어려운 어플리케이션을 만들 수 있을 것입니다.

막상 처음 시작하게 되면 알아야 할 것은 많고 답답한 마음만 드실 것입니다. 하지만 안타깝게도 왕도는 없습니다. 흥미와 열정을 유지하면서 차근차근 해 나가다 보면 어느새 바라는 어플리케이션을 만들수 있는 날이 눈앞에 와있을 것 입니다.

간혹 주위에 개발경험이 전혀 없는 분들로 부터 "무엇부터 시작해야 아이폰 어플리케이션을 만들 수 있냐?"는 질문을 들으면서 이와 관련해서 간단히 포스팅을 해보아야 겠다고 생각했습니다.

정리는 해보았는데 역시 어렵네요. 제가 봐도 이상한 용어들만 난무하고 설명도 중구난방이고, 개발을 처음 하시는 분들이 보면 잘 이해가 안가실 것 같다는 생각이 듭니다.

* 관련링크
iPhone 어플리케이션 개발을 위한 준비 - 1. 하드웨어
iPhone 어플리케이션 개발을 위한 준비 - 2. 소프트웨어
iPhone 어플리케이션 개발을 위한 준비 - 3. 관련 자료
iPhone 어플리케이션 개발을 위한 준비 - 4. 시작하기

iOS 2008.11.22 14:33
오늘 맥을 켜니 아이튠즈와 함께 아이팟 터치 펌웨어도 2.2로 업그레이드 되었습니다. 애플 사이트에서 가져온 아이폰 펌웨어 2.2에서 변경된 내역은 아래와 같습니다.

* Enhancements to Maps
  • Google Street View*
  • Public transit and walking directions
  • Display address of dropped pins
  • Share location via email
* Decrease in call setup failures and dropped calls

* Enhancements to Mail
  • Resolved isolated issues with scheduled fetching of email
  • Improved formatting of wide HTML email
* Podcasts are now available for download in iTunes application (over Wi-Fi and cellular network)

* Improved stability and performance of Safari

* Improved sound quality of Visual Voicemail messages

* Pressing Home button from any Home screen displays the first Home screen

* Preference to turn on/off auto-correction in Keyboard Settings

사파리로 브라우징시에 간혹 튕기는 현상이 있었는데, 사파리의 안정화 부분 정도에만 눈길이 가네요. 캘린더에 TODO 기능과 iCal과의 동기화가 되었으면 좋겠는데, 애플에서 언제쯤 넣어줄지 모르겠습니다.

완료후에 iPhone DevCenter로 가보니 역시 SDK도 동일하게 2.2로 업그레이드 되었습니다. SDK를 새로 설치하니 Xcode도 3.1.1에서 3.1.2로 업그레이드 되었습니다.

IDE나 Core의 버젼이 올라갔지만 펌웨어에 맞추어 2.2로 업그레이된 것을 제외하고는 문서를 읽어보아도, 실행해서 대충 둘러 보아도 눈에 뛰게 변경된 점은 없는 것 같습니다.

등록신청하고 2주정도만에 겨우 완료했습니다. 단계마다 오류가 있어 애플과 몇번의 메일을 주고 받다가 오늘 무사히 등록했습니다. 등록후에는 아이폰 개발자 포탈에서 iPhone Developer Program Protal User Guide란 문서를 다운로드 받아 설명한대로 진행하니 별다른 어려움 없이 개발환경을 완료할 수 있었습니다.

편하게 사용할려고 App ID 생성시에 번들아이디를 와일드카드(*)로 지정했는데, 터치로 컴파일시에는 문제가 없었습니다. 배포시에는 문제가 될지 모르겠지마, 다시 생성하면 되니 일단은 저 App ID 하나로 쓰기로 했습니다.


모든 인증 완료 후에 아이팟 터치를 아이튠즈에서 동기화를 하고나니, 아래와 같이 설정/일반에 프로파일 항목이 추가되어 있습니다.


테스트를 해볼려고 Xcode를 실행하니 아래와 같은 오류가 났습니다.

SDK를 다시 설치할려다가 자료를 찾아 보니 아이팟 터치 2세대로 개발을 할 때는 아래와 같이 심볼릭 링크를 해주어야 한다고 합니다. 아래와 같이 심볼릭링크를 생성하니 위의 오류 메시지는 없어졌습니다.

> cd /Developer/Platforms/iPhoneOS.platform/DeviceSupport
> ln -s 2.1 2.1.1

프로젝트의 빌드 설정에서 아래와 같이 iPhone Developer를 제 이름으로 설정하고 Profile을 설정하고, 빌드를 하니 제 아이팟 터치에도 잘 올라가고 실행도 잘되네요.


이제는 뭘 만들어서 올리는 일만 남았는데, 뭘 만들어야 할지 모르겠네요.

PickerView, ImageView, TableView를 이용한 간단한 예제입니다. 좌측의 테이블뷰나 하단의 피커뷰에서 moveIn, push, reveal, fade 효과를 선택하면 우측의 이미지가 해당 효과로 다음 이미지와 변환되는 간단한 샘플입니다. 피커뷰의 2번째 컴퍼넌트에서는 효과가 진행되는 방향을 설정합니다. fade 효과에서는 이 방향이 적용되지 않습니다.

(사용한 샘플 이미지에 대해선 사과 드립니다)

이 샘플은 애플의 아이폰 DevCenter의 샘플코드중에서 UICatalogView Transitions 를 참조하였습니다.

1. 프로젝트 생성

Xcode의 메뉴에서 File / New Project를 클릭합니다. iPhone OS / Application을 선택하고 View-Based Application 템플릿을 선택하고 Choose..를 클릭합니다.


적당한 프로젝트명을 입력하고 Save를 클릭하여 프로젝트를 생성합니다. 여기서는 Control2로 생성하였습니다.


2. 소스코드 수정
Control2ViewController의 소스와 헤더 파일에 아래의 푸른색으로 되어 있는 부분을 입력합니다. 설명은 간단한 주석으로 대체하겠습니다.

1) Control2ViewController.h
#import <UIKit/UIKit.h>

@interface Control2ViewController : UIViewController {

    IBOutlet UITableView *myTableView;
    IBOutlet UIPickerView *myPickerView;
    IBOutlet UIView *myView;
   
    UIImageView *imageView1;
    UIImageView *imageView2;
   
    NSArray *effectArray;
    NSArray *directionArray;
}

@end

2) Control2ViewController.m
#import "Control2ViewController.h"
#import <QuartzCore/QuartzCore.h>

@implementation Control2ViewController

static NSString *kCellIdentifier = @"MyIdentifier";
static NSString *kAnimationKey = @"transitionViewAnimation";

- (void)viewDidLoad {
    [super viewDidLoad];
   
    // 에니메이션 효과들을 배열로 저장
    effectArray = [[NSArray arrayWithObjects:kCATransitionMoveIn, kCATransitionPush,
                    kCATransitionReveal, kCATransitionFade, nil] retain];
   
    // 에니메이션 시 진행방향을 배열로 저장
    directionArray = [[NSArray arrayWithObjects:kCATransitionFromLeft, kCATransitionFromRight,
                       kCATransitionFromTop, kCATransitionFromBottom, nil] retain];
   
    // 이미지 뷰 생성
    imageView1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"no1.png"]];
    imageView2 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"no2.png"]];
   
    // 이미지 뷰 크기 설정
    CGRect rect;
   
    rect.origin = CGPointZero;
    rect.size = [myView frame].size;
       
    [imageView1 setFrame:rect];
    [imageView2 setFrame:rect];

    // 첫번째 이미지 뷰 추가
    [myView addSubview:imageView1];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}


- (void)dealloc {
    [imageView1 release];
    [imageView2 release];
   
    [effectArray release];
    [directionArray release];
   
    [super dealloc];
}

#pragma mark Animation methods

/** 에니메이션 실행 */
- (void)transitionView:(NSUInteger)effect
{
    UIView* newView;
    UIView* oldView;
   
    // 나올 뷰와 사라질 뷰를 설정
    if ([imageView1 superview] == myView) {
        newView = imageView2;
        oldView = imageView1;
    } else {
        newView = imageView1;
        oldView = imageView2;
    }
   
    // 이전 뷰를 삭제하고 새로운 뷰를 등록
    [oldView removeFromSuperview];
    if (newView && ([newView superview] == nil)) {
        [myView insertSubview:newView atIndex:1];
    }
   
    // 에니메이션 효과와 방향을 설정
    NSString *transition = [effectArray objectAtIndex:effect];
    NSString *direction = [directionArray objectAtIndex:[myPickerView selectedRowInComponent:1]];
   
    // 에니메이션
    CATransition *animation = [CATransition animation];
   
    // 변환효과 설정
    if (transition == kCATransitionFade) {
        [animation setType:kCATransitionFade];
    } else {
        [animation setType:transition];
        [animation setSubtype:direction];
    }
   
    // 변환속도 설정
    [animation setDuration:0.75];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
   
    [[myView layer] addAnimation:animation forKey:kAnimationKey];
}   


#pragma mark TableView delegate/dataSource methods

/** 테이블뷰의 선택이 변경되었을 경우 */
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // PickerView의 효과 컴포넌트 선택 변경
    [myPickerView selectRow:[indexPath row] inComponent:0 animated:TRUE];
   
    // 에니메이션 실행
    [self transitionView:[indexPath row]];
}

/** 테이블뷰 섹션 수 */
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

/** 테이블뷰 row count */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [effectArray count];
}

/** 테이블뷰 각 항목 타이틀 설정 */
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (cell == nil)
    {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:kCellIdentifier] autorelease];
    }
   
    cell.text = [effectArray objectAtIndex:indexPath.row];
   
    return cell;
}

/** 색션 해더 타이틀 설정 */
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section
{
    return @"Effect";
}


#pragma mark PickerView delegate/dataSource methods

/** 피커뷰의 선택이 변경되었을 경우 */
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    // componet가 0(첫번째 컴퍼넌트)일 경우에만 처리
    if (component == 0) {
        // 테이블뷰의 선택된 row 변경
        [myTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:component]
                                 animated:NO
                           scrollPosition:UITableViewScrollPositionTop];
   
        // 에니메니션 실행
        [self transitionView:row];
    }
}

/** 피커뷰의 컴퍼넌트 수 설정 - 2(효과, 방향) */
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

/** 피커뷰의 각 컴퍼넌트의 row count */
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    if (component == 0)
        return [effectArray count];
    else
        return [directionArray count];
}

/** 피커뷰의 각 컴퍼넌트의 항목 타이틀 설정 */
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if (component == 0)
        return [effectArray objectAtIndex:row];
    else
        return [directionArray objectAtIndex:row];
}

@end


3. 인터페이스 빌더

이제 인터페이스 빌더에서 UI를 생성하고 속성들을 연결해 보겠습니다.

Groups & Files의 Resources 폴더에서 Control2ViewController.xib를 더블클릭하여 인터페이스 빌더를 오픈 합니다.

1) 컨트롤 배치
라이브러리 윈도우에서 View, TableView, PickerView를 각각 드래그 해서 아래와 같이
View에 배치합니다.

2) Outlet 연결
xib 윈도우에서 File's Owner를 우클릭 합니다. 여기서 File's Owner는 Control2ViewController 입니다. 각 Outlet들을 위에서 생성한 컨트롤들과 연결합니다. myPickerView를 PickerView와 myTableView를 TableView와 myView를 View로 연결합니다.
 

이제 작업이 완료되었습니다. 빌드를 하고 테스트를 하여 봅니다. 아래는 전체 프로젝트를 압축한 파일입니다.



맥 OS X의 코코아에서의 개발에 관심이 있는 분들이 그리 많지 않았습니다. 올해 초까지만 하더라도 인터넷을 제외하고 현실에서 코코아 개발에 관심을 가지고 있는 사람들을 직접 만난 적이 없었습니다. 간혹 지인들에게 '맥에서 개발의 재미'에 대해서 이야기를 해도 그냥 그런가 보다라고 시큰둥하게 받아 들이는 것이 대부분이었습니다.

하지만 애플에서 올해 초 iPhone SDK를 공개되고 App 스토어가 오픈되면서 개발자들의 관심이 서서히 높아가고 있는 것 같습니다.

좌측은 구글 접속통계에서 본 제 블로그의 올해 방문자 통계입니다.  제 블로그의 방문자 수는 시작부터 계속 일직선으로 항상 일정했습니다. 하지만 애플의 App 스토어가 오픈되면서 부터 조금씩 증가하기 시작했습니다. 한낱 개인 블로그의 방문자 수로 단정 짓기는 어렵지만 관심있는 업체나 개인이 조금씩 늘어 가고 있구나 하고 짐작하고 있습니다.

이는 아마존에서 나오는 신간을 봐도 알 수 있습니다. 이전에는 Xcode나 코코아에 관련된 책들은 가뭄에 콩나듯이 새책이 나왔습니다. 그런데 요즘은 아마존에서 오는 신간 안내 메일만 봐도 허풍을 조금 보태면 아이폰 관련 서적이 자고 일어나면 하나씩 나오는 것 같습니다.

사용자 삽입 이미지
(출처:amazon.com)

그외에도 우리나라에서는 osxdev.org외에는 거의 전무했던 맥 개발 관련 사이트와 까페들이 조금씩 늘어 가고 있습니다.


저도 한 까페에 가입하여 오브라인 모임도 자주 참석하고 글도 쓰면서 나름 열심히(?) 참여있습니다. 회원들이 대학생들이 많아 나이 차이가 꽤 크지만 같은 관심을 가진 분들과 교류한다는 것은 즐거운 일인 것 같습니다.

이야기를 들어 보면 역시 맥 보다는 아이폰에서의 개발에 관심을 가진 업체나 개인들이 많았습니다.

마케팅과 세일즈에 크게 노력을 하지 않고 개발 자체에만 집중하면 된다는 점과 App 스토어라는 세계를 상대로 하는 커다란 시장이 개발자들에게 매력적으로 다가 오지 않았나 하는 생각이 듭니다. 그리고 아직 초창기이기 때문에 많은 가능성도 가지고 있고요.

하지만 막상 우리나라에서 아이폰 출시는 루머만 무성하고 난항을 겪고 있습니다. 저는 개발 용도가 아닌 제 일상 생활의 편의와 재미를 위해서 간절히 출시를 바라고 있는데, 더 이상 조바심 내지말고 출시되기 전까지는 신경을 끄고 있기로 했습니다.

일본에서는 아이폰 판매가 기대보다는 저조했다고 하던데, 한국에서 출시가 되면 결과가 궁금해집니다. 제 개인적인 생각으로는 폭발적인 인기는 없을 것 같습니다.

일단 기본 용도가 휴대폰인데 많은 사람들이 휴대폰을 선택할 때는 기능보다는 디자인, 유행, 가격등으로 선택하고, 기능도 카메라 화소수, 멀티미디어 플레이어, DMB등의 휴대폰에서 중요시 하는 기능만 보면 더 우수한 다른 휴대폰들이 많기 때문입니다.
(출처: apple)

맥과 모바일미, 아이튠즈와 같이 사용하지 않고, 컴퓨터를 사용할 때와 같이 사용자들의 노력이 없다면 아이폰의 장점은 '보는 사람마다 다른 주관적인 디자인'외에는 많지 않은 것 같습니다. 하지만 그 디자인과 OS X를 기반으로 한 여러 이점때문에 스마트폰으로서는 괜찮은 판매량을 보일 것 같습니다.

맥을 쓰는 사람 보다 나온지 얼마 안된 아이팟 터치를 쓰는 사람들이 훨씬 많은데, 아마 터치 보다는 많이 팔리지 않을까 하는 생각이 듭니다. 이런 마케팅, 비즈니스 쪽으로는 문외한이라 빨리 뚜껑을 열어 보고 결과를 확인할 날이 빨리 왔으면 좋겠습니다.

사용자 삽입 이미지
맥과는 달리 아이폰에서는 아이콘 파일(*.icns)을 사용하지 않고, 57X57 사이즈의 PNG 파일 포맷을 사용하는 것 같습니다.





사용자 삽입 이미지
재미있는 것은 좌측이 원본 아이콘 이미지이고 우측이 아이폰의 홈스크린에서 보여지는 모습입니다. 시스템에서 자동으로 아이콘의 모서리를 둥글게 보여주고 명함 효과를 주기 때문에 이미지에서 별도의 작업이 필요 없습니다.

사용자 삽입 이미지
만약 이미지에 배경이 없고 투명색으로 처리되었을 경우에는 좌측과 같이 검정색 배경의 아이폰 아이콘의 형식으로 출력이 됩니다.




* 아이콘 파일 등록
아이콘 파일은 메뉴의 'Project/Add to Project...'나 Groups & Files에서 해당 디렉토리를 우클릭 한 후에 아래와 '같이 Add/Exsiting Files...'를 클릭하여 해당 파일을 선택하시면 됩니다.

사용자 삽입 이미지


* 아이콘 파일 지정
사용자가 지정하지 않으면 실행파일 번들내의 Icon.png를 기본 아이콘 파일로 인식합니다. 별도로 아이콘 파일을 지정할 경우에는 Info.plist 또는 아래와 같이 타겟의 Info Properties의 'Icon File:' 항목에서 png 확장자를 제외한 파일명을 입력합니다. 

사용자 삽입 이미지

이전 장에 이어서 이제 인터페이스빌더에서 작업을 하고 어플리케이션을 완성해 보겠습니다. 속성 변경 및 연결에 관한 자세한 설명을 생략하였습니다. 혹시 이부분에 이해가 가지 않으시면 이전 포스팅을 참고하시기 바랍니다.

3. MainView.xib
1) 이미지 추가
어플리케이션에서 사용하는 배경과 각종 이미지들을 프로젝트에 추가합니다. 사용된 이미지는 아래의 압축파일을 다운로드하여 사용하시거나 직접 그려서 사용하셔도 됩니다.


Xcode의 Groups & Files내의 Resources 디렉토리를 우클릭 한후에 Add/Existing Files...를 클릭합니다.  파일 선택창에서 배경과 아이콘들을 선택한 다음 Add 버튼을 클릭합니다. 그리고 MainView.xib 항목을 더블클릭 하여 인터페이스빌더를 엽니다. 라이브러리의 Media 탭을 클릭하면 등록된 이미지를 확인할 수 있습니다.


2) 레이아웃
라이브러리 윈도우에서 각각의 오브젝트들을 드래그해서 아래와 같이 Main View에 배치합니다. 이미지도 위의 라이브러리 윈도우에서 드래그해서 가져옵니다. 주의하실 점은 눈에 보이지 않더라도 하단 툴바의 중앙에 Bar Button Item이 있습니다.
 


3) 속성 변경 및 연결
이제 각 항목의 속성을 변경하고 아울렛을 연결하는 작업을 해보겠습니다. 각 오브젝트와의 연결은 MainView.xib의 File's Owner (MainViewController)를 우클릭하여 설정합니다. 

* MainView
배경색을 원하시는 색상으로 변경합니다.


File's Owner의 mainView 아울렛에 연결합니다.


* Navigation Bar
상단바의 타이틀을 더블클릭하여 좌측과 같이 변경합니다.


* View
월별로 바이오리듬 그래프를 보여주는 View 입니다.
배경색을 검정으로 변경합니다. 실행시에는 배경이미지로 대체되기 때문에 큰 의미는 없습니다(생략가능).

Class 항목을 이전 장에서 만들어 두었던 GraphView를 선택합니다.

File's Owner의 graphView 아울렛에 연결합니다.


* TextField
일별로 바이오리듬 보여주는 필드입니다. 3개 모두 아래와 같이 설정합니다.
정렬을 중앙으로 설정합니다.

사용자의 입력에 반응하지 않도록 선택을 해제합니다.

위에서 부터 차례로 File's Owner의 value_1, value_2, value_3 아울렛과 연결합니다.


* Bar Button Item
버튼들을 더블클릭하여 좌측과 같이 입력상태가 되면 각각 "<<", "<", ">", ">>"을 입력합니다.

각 버튼을 구별할 수 있도록 속성창 하단에 위치한 Tag를 설정합니다. "<<"는 1, "<"는 2, ">"는 3, ">>"는 4를 각각 입력합니다.

네 버튼 모두 File's Owner의 NavigationButtonClicked: 액션에 연결합니다.


* 날짜 표시 Bar Button Item
현재 선택된 날짜를 표시하는 툴바의 중앙에 위치한 버튼 아이템입니다. Plain 스타일에 타이틀이 없기 때문에 위의 이미지에서 툴바와 구별되지 않았습니다.
 
스타일을 Plain으로 설정합니다. 버튼 스타일로 출력되지 않습니다.

사용자의 입력을 받지 않기 위해 선택을 해제합니다.

File's Owner의 currentDate 아울렛에 연결합니다.


File's Owner의 연결상태는 아래와 같습니다.



4. FlipsideView.xib
1) 레이아웃
라이브러리 윈도우에서 Label과 Date Picker를 드래그해서 아래와 같이 Flipside View에 배치합니다.


2) 속성 변경 및 연결
* Label
텍스트를 "생일을 선택해 주세요." 로 변경합니다.

텍스트 색상을 흰색으로 변경합니다.



* Date Picker
Date Picker의 Mode를 Date로 설정합니다.


유효날짜를 설정합니다.


File's Owner의 datePicker 아울렛에 연결합니다.


Files's Owner의 연결상태는 아래와 같습니다.


5. MainWindow.xib
MainWindow.xib에서 Root View Controller를 더블클릭합니다. 해당 창의 우측 하단을 보시면 좌측과 같은 버튼이 있습니다. 기본 위치가 툴바의 위치와 맞지 않기 때문에 위치를 우측과 아래로 더 이동합니다. 


6. 테스트
이제 모든 작업을 완료하였습니다. 모두 저장하고 빌드한 후에 테스트를 해봅니다. 아래와 같이 정상적으로 바이오리듬이 출력되고 버튼이 동작하는지 확인합니다.



이상으로 간단한 바이오리듬 어플리케이션을 만들어 보았습니다. 혹시 이상한 점이 있으시면 아래의 소스파일을 다운로드 받아서 비교해 보시기 바랍니다.


이번에는 저번 바이오리듬 보다 조금 기능을 추가하여 만들어 보겠습니다. 상단에는 한달 단위로 그래프를 보여주고, 하단에는 해당일의 바이오리듬 정보를 보여 줍니다. 그리고 생일설정을 저장할 수 있도록 하겠습니다. 하단 버튼들의 기능은 아래와 같습니다.

  • << : 한달 전으로 이동
  • < : 하루 전으로 이동
  • > : 하루 후로 이동
  • >> : 한달 후로 이동
  • i : 생일 설정 

한가지 오류가 있습니다. 생일설정 시에 기존의 저장된 날짜가 DatePicker에 설정되지 않습니다. 값은 정확한데 보여지는 부분은 minimumDate로 초기에 선택되어져 있는 것 같습니다. 검색을 해봐도 같은 증상을 겪은 경우는 보았는데, 원인과 해결책은 찾지 못했습니다.


완성된 모습은 아래와 같습니다.

사용자 삽입 이미지


1. 프로젝트 생성
사용자 삽입 이미지
Xcode의 메뉴에서 File/New Project 클릭합니다. iPhone OS의 applicaion에서 좌측과 같은 Unility Application을 선택하고 Choose 버튼을 클릭합니다. 프로젝트 이름에 'iBiorhythm'을 입력한 후에 저장합니다.

Unility Application은 컨텐츠를 보여주는 main view와 설정등을 할 수 있는 flipside view를 제공하며, 두 뷰의 변환시에 아래와 같은 에니메이션 효과를 제공합니다.

사용자 삽입 이미지

사용자 삽입 이미지
Xcode의 Groups & Files를 보시면 좌측과 같이 그룹이 생성되어 있습니다. MainView, FlipsideView 각각에는 view와 view를 관리하는 controller 소스들이 있습니다.


2. 소스코드 
1) Global.h
사용자 삽입 이미지
여러 소스에서 사용할 상수나 변수등을 위해 헤더 파일을 하나 만듭니다. File/New File...을 클릭합니다. Mac OS X에서 C and C++ 항목을 클릭하고 좌측과 같은 Header File이란 아이콘을 선택한 후에 파일명을 Global.h로 입력하고 저장합니다. 생성된 파일에 아래와 같은 내용을 추가 합니다.

#define    MAX_DATATYPE            3

#define US_BIRTHYEAR_KEY        @"birth_year"
#define US_BIRTHMONTH_KEY     @"birth_month"
#define US_BIRTHDAY_KEY          @"birth_day"

#define NM_BIRTHDAYCHANGED   @"NTBirthdayChanged"

#define NAV_PREVMONTH            1
#define NAV_PREVDAY                 2
#define NAV_NEXTDAY                 3
#define NAV_NEXTMONTH            4

#define ONEDAY_SECOND            (24 * 60 * 60)

2) GraphView
그래프를 출력하기 위한 View를 생성합니다. 메뉴에서 File/New File...을 클릭 후에 iPhone OS/Cocoa Touch Classes 항목에서 UIView subclass를 선택한 후에 Next 버튼을 클릭합니다. 파일명에 GraphView.m을 입력한 후에 저장합니다.

* GraphView.h
#import <UIKit/UIKit.h>

@interface GraphView : UIView {
    UIImage                *backgroundImage;
   
    NSDateComponents    *currentDate;
    NSDateComponents    *birthDate;
   
    float biorhythmData[31][MAX_DATATYPE];
}

@property (nonatomic, retain) NSDateComponents *currentDate;
@property (nonatomic, retain) NSDateComponents *birthDate;

- (void)setBirthDate:(int)year atMonth:(int)month atDay:(int)day;
- (void)setCurrentDate:(int)year atMonth:(int)month atDay:(int)day;
- (void)changeDate:(int)pos;

- (NSDateComponents *)currentDate;

- (void)resetBiorhythmData;
- (float *)getBiorhythmDayData:(int)day;

@end

* GraphView.m
#import "Global.h"
#import "GraphView.h"

@implementation GraphView

@synthesize currentDate;
@synthesize birthDate;

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)awakeFromNib {
    currentDate = [[NSDateComponents alloc] init];
   
    /* 배경 이미지로 사용할 bio.png 로드 */
    backgroundImage = [[UIImage alloc] initWithContentsOfFile:
                       [[NSBundle mainBundle] pathForResource:@"bio" ofType:@"png"]];
}

- (void)drawRect:(CGRect)rect {
#define START_X            17    // 그래프 시작 X 위치
#define START_Y            22  // 그래프 시작 Y 위치
#define DAY_WIDTH        10  // 일별 간격
#define BOTTOM_HEIGHT    4   // 하단 공백 높이

    /* 데이터별 색상 테이블 */
    static const CGFloat color[MAX_DATATYPE][5] = {
        { 1.0, 0.0, 0.0, 1.0 }, //red
        { 0.0, 1.0, 0.0, 1.0 }, //green
        { 0.0, 0.0, 1.0, 1.0 }  //blue
    };
   
    CGSize viewSize = rect.size;
    CGContextRef context = UIGraphicsGetCurrentContext();

    /* 배경 이미지 출력 */
    CGPoint point;
    point.x = point.y = 0;
    [backgroundImage drawAtPoint:point];

    /* 오늘 날짜를 가르키는 위치는 노란 선으로 출력 */
    CGContextBeginPath(context);
    CGContextSetLineWidth(context, 1.0);
    CGContextSetRGBStrokeColor(context, 1.0, 0.8, 0.0, 1.0);
    CGContextMoveToPoint(context, START_X + (DAY_WIDTH * ([currentDate day] - 1)), START_Y);
    CGContextAddLineToPoint(context, START_X + (DAY_WIDTH * ([currentDate day] - 1)), viewSize.height - BOTTOM_HEIGHT);
    CGContextStrokePath(context);
   
    /* 바이오리듬 출력 */
    int i, j;
    int lineX = START_X;
   
    int vcenter = START_Y + (viewSize.height - START_Y - BOTTOM_HEIGHT)/2; // 그래프의 Y 중간 좌표
   
    /* 바이오리듬 출력 */
    for (i = 1; i < 31; i++) {
        for(j = 0; j < MAX_DATATYPE; j++) {
            CGContextBeginPath(context);
            CGContextSetLineWidth(context, 2.0);
           
            CGContextSetStrokeColor(context, color[j]);
           
            CGContextMoveToPoint(context, lineX, vcenter - biorhythmData[i-1][j]);
            CGContextAddLineToPoint(context, lineX + DAY_WIDTH, vcenter - biorhythmData[i][j]);
            CGContextStrokePath(context);
        }
        lineX += DAY_WIDTH;
    }
}

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

- (void)resetBiorhythmData {
    static const double s_values[MAX_DATATYPE] = {
        23.0, 33.0, 28.0 // 신체, 지성, 감성
    };
   
    /* 현재 설정된 날짜의 1일 부터 생일 사이의 날수를 구한다. */
    NSDateComponents *firstDate = [[NSDateComponents alloc] init];
    [firstDate setYear: [currentDate year]];
    [firstDate setMonth: [currentDate month]];
    [firstDate setDay:1];
   
    NSDate *tempDay = [[NSCalendar currentCalendar] dateFromComponents:firstDate];
    NSDate *birthDay = [[NSCalendar currentCalendar] dateFromComponents:birthDate];
   
    NSTimeInterval ti = [tempDay timeIntervalSinceDate:birthDay];
    [firstDate release];

    /* 바이오리듬 데이터를 구한다 */
    double days = ceil(fabs(ti) / ONEDAY_SECOND);
   
    for (int i = 0; i < 31; i++) {
        for(int j = 0; j < MAX_DATATYPE; j++) {
            biorhythmData[i][j] = sin((days/s_values[j]) * 2 * 3.14195) * 100;
        }
        days += 1.0;
    }
   
    /* 변경된 데이터로 다시 그림 */
    [self setNeedsDisplay];
}

/* 현재 날짜 변경 */
- (void)setCurrentDate:(int)year atMonth:(int)month atDay:(int)day {
    [currentDate setYear:year];
    [currentDate setMonth:month];
    [currentDate setDay:day];
}

/* 생일 변경 */
- (void)setBirthDate:(int)year atMonth:(int)month atDay:(int)day {
    [birthDate setYear:year];
    [birthDate setMonth:month];
    [birthDate setDay:day];
   
    NSLog(@"SET BIRTHDAY: %d, %d, %d", year, month, day);
}

/* 사용자가 버튼을 클릭했을 때, 날짜를 변경한다 */
- (void)changeDate:(int)pos {
    NSRange monthRange;
   
    NSTimeInterval newValue = 0;
    NSDate *tempDate = [[NSCalendar currentCalendar] dateFromComponents:currentDate];
   
    if (pos == NAV_PREVMONTH) {
        /* 이전 달로 이동을 위해 이전 달의 날수를 구한다 */
        NSDateComponents *prevMonthDate = [[NSDateComponents alloc] init];
        [prevMonthDate setYear: [currentDate year]];
        [prevMonthDate setMonth: [currentDate month]-1];
        [prevMonthDate setDay:1];
       
        monthRange = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                    inUnit:NSMonthCalendarUnit
                    forDate:[[NSCalendar currentCalendar] dateFromComponents:prevMonthDate]];
        [prevMonthDate release];
       
        /* 한달 전으로 이동 */
        newValue = ONEDAY_SECOND * monthRange.length;
        newValue = -newValue;
    }
    else if (pos == NAV_PREVDAY) {
        /* 하루 전으로 이동 */
        newValue = -ONEDAY_SECOND;
    }   
    else if (pos == NAV_NEXTDAY) {
        /* 하루 후로 이동 */
        newValue = ONEDAY_SECOND;
    }   
    else if (pos == NAV_NEXTMONTH) {
        /* 다음 달로 이동을 위해 현재 달의 날수를 구한다 */
        monthRange = [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
                                                        inUnit:NSMonthCalendarUnit
                                                       forDate:[[NSCalendar currentCalendar] dateFromComponents:currentDate]];
        /* 한달 후로 이동 */
        newValue = ONEDAY_SECOND * monthRange.length;
    }

    /* 날짜를 변경하고 변경된 날짜를 구한다. */
    NSDateComponents *newDate = [[NSCalendar currentCalendar] components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
                                                            fromDate:[tempDate addTimeInterval:newValue]];
   
    /* 현재 날짜를 변경 */
    [currentDate setYear: [newDate year]];
    [currentDate setMonth: [newDate month]];
    [currentDate setDay: [newDate day]];
   
    /* 바이오리듬을 다시 계산하고 출력한다. */
    [self resetBiorhythmData];
}

- (float *)getBiorhythmDayData:(int)day {
    return biorhythmData[day-1];
}

- (NSDateComponents *)currentDate {
    return currentDate;
}
@end


3) MainView
* MainView.h
#import <UIKit/UIKit.h>

@interface MainView : UIView {
    UIImage     *barFrame[MAX_DATATYPE];
    double        bioData[MAX_DATATYPE];
}

-(void)setData:(int)value1 secondValue:(int)value2 thirdValue:(int)value2;

@end

* MainView.m
#import "Global.h"
#import "MainView.h"

@implementation MainView

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    }
    return self;
}

- (void)awakeFromNib {
    /* 바의 배경을 위한 이미지를 로드한다. */
    for (int i = 1; i <= MAX_DATATYPE; i++) {
        barFrame[i - 1] = [[UIImage alloc] initWithContentsOfFile:
         [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"frame%d", i] ofType:@"png"]];
    }
}

- (void)drawRect:(CGRect)rect {
#define CENTER_X        210  // 바의 중간(0) 위치
#define START_Y            300  // 바가 출력될 Y 위치
#define START_X            120  // 바가 출력될 X 위치
#define BAR_HEIGHT        14.0 // 바의 높이   
#define    BAR_SPACE        40   // 각 바간 간격
   
    // Drawing code
    CGContextRef context = UIGraphicsGetCurrentContext();
   
    int y = START_Y + 15;
    CGPoint point;
   
    point.y = START_Y;
    point.x = START_X;
   
    for (int i = 0; i < MAX_DATATYPE; i++) {
        /* 배경 이미지 출력 */
        [barFrame[i] drawAtPoint:point];
       
        CGContextBeginPath(context);
        CGContextSetLineWidth(context, BAR_HEIGHT);
       
        /* 바이오리듬 값이 0보다 작으면 붉은 색으로 크면 파란색으로 표시 */
        if (bioData[i] > 0)
            CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
        else
            CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
       
        /* 바를 그린다 */
        CGContextMoveToPoint(context, CENTER_X, y);
        CGContextAddLineToPoint(context, CENTER_X + (bioData[i] * 80/100) , y);
       
        CGContextStrokePath(context);

        /* Y 좌표 변경 */
        point.y += BAR_SPACE;
        y += BAR_SPACE;
    }
}

-(void)setData:(int)value1 secondValue:(int)value2 thirdValue:(int)value3 {
    bioData[0] = value1;
    bioData[1] = value2;
    bioData[2] = value3;
}

- (void)dealloc {
    for (int i = 0; i < MAX_DATATYPE; i++) {
        [barFrame[i] release];
    }
   
    [super dealloc];
}

@end


4) MainViewController
* MainViewController.h
#import <UIKit/UIKit.h>

@class GraphView;
@class MainView;

@interface MainViewController : UIViewController {
    IBOutlet MainView        *mainView;   
    IBOutlet GraphView      *graphView;
    IBOutlet UITextField    *value_1;
    IBOutlet UITextField    *value_2;
    IBOutlet UITextField    *value_3;
   
    IBOutlet UIBarButtonItem *currentDate;
}

- (void)updateInfoUI;
- (void)birthdayChanged:(NSNotification *)note;

- (IBAction)navigationButtonClicked:(id)sender;

@end

* MainViewController.m
#import "Global.h"
#import "MainViewController.h"
#import "MainView.h"
#import "GraphView.h"

@implementation MainViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad {
    /* 오늘 날짜를 구한다. */
    NSDateComponents *today = [[NSCalendar currentCalendar]
                       components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
                       fromDate:[NSDate date]];
   
    /* 현재 날짜 설정 */
    [graphView setCurrentDate:[today year]
                      atMonth:[today month]
                        atDay:[today day]];

    /* 생년월일 설정 */
    [graphView setBirthDate:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHYEAR_KEY]
                      atMonth:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHMONTH_KEY]
                        atDay:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHDAY_KEY]];

    /* 그래프를 그리고 UI를 설정한다. */
    [graphView resetBiorhythmData];
    [self updateInfoUI];
   
    /* 환경설정에서 생일이 변경되었을 때를 위해 옵저버로 등록 */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(birthdayChanged:)
                                                 name:NM_BIRTHDAYCHANGED
                                               object:nil];
}

- (void)updateInfoUI {
    /* 하단 타이틀에 현재 날짜를 출력 */
    NSDateComponents *date = [graphView currentDate];
    [currentDate setTitle:[NSString stringWithFormat:@"%d.%02d.%02d",
                           [date year], [date month], [date day]]];
   
    float *bioValue = [graphView getBiorhythmDayData:[date day]];
    int value1 = (int)ceil(bioValue[0]);
    int value2 = (int)ceil(bioValue[1]);
    int value3 = (int)ceil(bioValue[2]);
   
    /* 각 바이오리듬 값 출력 */
    [value_1 setText:[NSString stringWithFormat:@"%d", value1]];
    [value_2 setText:[NSString stringWithFormat:@"%d", value2]];
    [value_3 setText:[NSString stringWithFormat:@"%d", value3]];
   
    /* mainView에 현재 설정된 값을 알려주고, 바가 다시 그려지도록 한다. */
    [mainView setData:value1 secondValue:value2 thirdValue:value3];
    [mainView setNeedsDisplay];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

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

- (void)birthdayChanged:(NSNotification *)note
{
    /* 생일이 변경되었을 때 다시 설정하고 그려지도록 한다. */
    [graphView setBirthDate:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHYEAR_KEY]
                      atMonth:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHMONTH_KEY]
                        atDay:[[NSUserDefaults standardUserDefaults] integerForKey:US_BIRTHDAY_KEY]];
   
    [graphView resetBiorhythmData];
    [self updateInfoUI];
}       

- (IBAction) navigationButtonClicked:(id)sender {
    /* 사용자가 날짜 이동 버튼을 클릭하였을 경우 처리 */
    [graphView changeDate:[sender tag]];
    [self updateInfoUI];
}

@end


5) FlipsideViewController
* FlipsideViewController.h
#import <UIKit/UIKit.h>

@interface FlipsideViewController : UIViewController {
    IBOutlet UIDatePicker    *datePicker;
}

@property (nonatomic, retain) UIDatePicker *datePicker;

@end

* FlipsideViewController.m
#import "FlipsideViewController.h"

@implementation FlipsideViewController

@synthesize datePicker;

- (void)viewDidLoad {
    self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor];       
 }

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

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

@end


5) RootViewController
* RootViewController.m
#import "Global.h"
#import "RootViewController.h"
#import "MainViewController.h"
#import "FlipsideViewController.h"

@implementation RootViewController

@synthesize infoButton;
@synthesize flipsideNavigationBar;
@synthesize mainViewController;
@synthesize flipsideViewController;

- (void)viewDidLoad {
   
    MainViewController *viewController = [[MainViewController alloc] initWithNibName:@"MainView" bundle:nil];
    self.mainViewController = viewController;
    [viewController release];
   
    [self.view insertSubview:mainViewController.view belowSubview:infoButton];
}

- (void)loadFlipsideViewController {
   
    FlipsideViewController *viewController = [[FlipsideViewController alloc] initWithNibName:@"FlipsideView" bundle:nil];
    self.flipsideViewController = viewController;
    [viewController release];
   
    // Set up the navigation bar
   
    UINavigationBar *aNavigationBar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 44.0)];
    aNavigationBar.barStyle = UIBarStyleBlackOpaque;
    self.flipsideNavigationBar = aNavigationBar;
    [aNavigationBar release];
   
    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(toggleView)];
    UINavigationItem *navigationItem = [[UINavigationItem alloc] initWithTitle:@"iBiorhythm"];
    navigationItem.rightBarButtonItem = buttonItem;
    [flipsideNavigationBar pushNavigationItem:navigationItem animated:NO];
    [navigationItem release];
    [buttonItem release];
}

- (IBAction)toggleView {   
    if (flipsideViewController == nil) {
        [self loadFlipsideViewController];
    }
   
    UIView *mainView = mainViewController.view;
    UIView *flipsideView = flipsideViewController.view;
   
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:1];
    [UIView setAnimationTransition:([mainView superview] ? UIViewAnimationTransitionFlipFromRight : UIViewAnimationTransitionFlipFromLeft) forView:self.view cache:YES];
   
    if ([mainView superview] != nil) {
        [flipsideViewController viewWillAppear:YES];
        [mainViewController viewWillDisappear:YES];
        [mainView removeFromSuperview];
        [infoButton removeFromSuperview];
        [self.view addSubview:flipsideView];
        [self.view insertSubview:flipsideNavigationBar aboveSubview:flipsideView];
        [mainViewController viewDidDisappear:YES];
        [flipsideViewController viewDidAppear:YES];
       
        /* DataPicker 날짜를 저장된 날짜로 설정 (현재 동작하지 않음) */
        NSDateComponents *curDate = [[NSDateComponents alloc] init];
       
        int year = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];
        int month = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];
        int day = [[NSUserDefaults standardUserDefaults] integerForKey:@"birth_year"];

        [curDate setYear: year];
        [curDate setMonth: month];
        [curDate setDay: day];
   
        NSDate *tempDate = [[NSCalendar currentCalendar] dateFromComponents:curDate];
        [[flipsideViewController datePicker] setDate:tempDate animated:YES];
           
        [curDate release];
    } else {
        [mainViewController viewWillAppear:YES];
        [flipsideViewController viewWillDisappear:YES];
        [flipsideView removeFromSuperview];
        [flipsideNavigationBar removeFromSuperview];
        [self.view addSubview:mainView];
        [self.view insertSubview:infoButton aboveSubview:mainViewController.view];
        [flipsideViewController viewDidDisappear:YES];
        [mainViewController viewDidAppear:YES];
       
        /* 사용자가 설정한 날짜(생일)를 저장 한다 */
        NSDateComponents *newDate = [[NSCalendar currentCalendar]
              components:(NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit)
              fromDate:[[flipsideViewController datePicker] date]];
       
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate year] forKey:US_BIRTHYEAR_KEY];
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate month] forKey:US_BIRTHMONTH_KEY];
        [[NSUserDefaults standardUserDefaults] setInteger:[newDate day] forKey:US_BIRTHDAY_KEY];
       
        /* 바이오리듬이 변경되도록 메시지를 보낸다. */
        [[NSNotificationCenter defaultCenter]
                   postNotificationName:NM_BIRTHDAYCHANGED
                   object:self];
    }
    [UIView commitAnimations];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}

- (void)dealloc {
    [infoButton release];
    [flipsideNavigationBar release];
    [mainViewController release];
    [flipsideViewController release];
    [super dealloc];
}

@end

다음 포스팅에선 인터페이스 빌더에서 작업을 하고 어플리케이션이 동작하도록 완성시켜 보겠습니다. 동작은 하는데 제대로 만든 것인지는 모르겠습니다.