iOS: iPhone용 계산기 프로그램 만들어보기

이 내용은 Stanford 대학에서 제공한 "Developing Apps for iOS"의 강의 내용을 참고하여 작성한 것임을 밝힌다.

2012/2/22 - Xcode 4.3 (iOS 5.0) 기준으로 업데이트!

Stanford의 강의 내용이 좋지만 영어의 장벽때문에 좌절하고 있을 개발자 분들에게 약간이나마 도움이 되었으면 하는 마음과 내 스스로 정리한다는 차원에서 해당 강좌를 듣고 내용을 정리해보고자 한다. 이 강의는 iTunes University에서 "Developing Apps for iOS"로 직접 검색하여 "Building a simple calculator"라는 제목의 동영상을 통해 볼 수 있다.

원래 이 Stanford 강의의 목적은 기본적인 iOS 프로그램 개발과 MVC 구조에 대한 이해를 돕는 것이다. 아직 기초 단계의 강의라 메모리 해지 등에 대해서는 신경쓰지 않고 진행한다.

1. Xcode에서 새로운 프로젝트 생성
Xcode를 구동한 후 iOS의 Single view application으로 Calc라는 이름의 새로운 프로젝트를 만들자. (Storyboards는 사용하지 않는다.)

2. MVC에서 M에 해당하는 Model 소스를 추가
좌측의 Calc 폴더에서 context menu를 뛰우고 New File...을 선택한다. New File 다이얼로그에서 Objectiv-C class를 선택하고 이름을 CalcBrain이라고 하자. CalcBrain.h와 CalcBrain.m 파일이 생성된다. (.m 파일은 .c 로 생각하면 된다.) CalcBrain은 계산기 구현에 필요한 model을 담당할 파일들이다.

3. CalcBrain.h에 instance variable과 instance method를 선언
계산기 model에 필요한 데이터와 동작을 header 파일에 선언한다.
#import <Foundation/Foundation.h>

@interface CalcBrain : NSObject {
 double operand;
 NSString *waitingOperation;
 double waitingOperand;
}

- (void)setOperand:(double)anOperand;
- (double)performOperation:(NSString *)operation;

@end

4. CalcBrain.m에 instance method 구현
#import "CalcBrain.h"

@implementation CalcBrain

- (void)setOperand:(double)anOperand
{
 operand = anOperand;
}

- (void)performWaitingOperation
{
 if ([@"+" isEqual:waitingOperation]) {
  operand = waitingOperand + operand;
 } else if ([@"-" isEqual:waitingOperation]) {
  operand = waitingOperand - operand;
 } else if ([@"*" isEqual:waitingOperation]) {
  operand = waitingOperand * operand;
 } else if ([@"/" isEqual:waitingOperation]) {
  if (operand != 0) {
   operand = waitingOperand / operand;
  }
 }
}

- (double)performOperation:(NSString *)operation
{
 if ([operation isEqual:@"sqrt"]) {
  operand = sqrt(operand);
 } else {
  [self performWaitingOperation];
  waitingOperation = operation;
  waitingOperand = operand;
 }
 return operand;
}

@end

위 구현 코드에서 좀 햇갈릴 수 있는 부분은 performWaitingOperation이다. performOperation 메쏘드는 인자로 +, -, *, /, =, sqrt와 값은 연산기호를 입력받는다. 이 때 전에 기다리고 있는 연산이 있다면 이를 수행하기 위해 performWaitingOperation을 호출하고 이번에 받은 연산자는 현재 operand 값과 함께 다음번에 계산하기 위해 각각 waitingOperation, waitingOperand에 보관된다. 좀 더 이해를 돕기 위해 3 + 4 = 를 사용자가 순서대로 입력했다고 가정하고 어떤 순서로 수행되는지 설명해보겠다.
  • setOperand: 3
  • performOperation: +
  • performWaitingOperation: 하지만 기존에 기다리던 연산이 없다. 그냥 리턴 됨.
  • waitingOpeation = +
  • waitingOperand = 3
  • setOperand: 4
  • performOperation: =
  • performWaitingOperation: 기존에 저장해둔 + 연산을 수행. 즉, 3 + 4의 값이 operand에 결과 값으로 저장 됨.
  • waitingOperation이 = 으로 변경 됨. 다음 번 performWaitingOperation이 수행되면 = 는연산자로서 아무런 의미가 없으므로 그냥 리턴 됨.
위 처리 방법을 이해하였으면 계산기 구현을 위한 Model 구현은 끝났다. 다음은 MVC에서 V에 해당하는 View를 구성해보도록 하자.

5.IBOutlet과 IBAction 선언
Objective-C에서는 MVC 구조에서 View와 Controller간의 통신을 위해 outlet과 target/action을 사용한다. outlet은 Controller가 View에 있는 UI 객체를 가리키는 참조이고 이를 통해 UI 객체를 다룰 수 있다. target/action은 반대로 View에서 어떤 UI 객체가 사용자에 의해 이벤트가 발생한 경우 이를 Controller에게 알려주는 방법이다. outlet과는 달리 View에서 Controller 방향으로는 참조가 존재하지 않고 View가 Target인 Controller에 Action에 해당하는 메시지를 전달하는 방식이다.

Outlet, Target/Action을 구현하기 위해 우리 예제의 Controller에 해당하는 ViewController.h에 아래와 같이 코드를 작성한다.
#import <UIKit/UIKit.h>
#import "CalcBrain.h"

@interface ViewController : UIViewController {
 IBOutlet UILabel *display;
 CalcBrain *brain;
 BOOL middleOfNumberTyping;
}

- (IBAction)digitPressed:(UIButton *)sender;
- (IBAction)operationPressed:(UIButton *)sender;

@end
IBOutlet UILabel *display; 코드 라인이 View에 있는 UILabel 객체를 다루기 위해 사용하는 참조이다. 맨 앞에 붙은 IBOutlet은 void를 #define 한 것으로 Interface Builder에서 소스 코드를 분석하기 위해 사용하는 일종의 메타데이터라고 이해하자.

(IBAction)digitPressed와 (IBAction)operationPressed는 View에서 사용자에 의해 숫자 버튼 혹은 연산자 버튼이 눌리면 각각 Action에 대한 Target이 될 메쏘드이다. 역시 앞에 (IBAction)이라는 것은 void에 해당하며 Interface Builder를 위한 소스 코드 분석용 메타데이터로 이해하자.

6. Interface Builder에서 View 구성
좌측 화면의 Resources에서 ViewController.xib을 클릭하여 Interface Builder를 실행한다. 아래 순서대로 화면을 구성하자.
  • Library에서 Label을 선택하여 View 윈도우의 상단에 적절히 배치한다. (계산 결과 긴 문자열이 표시 될 수 있도록 라벨의 폭을 넓게 잡아주어야 한다.)
  • 배치한 Label의 Attributes를 수정하여 폰트 크기를 늘리고 text의 layout을 우측 정렬로 바꾼다.
  • Label에 대한 outlet을 설정하기 위해 File's Owner를 선택하고 control 키와 왼쪽 마우스 버튼을 누른 상태에서 마우스를 끌어 Label까지 이동 후 버튼을 논다. 그러면 컨텍스트 메뉴가 나오면서 outlet으로 어떤 변수를 선택할지 물어본다. 이 때 display를 선택하자. 이제 View에 있는 Label 객체는 Controller의 display 멤버에 의해 참조할 수 있게 되었다.
  • Round Rect Button을 Library에서 선택하여 View 윈도우에 배치한다.
  • 이 버튼을 선택한 후 control 키와 왼쪽 마우스 버튼을 누른 상태에서 마우스를 끌어 File's Owner 아이콘위로 이동 후 마우스 버튼을 띈다. 
  • 그러면 File's Owner 아이콘 위로 digitPressed, operationPressed라는 context 메뉴가 나타날 것이다. 여기서 digitPressed를 선택한다. 여기서 File's Owner은 ViewController이다. digitPressed와 operationPressed는 IBAction으로 앞에서 선언해두었기 때문에 Interface Builder가 이를 인식하여 메뉴에 보여주는 것이다.
지금까지 설명한 과정은 숫자입력을 위한 버튼을 Controller의 action target에 적절히 연결하는 과정이다. View 화면은 대충 아래와 같은 모양이 되어 있을 것이다.

  • 숫자입력 버튼에 대한 target/action 설정을 그대로 활용하기 위해 이미 추가한 버튼을 선택하고 Cmd-C로 복사하고 Cmd-V로 9개의 버튼을 더 추가한다. 그런 후 적절히 숫자 값을 지정해준다.
  • Operation을 나타내는 +, -, *, /, sqrt, = 에 해당하는 버튼을 추가해보자. 다시 새로운 Round Rect Button을 하나 View에 배치한다. 
  • 역시 이 버튼을 선택하고 control 키와 왼쪽 마우스 버튼을 누른채로 File's Owner로 마우스를 끌고 가서 버튼을 논다. 이번에는 메뉴에서 operationPressed를 선택한다.
  • Operation에 해당하는 모든 버튼을 추가하기 위해 이 버튼에 대해서도 Cmd-C, Cmd-V를 수행하여 5개의 버튼을 더 추가 배치한다.
  • View를 아래와 같이 정리해보자.

7. Controller 코드 구현
마지막으로 Controller의 코드를 구현해주자. ViewController.m 파일을 아래와 같이 구현한다.

@implementation ViewController

- (CalcBrain *)brain
{
 if (!brain) {
  brain = [[CalcBrain alloc] init];
 }
 return brain;
}

- (IBAction)digitPressed:(UIButton *)sender
{
 NSString *digit = [[sender titleLabel] text];
 if (middleOfNumberTyping) {
  [display setText:[[display text] stringByAppendingString:digit]];
 } else {
  [display setText:digit];
  middleOfNumberTyping = YES;
 }
}

- (IBAction)operationPressed:(UIButton *)sender
{
 if (middleOfNumberTyping) {
  [[self brain] setOperand:[[display text] doubleValue]];
  middleOfNumberTyping = NO;
 }
 NSString *operation = [[sender titleLabel] text];
 double result = [[self brain] performOperation:operation];
 [display setText:[NSString stringWithFormat:@"%g", result]];
}

@end
Controller 코드는 View에서 발생한 이벤트에 대하여 Model에 적절한 값을 전달하거나 혹은 필요한 logic을 수행하도록 하고 그 결과를 다시 View에 반영하는 역할을 한다.

8. 실행
자 이제 Run을 선택하여 실행해보자. 혹시, 실행에 문제가 있다면 댓글로 알려주기 바란다.

댓글

  1. 3번 확인 좀 부탁드려요. 전부다 따라했는데 에러가 12개가 나왔는데요 (ㅠㅠ) 3번이 calcbrain.h 헤더파일 해주라고 하셨는데 calcbrain.h가 아니고CalcViewController.h 같습니다. 빨리 확인 좀 해주세요 .. 하구싶어용~

    답글삭제
  2. 에고 3번 과정에 CalcBrain.h 소스가 아닌 다른 소스를 복사해 두었네요. 죄송합니다. 수정 하였으니 한번 확인해 보세요.

    답글삭제
  3. 스탠포드 강의를 옮겨놓으셨네요~

    답글삭제
  4. 5번과정의 CalcBrain *brain; 에서

    Expected specifier-qualifier-list before 'CalcBrain' 에러나는데..

    어떻게 된건가요 ㅠㅠ
    도와주세요!

    답글삭제
  5. 5번 과정의 소스 코드에 #import "ClacBrain.h" 를 추가하면 해결 될 겁니다. 소스 코드 예제에서 빼먹었었네요.

    답글삭제
  6. self.window.rootViewController = self.viewController;

    이게 뭐죠

    실행시키면 스레드 에러뜨는거같은데

    답글삭제
  7. 안되자나요! 테스트하고 올린건가요?
    에러나자나요!
    테스트좀 하고 올리지!

    답글삭제
    답글
    1. Xcode가 버전업 되면서 기존 내용과 달라진 부분이 있어 에러가 발생했군요. 4.3 버전 기준으로 수정해 놓았습니다.

      삭제
    2. 졸라 싸가지없네; 버전업때문에 안되는걸...

      주인장님 힘내십쇼 잘보고갑니다

      삭제
  8. double result = [[self brain] performOperation:operation];
    부분의 performOperation -> performOperand로 바꾸시면 잘 실행됩니다

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

Wireless: HotSpot 2.0 이란?

Apple M1 Mac Mini에서 이더리움 (Ethereum) 채굴하기

Java: Java for Game? Java가 Game 개발에 어울릴까?