사용자 삽입 이미지
이번 장에서는 적기들을 출력하고 동작하게 하는 작업을 해보겠습니다.

적기는 좌측과 같이 세 종류로 상단에 위치해 있으며 인베이더 게임과 같이 좌우로 움직이며 아래로 내려 옵니다.

마지막 적기가 화면에서 사라지면 다음 스테이지로 넘어가며, 스테이지가 진행될 수록 적기의 움직임이 빨라 집니다.




1.8.5 소스 파일 추가

1) global.h

게임에서 사용되는 여러 속성들의 공유를 위해 global.h 헤더파일을 작성합니다. XCode의 메뉴에서 File/New File을 클릭한 후, BSD/Header File을 선택하고 [Next] 버튼을 클릭합니다. 파일명을 global.h로 입력하고  [Finish] 버튼을 클릭합니다.

이제 에디터에서 global.h를 열어서 아래와 같이 내용을 입력합니다.
#define SCREEN_WIDTH        300        /* 화면 너비 */
#define SCREEN_HEIGHT       320        /* 화면 길이 */

#define HERO_SPEED          3       /* 우주선 속도 */
#define MISSILE_SPEED       4       /* 총알발사 속도 */

#define MAX_ENEMY1            5        /* 적기1 갯수 */
#define MAX_ENEMY2            3        /* 적기2 갯수 */
#define MAX_ENEMY3            1        /* 적기3 갯수 */

#define ENEMY_DOWN            20        /* 적기 하강 픽셀 */

이전 StageView.m에 있던 SCREEN_WIDTH및 위와 중복되는 define된 부분 (검은색)을StageView.m에서 삭제 합니다.  그리고 StageView.m 상단에 아래와 같이 global.h를 임포트 합니다.
 
#import "global.h"
#import "StageView.h"


2) Enemy 오브젝트 추가

최대한 간단하게 만들기 위해 class 추가 없이 StageView.m에서 다 해결 할라고 했는데, 적기 때문에 너무 복잡해 질 것 같아 Enemy 클래스를 추가하기로 하였습니다.
 
XCode의 메뉴에서 File/New File을 클릭한 후, Cocoa/Objective-C class를 선택하고 [Next] 버튼을 클릭합니다. 파일명을 Enemy.m으로 입력하고  [Finish] 버튼을 클릭합니다.

설명은 소스에 간단한 주석으로 대치 합니다.

Enemy.h
#import <Cocoa/Cocoa.h>

@interface Enemy : NSObject {
    int type;
    int energy;
    int state;
   
    NSImage* image;
    NSPoint position;
}

- (id)initWithImage:(NSImage*)img
               type:(int)t;

- (int)state;
- (void)setState:(int)s;
- (void)setPosition:(NSPoint)pos;

- (void)down;
- (BOOL)moveAndDisplay:(int)dir;

@end
type과 energy는 아직 사용하지 않습니다.

Enemy.m
#import "global.h"
#import "Enemy.h"

@implementation Enemy

/** 적기 타입과 이미지를 설정 */
- (id)initWithImage:(NSImage*)img
           type:(int)t
{
    [super init];
   
    type = t;
    image = img;

    return self;
}

- (void)setPosition:(NSPoint)pos
{
    position = pos;
}

- (int)state
{
    return state;
}

- (void)setState:(int)s
{
    state = s;
}

/** 적기를 한단계 아래로 옮기고, 화면에 안 나올 경우에는 상태를 0으로 변경한다. */
- (void)down
{
    position.y -= ENEMY_DOWN;
    if(position.y + [image size].height < 0)
        state = 0;
}

- (BOOL)moveAndDisplay:(int)dir
{
    BOOL isChange = false;
    NSRect drawRect;
    NSRect imgRect;
       
    drawRect.size = [image size];
   
    /** 적기를 이동하고 좌우 화면의 경계를 넘었을 경우에는 아래로 이동하도록 isChange를 1로 세팅 해서 반환. */
    position.x += dir;
    if(position.x < 0)
    {
        isChange = true;
    }   
    if(position.x >= SCREEN_WIDTH - drawRect.size.width)
    {
        isChange = true;
    }
   
    /** 적기를 출력한다. */
    drawRect.origin.x = position.x;
    drawRect.origin.y = position.y;
   
    imgRect.origin = NSZeroPoint;
    imgRect.size = [image size];
   
    [image drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];   
   
    return isChange;
}

@end


1.8.6 StageView 변경
다음은 스테이지를 설정하고, 적기를 출력 하기 위해 Stageview 클래스를 변경해 보겠습니다. 스테이지가 증가할 수록 적기의 이동속도가 빨라 집니다.

StageView.h
/* StageView */

#import <Cocoa/Cocoa.h>

@class Enemy;

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
   
    int curStage;
    int enemyDir;

    NSImage *enemyImage1;
    NSImage *enemyImage2;
    NSImage *enemyImage3;
   
    Enemy *Enemy1[MAX_ENEMY1];
    Enemy *Enemy2[MAX_ENEMY2];
    Enemy *Enemy3[MAX_ENEMY3];
}

- (void)setStage;
- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end

StageView.m
#import "global.h"
#import "StageView.h"
#import "Enemy.h"

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 적기1 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy1" ofType:@"png"];
        enemyImage1 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기2 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy2" ofType:@"png"];
        enemyImage2 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기3 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy3" ofType:@"png"];
        enemyImage3 = [[NSImage alloc] initWithContentsOfFile:imageName];
               
        int i;
       
        /** 적기 오브젝트 생성 */
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            Enemy1[i] = [[Enemy alloc] initWithImage:enemyImage1
                                                type:1];
        }
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            Enemy2[i] = [[Enemy alloc] initWithImage:enemyImage2
                                                type:2];
        }
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            Enemy3[i] = [[Enemy alloc] initWithImage:enemyImage3
                                                type:3];
        }
       
        /** 1 스테이지 설정 */
        curStage = 0;
        [self setStage];
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];       
    }
    return self;
}

/** 할당된 메모리 반환 */
- (void) dealloc
{
    int i;
   
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
   
    [enemyImage1 release];
    [enemyImage2 release];
    [enemyImage3 release];

    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] release];
    }
   
    [super dealloc];
}

- (void) setStage
{
    int i;
    NSPoint pos;
   
    /* 현재 스테이지를 1 증가 시킨다. */
    curStage++;
       
    /* 적기의 좌/우 움직이는 속도와 방향 설정. */
    enemyDir = curStage;

    isFire = NO;
   
    /** 적기들의 초기 위치와 상태를 설정한다. */
    pos.x = 45;
    pos.y = 190;
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] setPosition:pos];
        [Enemy1[i] setState:1];
   
        pos.x += 45;
    }

    pos.x = 78;
    pos.y = 225;
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] setPosition:pos];
        [Enemy2[i] setState:1];
   
        pos.x += 55;
    }

    pos.x = 122;
    pos.y = 265;
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] setPosition:pos];
        [Enemy3[i] setState:1];
   
        pos.x += 80;
    }
}

/** 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
   
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
       
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }
   
    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;
       
        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
                 fromRect:imgRect
                operation:NSCompositeSourceOver
                 fraction:1.0];   
   
   
    int i;
    int isChange = 0;
    int enemyCount = 0;
   
    /* 적기들을 출력하고 좌우 경계선을 넘어간 적기가 있을 경우를 체크한다. */
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        if([Enemy1[i] state])
        {
            enemyCount++;
            isChange |= [Enemy1[i] moveAndDisplay:enemyDir];
        }   
    }

    for(i = 0; i < MAX_ENEMY2; i++)
    {
        if([Enemy2[i] state])
        {
            enemyCount++;
            isChange |= [Enemy2[i] moveAndDisplay:enemyDir];
        }   
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        if([Enemy3[i] state])
        {
            enemyCount++;
            isChange |= [Enemy3[i] moveAndDisplay:enemyDir];
        }
    }
   
    /** 남은 적기가 없을 경우에는 다음 스테이지로 넘어 간다. */
    if(enemyCount == 0)
    {
        [self setStage];
        return;   
    }
       
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                        fromRect:imgRect
                       operation:NSCompositeSourceOver
                        fraction:1.0];   
    }
   
    /** 적기가 좌우측의 경계를 넘었을 경우에는 아래로 이동한다. */
    if(isChange)
    {
        enemyDir *= -1;
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            if([Enemy1[i] state])
                [Enemy1[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            if([Enemy2[i] state])
                [Enemy2[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            if([Enemy3[i] state])
                [Enemy3[i] down];
        }
    }
}
@end

이번장에서 사용된 이미지와 전체 프로젝트 파일은 아래에서 다운 받으실 수 있습니다.

다음장에서는 충돌 검사, 스테이지/점수/에너지 표시등을 넣고 몇 가지 사항을 수정하여 초 간단한 게임을  완료해 보겠습니다.

AND

Xcode 2 2007. 10. 1. 11:52
그동안 바쁘다는 핑계로 포스트를 뜸하게 했지만, 10월 부터는 다시 초심의 마음으로 열심히 할려고 합니다. 우선 이전부터 생각했던 것인데 XCode를 처음 사용할 때 만나는 다소 생소한 용어들을 정리해 볼려고 합니다.

일단 간단히 정리만 해놓고 계속 업데이트를 할려고 합니다. 티스토리에 위키 기능이 있었으면 좋겠다는 생각이 드네요.

자료는 ADC의 문서와  Cocoa Programming for MAC OS X, Step into Xcode 서적을 참고하였습니다.

Action (액션)
버튼 등의 UI 컨트롤이 변경될 때 호출되는 메소드로 인터페이스 빌더에서 연결된다.

awakeFromNib [메소드]
어플리케이션이 실행되어 nib파일에서 읽어들인 정보로, 객체를 할당하고 초기화(init 메시지 발송)를 한 후에 nib에 있는 모든 오브젝트들에게 보내지는 메지지. init은 아울렛이 설정되기 전에 불려 지므로, 다른 객체에 메시지를 보낼 수 없다. 이럴 때는 awakeFromNib파일을 이용한다.

Bundle (번들)
패키지된 실행가능한 파일들과 이미지, 사운드, nib파일등의 리소스들을 가지고 있는 디렉토리이다. 응용 프로그램과 프레임워크는 일반적인 번들의 예이다.

FirstResponder
기존의 클래스에 추가하는 메소드들의 집합. 이벤트를 처리하는 첫번째 NSResponder 객체이며 First Responder가 받은 이벤트를 처리하지 않는 다면, 핸들러를 찾을 때까지 Responder 체인을 찾는다.

Framework (프레임워크)
헤더파일, 동적으로 로드되는 라이브러리와 단일 디렉토리의 하부 디렉토리에 있는 리소스들을 포함하고 있는 번들이다. Framworks는  디렉토리이지만 .framework의 확장자를 가진다.

Method (메소드)
class 내부 함수. 메시지를 보내서 호출한다.

MVC (Model-View-Controller)
디자인 패턴, 클래스는 MVC중 하나로 분류될 수 있어야 한다.
Model
데이터를 의미하면, 데이터를 변경하는 연산을 지원한다.

View
사용자에게 Model을 보여주는 역활을 한다. 하나의 Model에 관하여 출력 방식이 다른 다양한 View가 존재할 수 있다.

Controller
사용자의 입력과 로직을 처리하여 Model을 변경한다.

Nan (난)
Not a number의 약자로 숫자가 와야 할 곳에 오류 또는 숫자가 아닌 다른 값일  경우를 나타내는 상태다. 숫자값이 NaN이 된 상태에서는 다른 연산도 모두 NaN이 된다.

Nib 파일
NeXT Interface Builder의 약자이며, 오브젝트의 속성, 연결 등의 정보를 가지고 있는 패키지이다. 인터페이스 빌더에서 생성되며 해당 어플리케이션이 실행시에 로드된다.

nil (닐)
객체의 포인터가 NULL일 경우에는 nil로 사용한다.

Outlet (아울렛)
객체를 가르키는  인스턴스 변수. 인터페이스 빌더에서 다른 객체를 지정하는 포인터로 사용된다.

Package (패키지)
OS X의 파인더가 실제로는 디렉토리이며 내부에는 하부 디렉토리를 가지고 있다고 하더라도, 사용자에게는 하나의 파일 형태로 보여주는 디렉토리이다. 응용프로그램과 RTFDs와 같은 복합적인 문서들이 일반적인 예이다.

Protocol (프로토콜)
Objective-C에서 클래스가 구현하기로 약속한 method의 한벌(suit). 만약 클랙스가 프로토콜의 모든 메시지를 구현하지 않으면 컴파일러는 오류를 내보낸다. id <ProtocolName>의 오브젝트 포인터 타입은 구현된 어떠한 ProtocolName 프로토콜의 타입을 정의한다.

메소드선언의 목록이며, 프로토콜을 구현한 클래스를 생성하면 클래스는 선언한 모든 메소드를 구현하여야 한다.

ZeroRink (제로링크)
Xcode에서 빌드 속도를 높이기 위해 어플리케이션이 실행되기 전 링크를 생략한다. 어플리케이션을 실행하기 위한 모듈만 링크되며, 다른 모듈들은 호출될 때만 링크가 된다.

ZeroLink된 어플리케이션은 현재 개발중인 컴퓨터에 종속되며, 다른 컴퓨터에서는 적용 되지 않는다.Deployment로 빌드하면 어플리케이션에서 디버깅 심벌을 제가하고 실제로 링크 작업을 한다.

AND

1.8.0 프로젝트 개요
이번 장에서는 NSView와 NSImage를 이용해서 간단한 슈팅게임을 만들어 보겠습니다.

사용자 삽입 이미지
좌측과 같이 배경을 출력하고, 우주선을 좌우로 조종하고, 미사일을 발사하는  작업까지 하겠습니다.

키입력에서 다중으로 입력 받을 수가 없어 이동시에 미사일을 발사하면 이동이 중지 됩니다. 이문제는 다음 장에서 해결하도록 하겠습니다.





1.8.1 프로젝트 생성
Xcode를 실행하고 아래와 같이 SimpleShot 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. project Name에 SimpleShot를 입력하고 finish 버튼을 클릭합니다.

1.8.2 인터페이스 빌더에서 작업
1) 서브 클래스 생성

사용자 삽입 이미지
Nib 파일을 클릭하여 인터페이스빌더를 열고 NSView 클래스의 서브클래스를 만들고(아래의 창에서 NSView에 마우스 우클릭 합니다. 그 다음 Subclass NSView 클릭) 이름을 StageView로 변경합니다


사용자 삽입 이미지
StageView를 우클릭 하고 좌측과 같이 Create Files for StageView를 클릭하여 소스파일을 생성합니다.





2) NSView 생성 및 속성 설정

윈도우를 열고 팔레트의 Containers 항목에서  CustomeView를 드래그하여 아래와 같이 윈도우에 배치 합니다. View의 크기를 300X320으로 변경하고,  Custom Class 항목에서 StageView로 선택합니다.
사용자 삽입 이미지 사용자 삽입 이미지
(클릭하면 이미지가 확대됩니다.)

1.8.3 이미지 파일

1) 이미지 생성

이제 배경, 우주선, 미사일에 쓰일 이미지를 준비합니다. 배경은 상관없지만 우주선과 미사일은 윤곽선이외에는 투명색으로 보여야 하기 때문에 png를 사용했습니다.

사용자 삽입 이미지
저는 도스의 디럭스페인트 이후로는 그래픽 작업이 불가능하므로 초등학생인 아들에게 이미지를 부탁하였습니다. 그래픽툴들을 다루실 수 있는 분들은 새로 만드셔도 되고, 아니면 첨부파일에서 다운로드 받으셔서 사용하시면 됩니다.

좌측은 배경화면 이미지입니다. StageView와 같은 300X320의 크기를 가지고 있습니다.



사용자 삽입 이미지
우주선 이미지 입니다. 하얗게 보이는 부분을 투명하게 처리해야 하단의 배경화면에서 올바르게 보입니다. 이를 위해 png파일 포맷을 사용했습니다.

사용자 삽입 이미지
미사일 이미지 입니다. 위와 같이 png파일 포맷을 사용했습니다.

2) 이미지 등록

사용자 삽입 이미지
준비된 이미지 파일을 드래그하여 Xcode 좌측의  Group & Files 목록의 Resources에 가져다 놓습니다.



드래그 시 나오는 판넬에서 아래와 같이 Copy items... 항목의 체크를 확인합니다.
사용자 삽입 이미지

1.8.4 소스코드 작성

1) StageView.h 수정

#import <Cocoa/Cocoa.h>

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
}

- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end


2) StageView.m 수정

#import "StageView.h"

#define SCREEN_WIDTH        300
#define SCREEN_HEIGHT        320

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 총알발사 속도 */

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
       
        isFire = NO;
       
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];
    }
    return self;
}

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

/* 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
   
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }

    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;

        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}       

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
               fromRect:imgRect
              operation:NSCompositeSourceOver
               fraction:1.0];   
   
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];   
    }   
}

@end

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 미사일발사 속도 */

한 프레임에서 우주선과 총알의 움직이는 속도를 설정합니다. 좌/우 방향키를 누르면 각각의 방향으로 우주선이 한 프레임당 3픽셀을 움직입니다. 미사일은 4픽셀씩 상단으로 이동합니다. 숫자를 바꾸어 주면 해당 오브젝트의 속도를 변경할 수 있습니다.

StageView가 생성될 때 자동으로 호출되는 - (id)initWithFrame:(NSRect)frameRect 메소드에서 이미지 파일을 로드하고 타이머를 세팅하는 작업을 합니다.

NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

어플리케이션의 리소스 디렉토리에서 background.png 이미지 파일을 로드하고 NSImage를 생성 합니다. 우주선과 미사일 이미지를 차례로 로드합니다.

timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];

1/30초 마다 processGame 함수를 호출하도록 타이머를 설정합니다. processGame 함수에서는 [self setNeedsDisplay:YES];로 설정하여 DrawRect가 호출되어 이미지를 출력하게 합니다.
이제 컴파일을하고 실행을 합니다. 좌우 방향키로 우주선을 움직이며 스페이스키로 미사일을 발사할 수 있습니다. 미사일은 현재 한발씩만 발사 할 수 있습니다. 다음 장에서는 적기들을 만들고 움직여 보도록 하겠습니다.
AND

Xcode 2/Tip 2007. 8. 4. 09:49
#pragma mark [title]
사용 용도는 Xcode 에디터 상단에 함수목록을 볼 수 있는 팝업이 있습니다. 이 함수목록에서  [title]로 되어 있는 부분이 굵은 글씨체로 나오게 합니다. pragma mark는 컴파일 시에는 아무런 영향을 미치지 않고 무시가 됩니다.

아래는 사용 예 입니다.
#import <Foundation/Foundation.h>

#pragma mark print module

void print_hello()
{
    NSLog(@"Hello, World!");
}

void print_answer()
{
    NSLog(@"I'm fine~");
}

#pragma mark main

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    print_hello();
    print_answer();
    
    [pool release];
    return 0;
}


사용자 삽입 이미지
에디터 상단을 보면 파일 목록 우측에 소스코드상의 함수 목록을 보여주는 메뉴가 있습니다. 좌측의 붉은색 화살표가 가르키는 곳 입니다.


사용자 삽입 이미지
만약 위의 #pragma mark가 없다면 좌측과 같이 소스코드내의 함수목록들만 순서대로 보여 줍니다.



위 소스의 #pragm mark로 인해 print module, main이 아래와 같이 굵은 글씨로 보여 집니다. 이는 많은 함수를 가진 소스에서 종류별로 그룹화를 할 수 있어 함수들의 목록과 용도를 쉽게 알수 있도록 하여 줍니다.
사용자 삽입 이미지
AND

1.7.5 소스 수정

1) AppController.h 수정 및 MyWindow 연결

AppController.h를 오픈하여 아래와 같이 추가합니다. "/* */" 또는 "//"로 주석 처리된 부분은 생략 하셔도 됩니다.

#import <Cocoa/Cocoa.h>

/* 해당 버튼의 Tag 값 */
#define BTN_PLUS        100
#define BTN_MINUS        101
#define BTN_MULTIPLE    102
#define BTN_DIVISION    103
#define BTN_RESULT        104
#define BTN_CLEAR        105

@class MyWindow;

@interface AppController : NSObject {
    int prevOperation;       /* 사용자가 이전에 클릭한 연산 값 */
    int isClear;                   /* 연산을 클릭 후, 다시 값을 입력할 때 지원야 함을 알리는 플래그 */
   
    int totalValue;              /* 현재 계산 총 합 */
   
    IBOutlet MyWindow *myWindow;          /* 사용자 윈도우 */
    IBOutlet NSTextField *txtValue;             /* 현재/결과 값 표시 창 */
    IBOutlet NSTextField *txtOperation;      /* 현재 연산 모드 결과 표시 창 */
    IBOutlet NSTextField *txtHistory;          /* 연산 내역 표시 창 */
}

- (void)awakeFromNib;
- (void)processCalcul:(int)val;
- (void)processInput:(int)val;

- (IBAction) processCommand:(id)sender;
@end

저장 후에 Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

이 작업은 이전 장에서 하였지만 변경된 AppController를 인터페이스 빌더에서 작업하기 위해 다시 한번 반복합니다. 아래와 같이 AppController의 myWindow 아울렛을 Window에 연결합니다.
사용자 삽입 이미지

MainMenu.nib를 저장 후, 인터페이스 빌더를 닫습니다.

2) MyWindow 소스파일 변경

MyWindow.h를 오픈하여 아래와 같이 추가합니다.
#import <Cocoa/Cocoa.h>

@class AppController;

@interface MyWindow : NSWindow
{
    AppController *app;
}

- (void)setAppController:(AppController *)a;
- (void)keyDown:(NSEvent *)event;
@end

MyWindow.m을 오픈하여 아래와 같이 추가 합니다. 사용자의 키입력을 처리하고 AppController에 알려줄 수 있도록 합니다.
#import "MyWindow.h"
#import "AppController.h"

@implementation MyWindow
/* AppControllerd에서 호출 */
- (void)setAppController:(AppController *)a
{
    app = a;
}

/* 키가 눌려질 경우, 자동으로 호출된다. */
- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    int val = -1;
 
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];


    NSLog(@"KEY: %d", keyCode);
   
    /* 키값을 AppController에서 처리할 수 있도록 변경해 준다.
        각각의 키값은 좌측 숫자키 보드에서 누른 키값들이며, 위의 NSLog에서 확인할 수 있다. */
    if(keyCode >= 82 && keyCode <= 90) // 0~7
        val = keyCode - 82;
    else if(keyCode ==91) // 8
        val = 8;
    else if(keyCode ==92) // 9
        val = 9;
    else if(keyCode == 67) // *
        val = BTN_MULTIPLE;   
    else if(keyCode == 75) // /
        val = BTN_DIVISION;
    else if(keyCode == 69) // +
        val = BTN_PLUS;
    else if(keyCode == 78) // -
        val = BTN_MINUS;
    else if(keyCode == 71) // clear
        val = BTN_CLEAR;
    else if(keyCode == 81) // =
        val = BTN_RESULT;
   
    /* val이 세팅되어 처리해야 될 값일 경우에, AppController의 processInput에 값을 넘겨 준다. */
    if(val != -1)
        [app processInput:val];
   
    [self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
@end

3) AppController.m 소스파일 변경

#import "AppController.h"
#import "MyWindow.h"

@implementation AppController
/* awakeFromNib는 nib파일이 로드된 후, 오브젝트들에게 보내지는 메세지 입니다.
    어플리케이션 실행 후, 각종 초기화 작업을 할 수 있습니다.
*/
- (void)awakeFromNib
{
    isClear = 0;
    prevOperation = 0;
    totalValue = 0;

    /* myWindow의  AppController 변수를 세팅합니다.
        myWindow는 키보드 입력시 이 AppController로 전달합니다.
    */
    [myWindow setAppController:self];
}

/* 연산자 (+, -, X, /, = )가 입력되었을 경우, 처리 합니다. */
- (void)processCalcul:(int)val
{
    /* 이전에 설정된 연산자를 처리한다. */
    if(prevOperation == BTN_PLUS)
        totalValue += [txtValue intValue];
    else if(prevOperation == BTN_MINUS)
        totalValue -= [txtValue intValue];
    else if(prevOperation == BTN_MULTIPLE)
        totalValue *= [txtValue intValue];
    else if(prevOperation == BTN_DIVISION)
        totalValue /= [txtValue intValue];
   
    /* 연산명령이 처음 수행되었을 경우에는, 결과값을 현재 입력된 값으로 설정 합니다. */
    if([[txtHistory stringValue] length] < 1)
        totalValue = [txtValue intValue];
   
    char operation;
    NSString *strTemp;
  
    /* 입력된 키에 따라 출력될 문자를 설정 합니다. */
    if(val == BTN_PLUS)
        operation = '+';
    else if(val == BTN_MINUS)
        operation = '-';
    else if(val == BTN_MULTIPLE)
        operation = '*';
    else if(val == BTN_DIVISION)
        operation = '/';
    else
        operation = '=';

   
/* 계산 History에 출력될 값으로 = 일 경우에는 결과값을 출력합니다. */
    if(val == BTN_RESULT)
        strTemp = [[NSString alloc] initWithFormat:@"= %d\n", totalValue];
    else
        strTemp = [[NSString alloc] initWithFormat:@"%c", operation];
   
   
/* clear 모드를 1로 설정해 이후 다시 숫자를 입력 하면, 이전 결과 값이 지워진 후,
        새 입력값이 나올 수 있도록 합니다.
        prevOperation에 다음 연산 작업을 위해 입력된 연산값을 저장합니다.
    */
    isClear = 1;
    prevOperation = val;
   
    /* 연산 창에 현재 연산모드를 출력합니다. */
    [txtOperation setStringValue:[NSString stringWithFormat:@"%c", operation]];

    /* 연산내역 창에 현재 연산 내역을 추가 합니다. */
    [txtHistory setStringValue:[NSString stringWithFormat:@"%@ %@ %@",
        [txtHistory stringValue],
        [txtValue stringValue],
        strTemp]];
   
   
/* 결과 창에 현재까지 계산된 결과를 보여 줍니다. */
    [txtValue setFloatValue:totalValue];
    [strTemp release];
}

/* 버튼 클릭과 사용자의 키보드 입력을 처리 합니다. */
- (void)processInput:(int)val
{
    NSString *strTemp;
    
    if(val >= 0 && val <= 9)
    {
      
/* 숫자가 입력되었을 경우, 처리 합니다. */
        if(isClear == 1)
        {
             /* 연산을 클릭하고, 처음 숫자가 입력되었을 경우 결과창을 초기화 합니다. */
            [txtValue setStringValue:@""];
            isClear = 0;
        }   
       
        /* 결과 창에 현재 입력된 값을 추가하여 출력 합니다. */ 
        strTemp = [NSString stringWithFormat:@"%@%d", [txtValue stringValue], val];
        [txtValue setStringValue:strTemp];
    }   
    else if(val == BTN_CLEAR)
    {
       /* clear 키를 입력하였을 경우, 데이터와 출력을 초기화 합니다. */
        isClear = 0;
        prevOperation = 0;
       
        [txtValue setStringValue:@""];
        [txtHistory setStringValue:@""];
        [txtOperation setStringValue:@""];
    }
    else if(val >= BTN_PLUS && val <= BTN_RESULT)
    {
       /* 연산이 입력되었을 경우에 현재 값이 있는지 확인 후 처리 합니다. */
        if([[txtValue stringValue] length] > 0)
            [self processCalcul:val];
    }
}

/* 버튼이 클릭되었을 경우에, processInput에 알려 줍니다. */
- (IBAction) processCommand:(id)sender
{
    /* 현재 선택(클릭)된 버튼의 tag값을 얻어 옵니다. */
    int val = [[sender selectedCell] tag];
   
    [self processInput:val];
}
@end

이제 모든 작업이 완료되었습니다. 빌드하고 실행시켜 계산기 프로그램을 테스트 해 봅니다.

아직 처리하지 않는 부분이 많이 있습니다. 이 부분과 몇 가지 기능들을 다음 튜토리얼을 통해 계속 수정/추가해 나가겠습니다. 이번 장부터는 소스코드를 압축하여 올립니다. 참고하실 분은 다운 받아 압축을 풀고, Xcode에서 확인하시고, 이 소스코드에는 주석이 되어 있지 않습니다.

AND

1.7.1 프로젝트 개요 및 생성

1) 개요

사용자 삽입 이미지
이전에도 간단한 계산기를 만들어 보았지만, 이번에는 연속계산이 가능하고, 키보드 입력과  몇 가지 출력을 추가한 업그레이드 된 계산기를 만들어 보겠습니다.

좌측과 같은 계산기인데 첫번째  232라고 되어있는 창에 입력한 값과 계산된 값이 출력 됩니다. 그 옆의 작은 창에 현재 계산 모드가 출력 됩니다. 그 아래의 큰 창에는 계산했었던 내용들이 보여 집니다. 입력은 숫자 키패드와 버튼으로 받습니다.

이번 튜토리알에서는 새로운 내용인 키입력 처리와 NSMatrix에 대해서 알아 보겠습니다.


2) 프로젝트 생성

Xcode를 실행하고, MyCalculator로 cocoa aplication 프로젝트를 새로 만듭니다. 메뉴에서 File/New File을 선택하여 Objective-C 클래스를 선택하고 AppController 클래스를 만듭니다.

위의 과정에 대한 상세한 설명은 이전 포스트에서 설명하였으니, 앞으로는 자세한 과정은 생략하도록 하겠습니다.

1.7.2 AppController 생성 및 변경
Xcode에서 AppController.h을 열어 아래와 같이 추가하고, 저장합니다. 소스에 대한 설명은 다음 장에서 하겠습니다.

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    int prevOperation;
    int isClear;
   
    int totalValue;
   
    IBOutlet NSTextField *txtValue;
    IBOutlet NSTextField *txtOperation;
    IBOutlet NSTextField *txtHistory;
}

- (IBAction) processCommand:(id)sender;
@end

1.7.3 사용자 인터페이스 편집

1) AppController 인스턴스 생성

Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

사용자 삽입 이미지
Classes에서 AppController를 우클릭 한 후, 메뉴에서 Instantiate AppController를 클릭하여 인스턴스를 만듭니다.

좌측과 같이 인스턴스가 만들져 있는 것을 확인합니다.





2)  Window 컨트롤 배치 및 속성 변경

위의 Intances 창에 보이는 Window를 더블 클릭하여 윈도우를 엽니다.

사용자 삽입 이미지
팔레트에서 텍스트 필드 세개와 버튼 하나를 윈도우에 가져다 놓고 좌측과 같이 배치합니다.

처음 긴 텍스트 필드는 계산 결과 및 입력값, 우측 작은 텍스트 필드는 현재 연산자, 아래의 넓은 택스트 필드는 히스토리를 보여줍니다.

버튼 모양은 인스펙터에서 아래와 같이 설정하였습니다. 원하시는 모양을 선택하시면 됩니다.
사용자 삽입 이미지






사용자 삽입 이미지
첫번째 텍스트필드를 선택하고 인스펙터를 엽니다. 좌측과 같이 정렬을 우측으로 설정합니다. 인스펙터는 해당 오브젝트를 클릭 후에 메뉴 에서 선택 또는 shitf + commad(사과 키) + i를 동시에 누릅니다.

두번째 연산모드 텍스트 필드의 정렬을  가운데로 설정합니다.




사용자 삽입 이미지
위의 세 텍스트필드 모두, 옵션을 좌측과 같이 모두 체크를 해제 합니다. 키보드 입력을 윈도우가 모두 받기 위함입니다.


3) MSMatrix 생성

위의 작업들은 이전 튜토리얼에서 해 본 작업 들입니다. 이해가 가지 않는 분들은 이전 포스트를 확인해 주세요. 이제 NSMatrix를 사용하기 위해 새로운 작업을 해보겠습니다.

사용자 삽입 이미지
버튼을 클릭한 후, 쉬프트 + 옵션 + 마우스를 클릭한 상태에서, 버튼 우측 하단의 원을 클릭하면 좌측과 같이 하나의 원만 남습니다.

위의 상태에서 이 원을 드래그 하여 크기를 늘리면 버튼이 늘어 나는데, 4X4개의 버튼이 되게 합니다. 그리고 텍스트필드와 윈도우를 적당한 크기로 조절합니다.
 
사용자 삽입 이미지

위의 작업이 완료가 되면 좌측과 같은 모습을 보실 수 있습니다. 버튼을 더블클릭하면 텍스트를 수정 할 수 있습니다.

버튼을 더블클릭하여 아래와 같이 모든 버튼들의 텍스트를 변경합니다. 곱하기는 대문자 x를 사용합니다.
사용자 삽입 이미지







4) 버튼셀 tag 변경

이 작업은 매우 중요합니다. 아래와 같이 Tag 값을 정확히 설정하여야, 정확한 계산 결과 값을 보실 수 있습니다.

아래와 같이 0 버튼을 클릭하고 인스펙터에서 Tag값을 0으로 세팅합니다. 0~9까지의 버튼들을 모두 텍스트와 같은 숫자(1->1, 2->2, ...)로 변경합니다.
사용자 삽입 이미지

사용자 삽입 이미지
숫자를 제외한 +, -, X, /, =, C 기호들은 아래와 같이 Tag 값을 입력해 줍니다.

+->100, -->101, X->102, /->103, =->104, C->105


5) 텍스트 필드 Connection 설정

아래와 같이 텍스트필드들을 AppController의 아울렛들과 연결해 줍니다.
012

사용자 삽입 이미지
좌측과 같이 NSMatrix를 AppController의 processCommand에 연결 시킵니다.












6) 윈도우 서브클래스 생성 및 Window와 연결

이제 윈도우를 위한 서브클래스와 소스파일을 생성합니다.
사용자 삽입 이미지
MainMenu.nib파일의 Classes 윈도우에서 NSWindow의 서브클래스를 생성합니다.

NSWindow는 NSObject > NSResponder 밑에 있습니다.






사용자 삽입 이미지
NSWindow의 서브클래스 MyWindow를 우클릭하여 나오는 메뉴에서  Create Files for MyWindow를 선택하여, MyWindow.h와 MyWindow.m 파일을 생성합니다.





사용자 삽입 이미지
Window의 인스펙터를 오픈한 후, Custome Class에서 위에서 만든 MyWindow를 선택합니다.





1.7.4 윈도우, 메뉴, 아이콘 설정

1) 윈도우 설정

사용자 삽입 이미지
왼쪽과 같이 윈도우 타이틀을 MyCalculator로 변경합니다.

Title bar controls에서 Zoom (and resize)를 해제 하여 사용자가 윈도우의 크기를 변경 할 수 없게하며, 우측 하단에 나오는 사이즈 변경 탭이 사라집니다.



2) 메뉴 설정

사용자 삽입 이미지
MainMenu를 클릭하여 좌측과 같이 메뉴 부분에서 New Application으로 되어 있는 부분을
MyCalculator로 변경합니다.










3) 아이콘 설정

이전 튜토리얼 SimpleViewer 이미지뷰어 (2) 1.6.4 어플리케이션 다듬기에서 4) 아이콘 변경을 참조하여, 원하는 아이콘으로 변경합니다.

저는 계산기 어플리케이션 자체를 아이콘으로 만들었습니다. shift + command + 4를 동시에 누르면 캡쳐할 범위를 선택할 수 잇는 십자모양의 커서가 나옵니다. 이 커서를 계산기로 이동하여 스페이스를 누르면 해당 윈도우만 캡쳐할 수 있습니다.

위의 튜토리얼에서 3) About SimpleViewer 판넬 변경을 참조하여, 판넬부분도 변경하여 줍니다. 빌드 후 어플리케이션을 실행시킵니다. 메뉴에서 About MyCalculator를 클릭하여 아래와 같이 변경사항을 확인합니다.
사용자 삽입 이미지

이제 계산기 어플리케이션의 겉모습이 완성되어습니다. 다음 장에서는 실제로 계산기가 동작하도록 소스코드를 작성해 보겠습니다.

개인적으로 일이 있어 오랫만에 포스트를 올립니다. 저도 오랫만에 Xcode를 실행해 보니 왠지 낯서네요. 꾸준히 올릴 수 있도록 하겠습니다.


AND

1.6.4 어플리케이션 다듬기
이번에는 이전에 만들어 놓은 SimpleViewer에 스크롤을 추가하고, 아이콘을 변경하고, 몇 가지 세부사항을 변경하는 작업을 해보겠습니다.

1) 스크롤 추가

인터페이스 빌더에서 윈도우를  선택하고 메뉴에서 아래와 같이  Layout/Make Subviews of/ScrollView를 클릭합니다.
사용자 삽입 이미지

사용자 삽입 이미지
위와 같이 하면 ImgView 우측과 하단에 좌측과 같이 여백이 생기는 것을 확인하실 수 있습니다.










ImgView와 NSScrollView의  인스펙트를 오픈 해  Size의 Autosizing 항목을 각각 아래와 같이 변경해 줍니다. NSScrollView는 우측 하단 모서리 부분을 클릭하면 선택됩니다. 인스펙터 창의 상단 타이틀에서 현재 선택된 항목을 확인 합니다.
사용자 삽입 이미지 사용자 삽입 이미지

2) 메뉴 타이틀 변경

사용자 삽입 이미지
SimpleViewer를 실행 시키고 메뉴를 보면 좌측과 같이
About NewApplication
Hide NewApplication
Quit NewApplication
과 같이 어플리케이션 이름이 아니라 인스펙트 빌더의 기본값인 NewApplication으로 되어 있습니다.



사용자 삽입 이미지
인스펙터 빌더에서 MainMenu.nib에서 각각의 항목을 더블클릭 해 좌측과 같이 모든 NewApplication을 SimpleViwer로 변경합니다.

Nib 파일은 실행시 로드되기 때문에 컴파일 없이 인스펙터 빌더에서 저장하고 어플리케이션을 실해 하여 확인해 봅니다.





3) About SimpleViewer 판넬 변경

사용자 삽입 이미지
어플리케이션을 실행 시키고, 메뉴에서 About SimpleViewer를 클릭하면 좌측과 같은 판넬을 볼 수 있습니다.

__MyCompanyName__으로 되어 있는 부분과 버젼을 변경해 보겠습니다.





사용자 삽입 이미지
Xcode에서 좌측과 같이 Resources 그룹에서 info.plist를 에디터에서 오픈합니다.

하단을 보면 아래와 같이 CFBundelVersion 키에서 string으로 되어 있는 부분에 1.0으로 되어 있는 값을 원하는 버젼으로 수정합니다.

    <key>CFBundleVersion</key>
    <string>0.9</string>



이제는 그 밑의 infoPlist.strings를 열어 NSHumanReadableCopyright 부분을 원하는 저작권자 명으로 변경하여 줍니다.

NSHumanReadableCopyright = "© cocoadev.tistory.com, 2007";

이제 다시 프로그램을 실행시키고 메뉴에서 About을 클릭하면 아래와 같이 변경된 사항을 확인할 수 있습니다.
사용자 삽입 이미지


4) 아이콘 변경

사용자 삽입 이미지
프로그램을 실행해서 하단의 독을 보거나,  폴더에서 실행파일의 아이콘을 보면 좌측과 같이 맥의 기본 응용프로그램 아이콘으로 되어 있습니다.

이번에는 이 아이콘을 변경해 보겠습니다.

/Developer/Applications/Utilities에서 Icon Composer를 실행합니다.
사용자 삽입 이미지
좌측과 같이 Image RGB/Alpha의 각각의 사각형 영역에  원하는 이미지 파일을 드래그해서 가져다 놓습니다.

저는 128X128 JPG 이미지를 하나 만들어 Thumbnail(128X128) 영역에 드래그 해 놓고, 그 부분을 위로 계속 드래그 시켜 항목을 채웠습니다.

이제 메뉴에서 아이콘을 적당한 이름으로 저장합니다.





사용자 삽입 이미지
위에서 저장한 아이콘 파일(*.icns)을 Xcode 좌측 메뉴에서 Resources 그룹에 드래그 해서 가져다 놓으면 좌측과 같은 창이 나옵니다.

상단의  Copy items into destination group's folder(if needed) 항목의 체크를 확인하고 add 버튼을 클릭합니다.





이제 이전에 열었던 info.plist를 열어 CFBundleIconFile 항목을 아래와 같이 설정합니다.
    <key>CFBundleIconFile</key>
    <string>simpleviewer</string>
저는 아이콘 파일명을 simpleviewer.icns로 만들었습니다. 이전에 저장한 icns를 제외한 파일명을 입력해 줍니다.

이제 어플리케이션을 실행하여 About 메뉴를 클릭하면,  아래와 같이 변경된 내역을 확인할 수 있습니다.
사용자 삽입 이미지




AND

1.6.0 프로젝트 개요
JPG, GIF등의 이미지 파일을 NSImage를 이용하여 화면에 출력하고 , 확대 축소를 해주는 간단한 이미지 뷰어 프로그램을 만들어 보겠습니다.
사용자 삽입 이미지

1.6.1 프로젝트 생성
이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleDraw 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. project Name에 SimpleViewer를 입력하고 finish 버튼을 클릭합니다.

1.6.2 인터페이스 빌더에서 작업

1) 서브 클래스 생성

Nib 파일을 클릭하여 인터페이스빌더를 열고 NSView 클래스의 서브클래스를 만들고(아래의 창에서 NSView에 마우스 우클릭 하여 Subclass NSView 클릭) 이름을 ImgView로 변경합니다.
사용자 삽입 이미지

위와 같은 방법으로 AppController란 NSObject의 서브클래스를 만듭니다. 서브클래스를 만든 후에 AppController를 우클릭하여 나오는 메뉴에서 Instantiate AppController를 클릭하여 인스턴스를 생성합니다.
사용자 삽입 이미지


2) 컨트롤 생성 및 속성 설정

윈도우를 열고 팔레트의 Containers 항목에서  CustomeView와 Controls 항목에서 Slider를 드래그해서 아래와 같이 윈도우에 배치 합니다.

CutomeView를 클릭 후에 인스펙터(command+5)를 불러 내어 Custom Class 항목에서, 이전에 만들어 놓은 ImgView를 선택하고 아래와 같이 CustomeView가 ImgView로 변경됨을 확인합니다.
사용자 삽입 이미지

Size 항목에서 하단의 Autosizing 항목에서 아래와 같이 사각형내의 선들을  마우스로 클릭하여 스프링처럼 나오게 만듭니다. 이 작업은 윈도우의 크기가 변경될 때 상하좌우 크기가 윈도우에 맞게 자동으로 변경되게 합니다.
사용자 삽입 이미지

슬라이더의 속성을 아래와 같이 설정 합니다. Minimum은 최소값, Maximum은 최대값, Current는 현재값, Number of Markers는 슬라이더 상단의 눈금의 갯수를 의미합니다.
사용자 삽입 이미지

슬라이더도 윈도우의 크기에 맞추어 변경되게 하기위해, 아래와 같이 Size 항목에서 하단의 Autosizing의 사각형 내부의 좌우선을 클릭하여 스프링이 되게 만듭니다. 이 작업은 슬라이더의 높이는 유지하며 너비가 윈도우의 크기와 함께 변하도록 만들어 줍니다.
사용자 삽입 이미지

3) 연결

사용자 삽입 이미지
인터페이스 빌더의 Instances항목에서 AppController의 인스펙터를 불러내어 imgView 아울렛을 추가하고, Type을 ImgView로 선택합니다.

인스펙터는 항목을 마우스로 클릭하여 포커스를 준 후에 shift+command+i 또는 commad+숫자 를 입력하여 불러낼 수 있습니다.

이 작업은 AppController에서 ImgView를 제어할 수 있도록 합니다.










사용자 삽입 이미지
이제 Actions 항목을 선택하고 resizeImage와 openImage 액션을 추가합니다.

resizeImage는 하단의 슬라이더가 변경될 때, 이미지 크기를 변경하고, openImage는 메뉴에서 파일을 오픈할 때 처리하기 위한 액션입니다.




control 키를 누른 상태에서 AppController를 드래그하여  ImgView에 놓습니다. 아래와 같이 AppControll의 ImgView 아울렛에 연결(connect 버튼을 클릭 후, 원모양 아이콘을 확인)합니다.
사용자 삽입 이미지

control 키를 누른 상태에서 슬라이더를 드래그하여 AppController에 놓습니다. Taget/Action에서 resizeImage에 연결합니다.
사용자 삽입 이미지

아래와 같이 메인메뉴의 File에서 오픈을 control 키를 누른 상태에서 드래그하여 AppController에 놓습니다. Target/Action에서 openImage에 연결합니다. 이 작업으로 사용자가 메뉴에서 Open을 클릭하면 AppController의 openImage를 호출합니다.
사용자 삽입 이미지

이제 인스펙트빌더에서 1차적인 작업이 완료되었습니다. Classes에서 AppController와 ImgView를 각각 우클릭하여 CreateFiles  for AppController(and Img View)를 선택하여 소스파일을 생성하고 Xcode로 돌아 갑니다.


1.6.3 소스코드 수정
1) AppController.h 수정

인터페이스 빌더에서의 작업으로 헤더파일에서 해야 할 작업은 거의 없습니다. 다만 컴파일 시 오류를 막기 위해 아래와 같이 @class ImgView; 란 라인을 추가합니다.
#import <Cocoa/Cocoa.h>

@class ImgView;

@interface AppController : NSObject
{
    IBOutlet ImgView *imgView;
}
- (IBAction)openImage:(id)sender;
- (IBAction)resizeImage:(id)sender;
@end


2) AppController.m 수정

#import "AppController.h"
#import "ImgView.h"

@implementation AppController

- (void)openPanelDidEnd:(NSOpenPanel *)openPanel
             returnCode:(int)returnCode
            contextInfo:(void *)x
{   
    NSString *path;
    NSImage *image;
   
    if (returnCode == NSOKButton) {
        path = [openPanel filename];
   
        NSImageRep *imgRep = [NSImageRep imageRepWithContentsOfFile:path];
        NSSize frameSize = [imgRep size];
       
        image = [[NSImage alloc] initWithContentsOfFile:path];
       
        [imgView setImage:image];
        [imgView setFrameSize:frameSize];
        [image release];
    }
}

- (IBAction)openImage:(id)sender
{
    NSOpenPanel *panel = [NSOpenPanel openPanel];
   
    [panel beginSheetForDirectory:nil
                             file:nil
                            types:[NSImage imageFileTypes]
                   modalForWindow:[imgView window]
                    modalDelegate:self
                   didEndSelector:
        @selector(openPanelDidEnd:returnCode:contextInfo:)
                      contextInfo:NULL];
}

- (IBAction)resizeImage:(id)sender
{
    [imgView setRatio:[sender floatValue]];
}

@end

#import "ImgView.h"
ImgView 클래스를 사용하기 때문에 위와 같이 ImgView.h를 인클루드 시킵니다.

아래의 메소드는 파일 오픈을 클릭하였을 경우, 파일선택 창에서 파일 선택이 완료 된 후에 불리어 지는 콜백 함수입니다. 이 메소는 파일오픈 판넬을 오픈할 때 등록하여 주며, 나중에 다시 설명하겠습니다.
- (void)openPanelDidEnd:(NSOpenPanel *)openPanel
             returnCode:(int)returnCode
            contextInfo:(void *)x
{   
    NSString *path;
    NSImage *image;

사용자가 파일 창에서 [열기] 버튼을 클릭하였을 경우에만 실행되도록 합니다.  
    if (returnCode == NSOKButton) {
        path = [openPanel filename];
   
파일 원본 크기를 알기 위해 NSImageRep 오브젝트를 사용합니다.
        NSImageRep *imgRep = [NSImageRep imageRepWithContentsOfFile:path];
        NSSize frameSize = [imgRep size];
     
NSImage에 이미지 파일을 등록합니다.  
        image = [[NSImage alloc] initWithContentsOfFile:path];
       
imgView에 이미지를 설정하고, imgView의 크기를 이미지의 원본크기로 변경합니다.
        [imgView setImage:image];
        [imgView setFrameSize:frameSize];
        [image release];
    }
}

다음은 사용자가 파일오픈을 선택하였을 때 호출되는 - (IBAction)openImage:(id)sender 메소드에서 파일선택 판넬을 오픈하기 위해 아래와 같이 추가합니다.
    NSOpenPanel *panel = [NSOpenPanel openPanel];
   
    [panel beginSheetForDirectory:nil
                             file:nil
                            types:[NSImage imageFileTypes]
                   modalForWindow:[imgView window]
                    modalDelegate:self
                   didEndSelector:
        @selector(openPanelDidEnd:returnCode:contextInfo:)
                      contextInfo:NULL];
모달(판넬이 떠있을 때는 닫기전에는 소유 윈도우는 제어되지 않습니다)로 뛰우며 선택 가능한 파일 종류, 소유 윈도우, 콜백함수를 설정합니다.

@selector(openPanelDidEnd:returnCode:contextInfo:) 이 부분에서 파일선택이 완료되면 위에서 작성한 openPanelDidEnd 메소드가 호출되도록 하여 줍니다.

[imgView setRatio:[sender floatValue]];
슬라이더가 변경되면 호출되는 resizeImage 메소드에서 imgView가 크기를 변경하도록 setRatio를 호출합니다. 이 메소드는 다음 ImgView 클랙스에서 작성되어질 것 입니다.

3) ImgView.h 수정

#import <Cocoa/Cocoa.h>

@interface ImgView : NSView
{
    NSImage *image;
    NSSize orgSize;
    float ratio;
}

- (void)setImage:(NSImage *)img;
- (void)setRatio:(float)r;

@end

NSImage *image;

이미지 출력을 위해 NSImage 클래스를 선언 합니다.

NSSize orgSize;

이미지의 원본 크기를 저장합니다.

float ratio;
이미지 확대/축소 비율을 저장합니다. 0이 최소, 20이 최대, 10이 원본크기를 출력합니다.

- (void)setImage:(NSImage *)img;
선택된 이미지를 image에 설정하는 메소드입니다. 파일메뉴에서 파일 선택 시 AppController에서 호출합니다.

- (void)setRatio:(float)r;
확대/축소 비율을 설정합니다. 슬라이더 변경 시 AppController에서 호출합니다.

4) ImgView.m 수정

#import "ImgView.h"

@implementation ImgView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
        ratio = 10;
    }
    return self;
}

- (void)drawRect:(NSRect)rect
{
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:rect];
   
    if(image) {
        NSRect imgRect;
        NSRect drawRect;
       
        if(ratio != 0.0f) {
            imgRect.origin = NSZeroPoint;
            imgRect.size = orgSize;
       
            drawRect = [self bounds];
       
            NSLog(@"drawRect: %f, %f", drawRect.size.width, drawRect.size.height);
           
            [image drawInRect:drawRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];
        }   
    }
}

- (void)setImage:(NSImage *)img
{
    [img retain];
    [image release];
   
    image = img;
   
    orgSize = [image size];
   
    [self setNeedsDisplay:YES];
}

- (void)setRatio:(float)r
{
    NSSize viewSize;
    ratio = r;
   
    NSLog(@"RATIO: %f", ratio);
   
    viewSize.width = (orgSize.width * ratio)/10;
    viewSize.height = (orgSize.height * ratio)/10;
   
    [self setFrameSize:viewSize];
    [self setNeedsDisplay:YES];
}

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

@end

ratio = 10;
initWithFrame는 ImgView가 초기화될 때, 자동으로 호출되는 메소드 입니다. 이곳에서 초기 오브젝트를 생성하거나 변수를 초기화 합니다. 이미지 선택시 원본 크기로 보이게 하기 위해 ratio를 10으로 설정합니다.

다음은 drawRect 메소드 내에 아래의 라인을 추가합니다. drawRect는 ImgView가 그려져야 할 필요가 있을 때 자동으로 불려지는 메소드 입니다. 이곳에서 그려질 내용들을 처리 합니다.

[[NSColor whiteColor] set];
[NSBezierPath fillRect:rect];
배경색을 흰색으로 채웁니다.

이미지 오브젝트가 설정되었을 때만 출력합니다.
    if(image) {
        NSRect imgRect;
        NSRect drawRect;

확대비율이 0일때는 출력하지 않습니다.  
        if(ratio != 0.0f) {
            imgRect.origin = NSZeroPoint;
            imgRect.size = orgSize;
       
            drawRect = [self bounds];

이미지를 출력합니다.  
            [image drawInRect:drawRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];
        }   
    }
}

아래의 setImgae에서는 사용자가 선택한 파일을 image에 설정합니다.
- (void)setImage:(NSImage *)img
{
    [img retain];
    [image release];
 
image를 설정하고 원본 사이즈의 크기를 저장합니다.
    image = img;
    orgSize = [image size];
다시 그려지도록 알려주며, 이 위의 drawRect가 호출됩니다.  
    [self setNeedsDisplay:YES];
}

아래의 setRatio에서는 사용자가 슬라이더의 변경시에, 크기를 변경해 주는 작업을 합니다.
- (void)setRatio:(float)r
{
    NSSize viewSize;
    ratio = r;

10을 원본 크기로 비율에 맞게 크기를 조절합니다.  
    viewSize.width = (orgSize.width * ratio)/10;
    viewSize.height = (orgSize.height * ratio)/10;

ImgView의 크기를 현재 배율에 맞게 변경합니다.  
    [self setFrameSize:viewSize];
다시 그려지도록 알려주며, 이 위의 drawRect가 호출됩니다.
    [self setNeedsDisplay:YES];
}

메모리에서 해제합니다.
- (void) dealloc
{
    [image release];
    [super dealloc];
}

이제 모든 변경사항을 저장하고 프로젝트를 빌드 후에, 이미지 파일을 선택하여 테스트 해봅니다. 이미지를 불러보고 윈도우의 크기를 변경해 봅니다. 아직 스크롤을 처리하지 않아 커다란 이미지는 제대로 보실 수 없을 것입니다. 이는 다음장에서 처리해 보겠습니다.

AND

Objective-C는 1980년대 Stepstone사의 Brad Cox와 Tom Love에 의해 기존의 C에 SmallTalk의 객체지향 장점을 추가하여 만들어진 멋진 언어라고 합니다. 하지만 주위에선 별로 사용자를 볼 수가 없습니다.

개발환경이 특정 하드웨어나 OS로 한정되어 있고, 가장 많은 사용자를 가진 윈도우즈용 어플리케이션을 제작할 수 없어서(제가 아는 한에서 이며, 확실치 않습니다.) 인 것 같습니다.

보기에도 언뜻 Objective-C 소스를 보면 C와는 상관없는 전혀 별개의 언어로 보여, C/C++ 사용자가 접근이 힘들어 보입니다. 이유는 C에다 클래스와 메시지 전달 방식의 메소드 등 추가된 문법 때문입니다.

하지만 이 부분은 쉽게 배울 수 있으며, 어디선가 C를 알고 객체지향 프로그램에 대한 이해가 있다면 2시간 이면 Object-C를 배울 수 있다는 내용을 본적이 있습니다. 저같이 C/C++을 잘하지 못하는 사람도 대충 배워 가는 것을 보면, 틀린 얘기는 아니라고 생각됩니다.

그래서 C/C++, 또는 윈도우즈에서 VC++을 사용해 보시고, Objective-C 경험이 전혀 없으신 분들을 위해 이 블로그의 제목처럼 맛만 보실 수 있도록 부실한 내용을 시작 하겠습니다.
 
우선 Xcode에서 cocoa application 프로젝트를 만들면, Xcode에서 자동으로 생성해주는 main.m를 확인해 보겠습니다. (만드는 방법은 튜토리얼 분류쪽의 포스트에 있습니다.)

우선 Objective-C에서는 헤더파일은 .h로 같은 이름을 사용하지만, 소스파일명에는 .c 대신에 .m 확장자를 사용함을 알 수 있습니다. 소스를 보겠습니다.

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}

MS처럼 윈도우로 넘어 오면서 C/C++ 고유의 main이란 이름을 가만히 놔두지 않는 것에 비하여, 전형적이고 친숙한 C의 main이 보입니다.

import만 생소하고 모두 C와 동일합니다. import는 Objective-C에서 추가된 전처리 명령어로 include와 유사하지만, 중복될 경우에는 한번만 include합니다. import대신 include의 사용도 가능합니다. 하지만 중복되어 인클루드될 경우를 대비해 헤더파일에
#ifndef _HEADER_NAME_H
#define _HEADER_NAME_H
.........
#endif  //_HEADER_NAME_H
과 같은 처리가 필요하지만 import를 사용하게 되면, 중복 오류를 신경 쓸 필요가 없습니다.

우선 테스트를 위해 아래와 같이 C코드를 추가하고 컴파일을 해봅니다.
void copy_str(char* des, char* src)
{
    strcpy(des, src);
}

int main(int argc, char *argv[])
{
    char buffer[12];
    char* ptr;
    
    copy_str(buffer, "Hellow!");
    ptr = buffer;
    
    printf("str: %s\n", ptr);
    
    return NSApplicationMain(argc,  (const char **) argv);
}

main을 봐도 알수 있듯이 C문법이 오류없이 컴파일 되며, 실행을 하게 되면 빈 윈도우와 함께 로그 윈도우에 str: Hello!를 출력합니다.

이 예를 들은 이유는 Objective-C가 C와 전혀 별개의 언어가 아닌 C언어의 확장으로 봐도 무방할 것 같습니다. 위에 언급한 바와 같이 C언어 또는 C++ 사용이 가능하면, 작은 노력으로도 쉽게 Objective-C를 사용할 수 있습니다.

이제 다른 소스를 확인해 보겠습니다. 아래는 Xcode에서 제공되는 샘플소스의 헤더 파일과 소스파일의 일부분입니다.

@interface DotView : NSView {
    float radius;
}
// Standard view create/free methods
- (id)initWithFrame:(NSRect)frame;
- (void)dealloc;
@end

#import <Cocoa/Cocoa.h>
#import "DotView.h"

@implementation DotView

- (void)mouseUp:(NSEvent *)event {
    NSPoint eventLocation = [event locationInWindow];
    center = [self convertPoint:eventLocation fromView:nil];
    [self setNeedsDisplay:YES];
}
@end

위에 테스트로 작성된 소스와는 다르게 객체를 사용하기 때문에, 전혀 C와는 관련 없는 소스로 보입니다.

C++이나 Java등을 사용하신 분들은 class의 선언과 구현인지 대충 짐작이 가시겠지만, C 문법이라고 보기엔 import, -, @, [], : 등 많은 부분이 눈에 거슬립니다. 아래에서 확연히 다른 점을 간단히 다루어 보겠습니다.

1) "@" 예약어

우선 "@"으로 시작되는 것은 Objective-C에서 추가된 예약어 입니다.

위에서 예를 보면 클래스의 선언부분은  @interface, @end로 구현부분은 @implementation, @end의 사이에 위치합니다.

또한 "hello"는 char* 형의 문자열을 의미하지만, @"hello"는 NSString에서 사용하는 문자열(참고로 @""는 아스키 코드만 가능하며, 한글은 UTF8로 처리해야 합니다.)을 의미하는 것과 같이 기존 C의 문법과 구별이 필요할 때에 사용한다고 보시면 됩니다..

2) 함수 선언

헤더파일을 보면 클래스의 메소드 선언 시 C++/Java와는 달리 클래스 선언 구역({})의 외부에서 선언 됩니다.

위의 - (id)initWithFrame:(NSRect)frame; 선언을 예로 들어 보겠습니다.

함수 앞에는 "-" 표시가 있는데 이는 인스턴스 메소드를 나타내며, "+"일 경우에는 클래스 메소드를 나타냅니다. "+"는 C++/Java의 static 맴버함수와 유사하여 인스턴스의 생성 없이 바로 사용할 수 있는 메소드입니다.

(id) 는 반환될 타입입니다. ()로 처리된다는 것만 제외하고 C와 동일합니다. 참고로 id는 모든 오브젝트를 가리키는 포인터입니다.

":"는 다음에 인자를 의미하며 인자가 ":(타입)이름"과 같이 나온다는 것을 의미합니다. 인자가 2개 이상일  경우에는 스페이스로 구분하며 아래와 같이 선언 합니다.

- (NSPoint)convertPoint:(NSPoint)aPoint fromView:(NSView *)aView;
보면 fromView라는 것이 혼돈을 주는데 인자의 별칭(alias)이라고 생각하시면 되고, 실제 호출 시에는 아래와 같이 사용합니다.

[self convertPoint:eventLocation fromView:nil];
이렇게 함으로써 소스코드는 길어 지지만, 메소드와 인자의 용도를 명확하게 합니다.

여기에는 없지만 변수앞에 사용하는 IBOutlet과 메소드에 사용하는 IBAction란 예약어가 있습니다. 이는 인터페이스 빌더에서 참조를 위한 것으로 변수와 메소드 타입에 영향을 주지 않습니다.  VC의 AFX_ 류로 생각하시면 됩니다.


3) 메소드 호출

center = [self convertPoint:eventLocation fromView:nil];
소스파일에 있는 위의 코드를 C++로 변경하면 아래와 같습니다.

center = this->convertPoint(eventLocation, NULL);

Objective-C에서는 메소드 호출(정확히는 메세지 전달)시 에는 [object method]의 형태로 사용됩니다. [object method:[object method]]와 같이 중첩해서 사용이 가능하며, 초기에는 혼돈이 오지만 자주 보면 object->method(object->method)와 같이 친숙해 집니다.


4) 프레임워크

라이브러리와 유사한 의미로 프레임워크라는 용어를 사용합니다. cocoa 프로젝트를 생성하면 Xcode 좌측의 Groups & Files에서 FrameWorks란 폴더를 찾을 수 있습니다.

 하단의 Other Frameworks를 보면 AppKit.framework와 Foundation.framework를 확인하실 수 있습니다. 이는 VC에서 MFC 클래스 라이브러리와 유사합니다.

Foundation은 NSObject, NSString, NSArray등의 기본적인 클래스들로 구성이 되어 있으며, AppKit은 NSWindow, NSButton, NSImage등 사용자 UI에 관련된 클래스들로 구성되어 있습니다.
 
사용자 삽입 이미지
좌측과 같이 이 두 framework 아래의 headers를 클릭해 보시면 cocoa 개발을 위한 기본적인 클래스 목록들을 확인하실 수 있습니다.






5) 기타

- Objective-C는 C++/Java와는 달리 class에 생성자/소멸자가 없지만, 이를 대치해서 사용할 수 있는 메소드와 이벤트가 있습니다.

- retain이라는 사용 카운터를 사용하여 오브젝트가 메모리에서 삭제되는 시기를 결정합니다. 인스턴스가 추가(alloc)되면 retain이 증가되고, 사용이 완료되면 release라는 메소드로 retain을 감소 합니다. retain이 0이 되면 삭제됩니다.

- Nib 파일 - NeXT Interface Builder의 약자로 오브젝트, 클래스, 리소스, 컨넥션, UI등의 정보와 파일을 가지고 있는 cocoa 어플리케이션에서는 매우 중요한 역활과 의미를 가지고 있는  파일 입니다.

이상으로 마치며... 프로그래밍 언어를 한번 정도 다루어 보신 분들, 특히 C++, Java라면 Objective-C는 쉽게 접근할 수 있는 언어입니다. C에 객체지향을 더했다는 측면에서, C++과 만들어진 이유와 나온 시기도 비슷하여 서로 비교해 보는 것도 재밌습니다. Cocoa와 MFC, VC++과 Xcode도 그렇고요.

앞으로 맥사용자들이 많이 늘어, C++ 사용자 처럼 Objective-C 사용자들이 많이 늘었으면 하는 바램입니다.
 

'Xcode 2 > Objective-C' 카테고리의 다른 글

Objective-C 코딩 스타일  (6) 2008.09.25
Objective-C class의 특징  (2) 2008.03.17
AND

1.5.0  프로젝트 개요
이번에는 NSView를 이용하여 도형들과 텍스트를 출력하는 예제를 만들어 보겠습니다. 아래와 같이 각각의 버튼들을 클릭하면 각각의 내용들을 출력하는 간단한 프로그램 입니다.
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지






1.5.1  프로젝트 생성

이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleDraw 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. roject Name에 SimpleDraw를 입력하고 finish 버튼을 클릭합니다.

1.5.2 인터페이스 빌더에서 작업
1) NSView 서브클래스 만들기

사용자 삽입 이미지
Xcode에서 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. 좌측과 같이 Classes 에서 NSObject > NSResponder > NSview를 우클릭(또는 control + 클릭) 하여 메뉴에서 Subclass NSView를 선택 합니다.


사용자 삽입 이미지
MyView라는 서브 클랙스가 생성됩니다. 여기서는 MyView라는 이름을 사용하지만, 용도에 맞는 이름으로 변경하셔도 됩니다.







사용자 삽입 이미지
다음은 MyView에 그리기 명려을 처리할 메소드를 추가합니다. 좌측과 같이 MyView를 우클릭 하여 Add Action to MyView를 선택 합니다.




 
사용자 삽입 이미지
좌측과 같이 기본으로 만들어진 myAction을 drawItem으로 변경합니다.







사용자 삽입 이미지
이제 소스파일을 생성하기 위해 MyView 클래스를 다시 우클릭하여 Create Files for MyView를 선택합니다.

[Choose] 버튼을 클릭하여 MyView.h와 MyView.m 파일을 생성합니다.



2) NSView 속성 설정
 
이제는 NSView와 버튼들을 배치하고 속성들을 설정해 보겠습니다. 아래와 같이 윈도우를 열고 팔레트에서 여섯번째 메뉴인 Containers를 선택하고, 윈도우로 CustomView를 드래그 하여 가지고 옵니다.
사용자 삽입 이미지

사용자 삽입 이미지
윈도우에서 CustomView가 선택된 상태에서 [shift + command + i]를 눌러 인스펙터를 오픈한 후, 상단에서 Custom Class를 선택합니다.

또는 [command + 5]를 눌러서 바로 Custom Class를 오픈해도 됩니다.

Class 항목에서 이전에 만들어 놓은 MyView를 선택합니다.


사용자 삽입 이미지
완료되면 CustomView가 MyView로 변경됩니다. MyView의 사이즈를 좌측과 같이 적절하게 조절 합니다.

상단의 Window에서 SimpleDraw로의 변경은 이전 장에서 해본 것과 같이 윈도우의 인스펙터를 불러내어 Window Title 항목을 변경합니다.






3) 버튼 생성 및 속성 설정

이제 버튼을 배치합니다. 아래와 같이 팔레트에서 버튼을 불러와 버튼을 복사/붙여넣기 하여 네개의 버튼을 만든 후 MyView 하단에 배치 합니다.
사용자 삽입 이미지

사용자 삽입 이미지
좌측과 같이 네개의 버튼의 제목을 각각 사각형, 원, 선, 문자로 변경합니다.

자세히 보면 버튼의 모양이 기본 버튼과 다른데, 이는 아래에서 설명하겠습니다.









사용자 삽입 이미지
사각형 버튼에서 인스펙터를 열어 보면, Tag에 1로 되어 있습니다. 이는 각각의 버튼이 클릭되었을 때, MyView의  DrawItem 메소드에서 구별하기 위해 설정 합니다.

사각형은 1, 원은 2, 선은 3, 문자는 4로 Tag 값을 각각 설정 합니다.

위의 버튼 모양은 Type 항목에서 Round Textured Button을 선택한 모양 입니다.


이제 버튼 클릭시 동작을 연결합니다. 이전 장과 같이 사각형 버튼을 control키를 누른 상태에서 드래그 하여 MyView에 가져다 놓습니다. 아래와 같이 인스펙터 창이 나타나면 Target/Actiond에서 drawItem을 선택하고 하단의 Connect 버튼을 클릭합니다.
사용자 삽입 이미지
위의 작업을 원, 선, 문자 버튼에서도 똑같이 drawItem에 연결 하여 줍니다.


1.5.3 소스코드 작성

1) 헤더파일 수정

이제 Xcode로 돌아 와서 소스코드를 수정합니다. 우선 MyView.h를 에디터에서 오픈합니다.

#import <Cocoa/Cocoa.h>

@interface MyView : NSView
{
    int itemType;
   
    NSString *txt_message;
    NSMutableDictionary *txt_attributes;
}
- (IBAction)drawItem:(id)sender;
@end

헤더파일에는 아래와 같이 세가지 속성들이 추가되었습니다.

int itemType;
어떤 방식으로 그릴지 설정을 저장할 변수 입니다. 위에서 버튼들의 인스펙터에서 설정 한 tag값(1, 2, 3, 4)들이 저장됩니다.

NSString *txt_message;
출력할 문자를 저장합니다.

NSMutableDictionary *txt_attributes;
폰트, 색상 등 문작의 출력 속성을 저장 합니다.

2) 소스 파일 변경 사항

이제 MyView.m을 에디터에서 오픈합니다. 이번에는 소스가 조금 길어 졌습니다.

#import "MyView.h"

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
        itemType = 0;
        
        txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
        txt_attributes = [[NSMutableDictionary alloc] init];
        [txt_attributes setObject:[NSColor blueColor]
                                   forKey:NSForegroundColorAttributeName];
    }
    return self;
}

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

- (void)drawRect:(NSRect)rect
{
    const int ITEM_WIDTH = 50;
    const int ITEM_HEIGHT = 50;
   
    NSRect itemRect;
    NSBezierPath *path;
        
    itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
    itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
   
    itemRect.size.width = ITEM_WIDTH;
    itemRect.size.height = ITEM_HEIGHT;

    switch (itemType) {
    case 1:
        path = [NSBezierPath bezierPathWithRect:itemRect];
        [path fill];
        break;
    case 2:
        path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
        [path fill];
        break;
    case 3:
        path = [NSBezierPath bezierPath];
        [path moveToPoint:itemRect.origin];
        [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
        [path setLineCapStyle: NSButtLineCapStyle];
        [path setLineWidth: 3];
        [path stroke];
        break;
    case 4:
        [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
        break;
    default:
        break;
    }
}

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}

@end

3) 오브젝트 생성과 해제

자동으로 생성된 initWithFrame 메소드는 이름에서 짐작하듯이 NSView가 처음 초기화 될 때 자동으로 호출됩니다. 주석 설명(// Add initialization code here)과 같이 초기화할 내용들을 아래와 같이 입력합니다.

itemType = 0;

초기에 아무것도 그려지지 않도록 0으로 넣습니다.
      
txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
출력할 텍스트 오브젝트를 생성하고 "Hello!!!"란 문구로 초기화 합니다.

txt_attributes = [[NSMutableDictionary alloc] init];
[txt_attributes setObject:[NSColor blueColor]
                            forKey:NSForegroundColorAttributeName];
텍스트 속성 오브젝트를 생성하고, 폰트칼라만 파란색([NSColor blueColor])으로 설정합니다.

- (void)dealloc {
    [txt_message release];
    [txt_attributes release];
    [super dealloc];
}
dealloc은 오브젝트 사용이 끝나고 메모리에서 해제될 때 호출됩니다. alloc으로 메모리를 할당한 오브젝트들(txt_message, txt_attributes)을, release로 메모리를 해제해 줍니다.

4) 그리기

drawRect는 NSView가 그려져야 할 때, 자동으로 호출됩니다. 그리는 작업을 이곳에서 하면 됩니다.

const int ITEM_WIDTH = 50;
const int ITEM_HEIGHT = 50;
그려질 도형들의 크기를 설정합니다.
  

NSRect itemRect;
NSBezierPath *path;
        
itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
itemRect.size.width = ITEM_WIDTH;
itemRect.size.height = ITEM_HEIGHT;
그려질 도형들의 위치와 크기를 설정합니다. 인자로 넘어온 rect는 그려져야 할 영역인데 NSView의 크기로 보시면 됩니다. NSView의 중앙에 오도록 계산하여 x, y를 설정 합니다.
 

switch (itemType) {
case 1:
    path = [NSBezierPath bezierPathWithRect:itemRect];
    [path fill];
    break;
case 2:
    path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
    [path fill];
    break;
case 3:
    path = [NSBezierPath bezierPath];
    [path moveToPoint:itemRect.origin];
    [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
    [path setLineCapStyle: NSButtLineCapStyle];
    [path setLineWidth: 3];
    [path stroke];
    break;
case 4:
    [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
    break;
default:
    break;
} 
itemType에 따라 NSBezierPath를 사용하여 사각형, 원, 직선과 문자를 출력합니다. 상세한 설명은  튜토리알 이 후에 다시 하겠습니다. 용도는 오브젝트, 변수, 메소드 명을 보시면 대충 짐작하실 수 있습니다.

다만 [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
를 보시면 x, y 20, 20으로 출력하는데 생각과는 달리 좌측 상단에 나오지 않고 좌측 하단에 출력 됩니다. 이는 기본 좌표 체계가 좌측 하단을 기준으로 시작하기 때문입니다.

5) 사용자 입력 처리

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}
버튼이 클릭될 때, 호출되는 메소드 입니다. 네개의 버튼이 연결되어 잇지만 [sender tag]로 현재 어느 버튼이 클릭되었는지 알 수 있습니다.

[self setNeedsDisplay:YES];
NSView가 다시 그려져야 됨을 알립니다. drawRect가 호출됩니다.

6) 마무리

이제 빌드 후 실행하고 테스트 해 봅니다. 오류가 나거나 정상적으로 동작하지 않으면 위의 내용을 확인해 보시기 바랍니다.

완전한 이해를 위해서는 Object-c의 문법과 메소드 기타 상세한 설명이 필요 하지만, 단순히 따라 해보는 튜토리알에 의미를 두었습니다. 추후 튜토리알과 다른 항목에서 설명하도록 할려고 합니다.

또한 결정적인 이유는 송구스럽게도 저도 대충 이해하고 구현에 중점을 두고 해보기 때문입니다.
 

AND

1.4.1  프로젝트 생성
이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleCalc  코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. roject Name에 SimpleCalc를 입력하고 finish 버튼을 클릭합니다.

1.4.2  AppController class 생성 및 변경
이전 장과 똑같은 방법으로 AppController.h와 AppController.m 파일을 생성합니다.

1) AppController.h 파일 변경

Xcode에서 생성된 AppController.h 파일을 편집창에서 열어 아래와 같이 파란색 라인을 추가합니다.
#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    IBOutlet NSTextField *txtResult;
    IBOutlet NSTextField *txtValue;
}

- (IBAction)plusClicked:(id)sender;
- (IBAction)minusClicked:(id)sender;
- (IBAction)muntiplyClicked:(id)sender;
- (IBAction)devideClicked:(id)sender;

@end

IBOutlet NSTextField *txtResult;
IBOutlet NSTextField *txtValue;
위와 같이 계산된 결과값을 표시해 줄 txtResult와 사용자가 입력하는 숫자를 입력 받을 txtValue를 선언 합니다.

- (IBAction)plusClicked:(id)sender;
- (IBAction)minusClicked:(id)sender;
- (IBAction)muntiplyClicked:(id)sender;
- (IBAction)devideClicked:(id)sender;
위와 같이 더하기/빼기/곱하기/나누기 연산을 처리 할 메소드를 선언합니다.

2)  AppController.m 파일 변경

다음은 AppController.m 파일을 편집창에서 열어 아래와 같이 파란색 라인을 추가합니다.
#import "AppController.h"


@implementation AppController
- (IBAction)plusClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];
   
    curResult += curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)minusClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];
   
    curResult -= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)muntiplyClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];

    if(curValue == 0.0f)
        return;

    curResult *= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

- (IBAction)devideClicked:(id)sender
{
    float curResult = [txtResult floatValue];
    float curValue = [txtValue floatValue];

    if(curValue == 0.0f)
        return;
   
    curResult /= curValue;
    [txtResult setFloatValue:curResult];
    [txtValue setStringValue:@""];
}

@end

나중에 인터페이스 빌더에서 만들어질 +, -, *, / 버튼이 클릭되었을 때, 행동을 지정합니다. 모두 같은 기능을 하니 plucClicked만 예를 들겠습니다.

float curResult = [txtResult floatValue];
결과 창의 현재 값을 가지고 옵니다. 소수점을 지원하기 위해 float으로 선언합니다.

float curValue = [txtValue floatValue];
현재 사용자가 입력한 값을 가지고 옵니다.

curResult += curValue;
사용자가 입력한 값을 현재 값에 더합니다. curResult = curResult + curValue; 과 같은 의미입니다. 더하기(+)와 마이너스(-)는 일반 사용하는 기호와 같지만 곱하기는 "*", 나누기는 "/"를 사용합니다.

[txtResult setFloatValue:curResult];
계산된 값을 텍스트 필드에 저장 합니다. 이 명령으로 사용자는 최종 계산된 값을 볼 수 있습니다.

[txtValue setStringValue:@""];
다음 입력을 위하여 현재 입력창의 내용을 지웁니다. 쌍따움표 앞에 @는 아스키 코드를 NSString 형태로 바꾸어 줍니다. @""로는 한글은 표현할 수 없습니다.
 
곱하기와 나누기에는 아래의 내용이 더 추가되어 있습니다.
if(curValue == 0.0f)
        return;
사용자가 미입력시나 0을 입력하였을 경우, 0으로 곱하거나 나누어지는 것을 방지합니다.

3) Nib에 인스턴스 생성

이제 소스파일에서 변경된 사항을 저장하고 Xcode의 AppController.h를 마우스로 드래그 하여 인터페이스 빌더의 윈도우로 가져다 놓습니다. classes 윈도우에서 AppController를 우클릭 하여, Instantiate AppController를 클릭하여 아래와 같이 인스턴스를 생성합니다.
사용자 삽입 이미지

1.4.3  인터페이스 빌더에서 윈도우 생성 및 연결

1) 컨트롤 추가

윈도우를 틀릭하여 아래와 같이 컨트롤들을 추가하고 배치 합니다.
사용자 삽입 이미지

텍스트 필드 두개와  버튼 네개 (버튼은 하나를 가져 온 후, 복사/붙여 놓기로 사용하셔도 됩니다.)를 가져다 놓습니다. 상하 구분선은 팔레트의 두번째 Controls 항목에 있으며, 아래의 화살표를 참조 하여 마우스로 드래그 해 오시면 됩니다.
사용자 삽입 이미지

텍스트 필드는 숫자 형식으로 보여지기 위해, 아래의 우측에 보이는 팔레트에서 1.99$로 표시되어 있는 숫자형식을 지정하는 콘트롤을 드래그 해서 윈도우의 텍스트 필드에 가져다 놓습니다. 그 하단의 텍스트 필드에도 위와 같이 숫자형식 콘트롤을 드래그 해 놓습니다.
사용자 삽입 이미지

윈도우의 텍스트 필드를 클릭하고 [command + shift + i]를 클릭하여,  인스펙터를  오픈합니다. 숫자를 표현하기 위해 좌측 Attributes 항목에서 Alignment를 세번째 우측 정렬로 선택합니다. 위의 숫자형식 콘트롤을 추가하였기 때문에 오른쪽 이미지와 같이 마지막 속성에 Formmatter란 숫자형식을 지정할 수 있는 속성이 추가되었습니다. 첫번째 항목을 선택 합니다. 위의 작업 역시 하단의 텍스트 필드에도 똑같이 해줍니다.

가장 위의 결과창(모서리가 동그란 입력창)은 보여주기만 하므로 Options에서 Editable 클릭을 해제합니다.
사용자 삽입 이미지 사용자 삽입 이미지

2) AppCrontroller와 연결

이제는 AppConroller의 속성과 메소드를 연결합니다. AppController를 controll키와 함께 드래그하여 상단의 텍스트 필드와 txtResult 아울렛에 연결합니다. 마찬가지로 하단 왼쪽의 텍스트 필드와 txtValue와 연결합니다.

사용자 삽입 이미지

각각의 버튼들 controll키와 함께 AppController로 드래그 하여 + -> plusClicked, - -> minusClicked, * -> multiflyClicked, / -> devideClicked 로 연결(Connect)합니다.
사용자 삽입 이미지

위의 인터페이스 빌더의 일련의 작업들이 이해가 안가시는 분은 이전 포스트를 참조해 주세요.

1.4.4 테스트 및 추가사항
이제 모든 작업이 완료되었습니다. Build And Go를 클릭합니다. 빌드가 완료되면 아래와 같이
윈도우가 나타납니다. 좌측 아래 사각 입력창에 숫자를 입력하고 버튼들을 차례로 누르면서 결과값을 확인해 봅니다.
사용자 삽입 이미지

정말로 간단한 계산기가 완료 되었습니다. 다음번엔 여기에 기능들을 추가하고 조금은 어플리케이션 다운모습으로 만들어 볼려고 합니다.

저도 공부하면서 올리는 중이니 용어 사용이나,  구현에 있어 적절치 못한 부분이 많이 있을 것으로 생각됩니다. 지적과 조언은 언제나 감사하게 받아 들이겠습니다.



AND

1.3.1 소스코드 생성
1) 소스코드 생성

사용자 삽입 이미지
툴바에서 Action버튼을 클릭하여 Add/New File... 을 클릭합니다. 키보드에서 [Command + N]을 누르거나 Xcode메뉴에서 File/New File을 클릭해도 같습니다.



다음 나타나는 메뉴에서 아래와 같이 Cocoa/Objective-C class를 선택 하고, 하단의 Next 버튼을 클릭합니다.
사용자 삽입 이미지

다음 메뉴에서 File Name을 아래와 같이 AppController로 입력하고, Finish 버튼을  클릭합니다. (Object-C에서 헤더는 *.h, 소스파일은 *.m이 확장자 입니다)
사용자 삽입 이미지

Xcode 좌측 메뉴를 보면 아래와 같이 AppController.h, AppController.m 두개의 파일이 생성되어 있습니다. 우측과 같이 두 파일을 드래그해서 Classes 그룹 밑으로 둡니다. 옮기지 않아도 상과 없으나, 성격에 맞는 그룹에 정리해 두는 것이 좋습니다.
사용자 삽입 이미지 사용자 삽입 이미지

2) 소스코드 수정

AppController.h 파일을 열고 아래와 같이 추가 합니다. 좌측 파일 목록 에서 해당 파일을 더블 클릭 하거나, 마우스로 클릭 후, 툴바의 Editor를 클릭 합니다.
/* AppController */

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject
{
    IBOutlet id myText;
}

- (IBAction)changeText:(id)sender;
@end

헤더파일을 보면 Java나 C/C++을 아는 분들은 익숙하면서도 뭔가 생소한 느낌이 들 것 입니다. 아래의 내용은 기존에 다른 개발 언어를 경험해 보신 분들은 참고 하시고, 아니면 그냥 건너  뛰시면 됩니다.
 
1. 주석
우선은 주석이 C, Java와 동일하게 "/*" 로 시갖해서 "*/"로 끝난다는 것을 알 수 있습니다.

2. 헤더파일 인클루드
#import로 필요한 헤더파일을 포함하는데, C와 같이 #를 전처리기로 사용하고 Java와 같이 include 대신에 import를 사용합니다.(C와 다른 점은 중복될 경우에는 오류없이 하나만 포함합니다.)

3. 클래스 선언
클랙스는 @interface로 시작해서 @end로 끝이 납니다. 맴버 변수(속성)만 { }에 포함되고 맴버 함수(메소드)는 {} 밖에서 선언되는 것 같습니다.

4. 접두어
cocoa에서는 내장 클래스들이 NS라는 접두어를 사용하고 있습니다.  NextSTEP의 NS에서 온 것 같다는 생각이 들지만 확실하지는 않습니다.

IB로 시작되는 것은 Interface Builder에서 연결된다는 의미인 것 같습니다. 둘 다 자동화를 위한 Xcoide의 예약어로 생각하시면 될 것 같습니다. (IBAction)은 리턴 값 타입이 오는 자리이지만 IBAction은 void와 같습니다.

아래 두개의 라인이 추가되었습니다.
IBOutlet id myText;
    윈도우의 Hellow World가 있는 텍스트 필드에 연결될 변수 입니다.

- (IBAction)changeText:(id)sender;
    변경 버튼이 클릭되었을 때 연결될 메소드 입니다.

이젠 AppController.m 파일을 오픈 합니다.
#import "AppController.h"

@implementation AppController
- (IBAction)changeText:(id)sender
{
    [myText setStringValue:@"Hello World!];
    NSLog(@"Text changed");
}
@end

AppControll.h에서 선언 해 놓은 ChangeMyText 메소드를 구현 합니다. 이 메소드는 변경 버튼이 클릭되었을 때, 실행됩니다.

[myText setStringValue:@"Hello World!];
버튼이 클릭되면 myText(Hello World 택스트 필드)가 "Hellow World!"로 문자를 변경하라는 의미입니다. Hello World! 다음에 "로 막아야 하지만, 오류 메시지를 보기 위해 입력하지 않았습니다.
사용자 삽입 이미지 사용자 삽입 이미지

NSLog(@"Text changed");
Log윈도우에 메세지를 출력해 주는 함수 입니다. 변수값 확인, 오류를 찾거나 수행여부를 확인할 때 사용할 수 있습니다.

이제 툴바의 Build 버튼([command + b])을 클릭 해서 컴파일을 합니다. 하단을 보면 컴파일 중이라는 메시지 두에 Build failed (2 errors)란 메시지를 볼 수 있습니다. 2개의 오류가 생겨서 컴파일을 완료하지 못했다는 뜻 입니다.
사용자 삽입 이미지

에디터를 보면 위와 같이 X 마트로 오류가 난 위치가 표시되어 있습니다. 상세한 오류 메시지를 보실려면 좌측의 Errors and Warnings를 클릭한 후 파일별로 내용을 확인할 수 있습니다.

또는 메뉴바에서 Build/Build results [shift + command + b]를 클릭하시면, 아래와 같이 컴파일 결과를 확인할 수 있습니다.
사용자 삽입 이미지
에디터에서 다음 오류 라인은 [command + =], 이전 오류 라인은 [command + shift + =]로 오류지점으로 이동할 수 있습니다.

[myText setStringValue:@"Hello World!"];
이제 위와 같이 Hellow World! 뒤에 "를 입력하고 Build and Go [command + r]
를 클릭 합니다. 실행이 되면 이전과 마찬가지로 아직은 어떠한 작동도 하지 않습니다. 이제 버튼 클릭에 동작하는 작업을 해보도록 하겠습니다.


1.3.2 인터페이스 빌더에서 추가 작업
다시 Xcode로 돌아 와 좌측 메뉴의 NIB Files/MainMenu.nib를 더블클릭 하여 인터페이스 빌더를 엽니다.

Xcode에서 AppController.h가 저장되어 있음을 확인합니다. 아래 좌측과 같이 m 아이콘이 어두우면 소스 변경 후 저장이 되지 않은 상태이며, 흰색이면 최종 변경 후 저장되어 있는 상태 입니다.
사용자 삽입 이미지 사용자 삽입 이미지

1) AppController.h 등록  과 인스턴스 생성

아래와 같이 Xcode의 좌측 메뉴에서 AppController.h를 드래그 하여 인터페이스 빌더의 Instances 윈도우로 가져다 놓습니다.
사용자 삽입 이미지

완료되면 아래와 같이 Classes 윈도우에 AppController가 등록되어 있는 것을 확인할 수 있습니다.
사용자 삽입 이미지

사용자 삽입 이미지
위에 보이는 AppController를 마우스 우측 버튼으로 클릭(control + 클릭)하시면, 좌측과 같은 메뉴가 나옵니다. 여기서  Instantiate  AppController를 클릭 합니다.


Instances 윈도우에 AppController 라는 인스턴스가 생성되었습니다.
사용자 삽입 이미지

2) 버튼 등록

이제 윈도우에 버튼을 추가 합니다. 아래 왼쪽 그림과 같이 팔레트의 두번째 버튼 항목에서  버튼을 클릭 후 드래그 해서 오른쪽 그림에서 보이는 윈도우의 적당한 위치에 가져다 놓습니다. 그 후 버튼을 더블클릭하여 Button이라는 문자를 "바꾸기"로 변경합니다.
사용자 삽입 이미지 사용자 삽입 이미지

3) changeText와 버튼 연결

버튼이 추가되면 이제 버튼 클릭시  AppController에서 만들어 놓은 changeText 메소드로 연결 합니다. control 키를 누른 상태에서 마우스 버튼을 바꾸기 버튼에 누른 상태에서 라인이 생기면 AppController의 인스턴스(파란  네모 상자)에다 놓습니다.

그러면 Inspector의 Connections의 Target/Action에서 아래 왼쪽 그림과 같이 changeText 항목이 나타나는데, 이를 선택하면 아래의 connect 버튼이 활성화 된다. connect 버튼을 클릭하면 changeText 앞에 아이콘이 나타나며 연결됩니다.
사용자 삽입 이미지 사용자 삽입 이미지
이제 버튼이 클릭되면 AppController의 changeText 메소드로 연결됩니다.

4) myText와 텍스트필드 연결

텍스트필드와 AppController의 myText를 연결합니다. 다시 control 키를 누른 상태에서 위와는 반대로 AppConroller(파란 네모 상자)를 마우스 버튼을 클릭하여 라인이 생기면 Hello World라고 되어 있는 텍스트 필드에 연결합니다.

연결이 되면 위와 같이 Inspector가 나오는데, Connections의 Outlets에서 왼쪽 그림과 같이 myText 항목이 있다. 위와 같이 myText를 선택하고 connect를  클릭하고, myText 앞에 아이콘이 있어 연결이 되었는지 확인 합니다.
사용자 삽입 이미지 사용자 삽입 이미지
이제 myText와 텍스트 필드가 연결되었습니다. Build And Go 버튼을 눌러 컴파일을 하고 잘 동작하는지 테스트를 해봅니다.

5) 실행 및 확인
컴파일이 오류없이 완료되면 Hello 윈도우가 실행됩니다. 바꾸기 버튼을 클릭하면 "!"가 추가된 텍스트로 오른쪽 아래와 같이 변경됩니다.
사용자 삽입 이미지 사용자 삽입 이미지

Xcode를 보면 버튼 클릭 시, 아래와 같이 NCLog에서 출력하는 "Text changed"란 메세지가 나옵니다.
사용자 삽입 이미지

 
AND

이제 처음으로 Xcode를 이용해서 간단하게 Hello를 표시하는 윈도우를 만들어 보겠습니다.

1.2.1  프로젝트 생성

사용자 삽입 이미지
Xcode를 실행 시키고 좌측과 같이 메뉴의 File을 클릭한 후 New Project를 선택합니다.

보이는 바와 같이 단축키 [Shift + Command + N]을 클릭하셔도 됩니다.

1) 프로젝트를 종류를 선택합니다.

사용자 삽입 이미지
위와 같이 Application 분류 에서 Cocoa Application을 선택하고 하단의 [Next] 버튼을 클릭합니다.

2) 프로젝트명과 폴더를 선택합니다.

사용자 삽입 이미지
프로젝트 이름을 Hello로 입력하고 프로젝트 폴더를 선택합니다. 참고로 ~/ 는 사용자의 루트폴더를 의미하며, 기본으로 이곳 하위에 프로젝트명으로 폴더를 생성하게 됩니다. project, devel, test, exam등의 폴더를 만드시고 이곳에 프로젝트들을 모아 놓는 것이 관리하기 쉬울 것입니다.

위와 같이 Project Name과 Project Directory를 입력 하신 후, [Finish] 버튼을 클릭하여 완료 합니다.

1.2.2 Xcode 둘러 보기
사용자 삽입 이미지
이젠 위와 같이 Xcode를 처음 만나 보게 됩니다. 복잡한 구성과 메뉴와 알듯 모를 듯한 영어들이 있지만 어려워 보이지만, 자주 보게 되면 친근하게 느껴 집니다. 좌측의 트리메뉴는 프로젝트에 포함된 소스파일들과 오브젝트, 리소스등에 쉽게 접근하게 해주는 메뉴이며, 우측은 좌측에서 선택된 내용들을 보여주는 창입니다.

툴바를 잠깐 살펴 보면 (이해할 수 없는 용어는 그냥 넘어 가면 됩니다.)
사용자 삽입 이미지
첫 윈도우 모양 아이콘은 프로젝트 기본 창입니다. 두번째 도구 모양 아이콘은 빌드 결과와 실행시 로그를 확인 할 수 있는 창입니다. 세번째 에프킬라 아이콘은 디버깅을 위한 창입니다.

사용자 삽입 이미지
Build 명령으로 컴파일되어 만들어질  목표를 설정합니다.



사용자 삽입 이미지
Release 또는 Debug로 Build될지 선택합니다. Debug 선택 시에는 디버깅  정보를 포함하고 있기 때문에  Release 보다 실행파일이 크고 느립니다. 개발시 편의를 위해 Debug 모드로 제작을 하고 최종 배포시 Release 모드로 Build해서 배포 하시면 됩니다.

사용자 삽입 이미지
자주 사용하는 명령어들과 설정들을 바로 실행할 수 있습니다.



사용자 삽입 이미지
Build는 실행파일을 만들기 위해 컴파일을 합니다.
Build And Go 는 Build 후에 실행을 합니다.
Tasks는 Build 또는 실행을 중지 합니다.

사용자 삽입 이미지
Info는 현재 선택된 목록에 대한 정보를 보여 줍니다. ([Command + i])
Editor는 좌측 메뉴에서 선택된 소스파일들을 편집할 수 있도록 에디터를 같은 창에서 실행 합니다. Editor 버튼 대신 소스를 더블클릭하면 새창에서 열립니다.



1.2.3 인터페이스 빌더 둘러 보기
사용자 삽입 이미지
위와 같이 Xcode의 좌측 메뉴에서 NIB Files를 열고, MainMenu.nib를 더블클릭 하여  인터페이스 빌더(Interface Builder)를 엽니다. 인터페이스 빌더가 열리면 몇 개의 윈도우가 같이 열립니다. 간단하게 살펴보면,

사용자 삽입 이미지
좌측이 인터페이스 빌더 메인윈도우 이며, 오브젝트 선택, 메뉴, 콘트롤, 서브클래스/인스턴스 생성, 아울렛/엑션 바인딩, 속성 변경 등의 다양한 작업을 할 수 있습니다. (지금은 무시하세요)







사용자 삽입 이미지
좌측이 어플리케이션 기본윈도우이며, 실행 시 전면에 나오는 윈도우 입니다.











사용자 삽입 이미지
좌측은 팔레트라 불리우며, 각종 콘트롤과 오브젝트들을 윈도우에 추가할 수 있습니다.








사용자 삽입 이미지
좌측은 메인 메뉴로 어플리케이션의 메뉴와 관련된 작업을 하는 윈도우 입니다. 이번에는 사용하지 않습니다.

1.2.4 Nib 파일 변경

1) 윈도우에 텍스트 필드 삽입

 아래의 좌측 이미지와 같이 팔레트의 세번째 항목 Text를 선택하고 System Font Text를 클릭한 후, 윈도우로 드래그해서 놓습니다. 결과는 우측과 같습니다.
사용자 삽입 이미지 사용자 삽입 이미지

사용자 삽입 이미지
 드래그 한 System Font Text를 더블클릭 하면, 입력할 수 있는 창이 나타납니다.

이곳에 Hello World (또는 다른 텍스트)를 입력합니다.




윈도우에 Hello World를 출력하는 것은 여기가 끝이지만, 간단하게 텍스트 필드를 꾸며보고 인스펙터를 구경하고 마치도록 하겠습니다.

2) 텍스트 필드 꾸미기

사용자 삽입 이미지
윈도우에서 Hellow World를 클릭하고 [Shift + Command + i]를 클릭하거나, 인터페이스 빌더 메뉴에서 Tools/Show Inspector를 클릭하여 인스펙터를 엽니다.

인스펙터는 각 오브젝트들의 속성과 행동을 설정 할 수 있는 툴로, 여기서는 버튼의 모양과 정열을 바꾸어 보겠습니다.Alignment 항목에서 두번째 가운데 정열을 선택하고, Text Border 항목에서 세번째를 선택하고, Options에서 Rounded를 선택합니다.













사용자 삽입 이미지
이번엔 윈도우에서 Hello Word 텍스트필드 이외의 부분을 클릭하고, 인스펙트 상단에 NSWindow Inspector이 나오게 합니다. Window Title을 MyFirst Cocoa App. 또는 원하는 문자로 변경합니다.



















아래가 완료된 윈도우 입니다. Xcode에서 Build and Go 버튼(또는 [command+R])을 클릭하면 결과를 확인할 수 있습니다.
사용자 삽입 이미지

실행파일은 Hello.app로 생성되며 [프로젝트 폴더]/build/Debug/에 위치합니다. 이곳에서 더블클릭 하여도 같은 윈도우를 실행할 수 있습니다. 만약 Xcode 툴바에서 Active Build Configuration이 Release로 지정되어 있을 때는 [프로젝트 폴더]/build/Release/에 위치합니다.

'Xcode 2 > Cocoa 따라해보기' 카테고리의 다른 글

1.6 SimpleViewer 이미지 뷰어 (1)  (2) 2007.05.16
1.5 NSView를 이용한 그리기  (7) 2007.05.11
1.4 간단한 계산기 프로그램  (14) 2007.05.07
1.3 Hello 프로그램 기능 추가  (3) 2007.05.04
1.1 Xcode 구하기  (0) 2007.05.04
AND

1.1.1 Xcode 다운로드
Xcode는 맥 구입시 설치 CD에 포함되어 있으며, 애플 홈페이지에서도 구할 수 있습니다. 여기서는 최신 버젼 설치를 위해서 애플 홈페이지에서 다운로드 받는 방법을 설명합니다.

2007년 5월 현재 한국애플 홈페이지에는 2.1 버젼이 영문 홈페이지에는 2.4.1이 링크되어 있으므로, 영문 홈페이지에서 다운로드 받으실 것을 권장합니다. xCode를 다운 받기 위해서는 ADC에 가입 해야 하는데, 다양한 지원을 받는 유료 회원과 무료회원이 있습니다.

사용자 삽입 이미지
이제 다운을 받기 위해서 ADC(Apple Developer Connection) 사이트로 갑니다. 이 곳에는ADC 사이트에는 osx 개발과 관련되 수 많은 기술 자료들과 샘플 소스들이 있습니다. 이 곳을 적극적으로 활용하시면 좋습니다.

좌측의 Development Resources 항목 중 Software Downloads의 가장 하단의 Xcode Developement Tools를 클릭 합니다.큭릭 후 우측 상단의 Tools Donwloads를 클릭합니다. 다음 페이지에서 Xcode 2.4.1을 클릭하면 로그인 화면으로 넘어 가는데, 기존에 계정이 있으면 로그인을 하고 없으면 하단의 sign up 링크를 클릭하고 ADC 회원으로 가입 후에 다운로드 하시면 됩니다.


1.1.2 기본 설정
다운로드가 완료 되면 dmg 파일을 더블클릭하여 마운트 하고, XcodeTools.mpkg를 실행시켜 Xcode와 관련 툴을 설치 합니다. 이제 Xcode를 사용하여 osx에서 cocoa로 개발할 수 있는 기본적인 환경이 완료 되었습니다.

사용자 삽입 이미지
Xcode를 실행(Macintosh HD/Developer/Applications 에 있습니다) 후에 좌측과 같이 메뉴의 Xcode  -> Preferences에서 각자의 편의에 맞게 설정하시면 됩니다. 제가 사용하는 설정은 아래와 같습니다.



1) General 항목의 Layout에서 All-In-One을 선택합니다.

사용자 삽입 이미지
Layout에는 Default, Condenced, All-In-One가 있습니다. Condenced는 Project에 관련된 기본적인 사항만 보이고 디버거, 로그 등의 다른 툴들이 개별 윈도우로 나타나며, All-In-One은 대부분의 툴들이 한 윈도우에서 나타납니다.  좌측은 All-In-One 선택 시, 우측은 Condenced 선택 시 나타나는 화면입니다.

사용자 삽입 이미지 사용자 삽입 이미지


2) Code Sense 항목의 Code Completion 에서 Show arguments in pop-up list를 체크 합니다. Automatically Suggest를 Always로 선택 합니다.

사용자 삽입 이미지
미리 정의된 함수의 인자들을 보여 주거나, 함수 목록 제시, 함수명 자동완성 등을 지원해 주는 설정입니다. 함수에서 ESC를 누르시면 임의로 확인 하실 수 있씁니다.

3) Text Editing 항목의 Display Option 에서 Show gutter, Show line numbers 항목을 체크 합니다.

사용자 삽입 이미지
편집기 좌측에 라인넘버, 오류, 브레이크 포인트 등을 보여줍니다.


4) Indentation 항목에서 Automatically insert closing "}"을 선택합니다.

사용자 삽입 이미지
입력 시 {를 치면 자동으로 }로 막아 줍니다.

이외에 저도 자세히 모르는 수많은 옵션들이 있습니다. 하나씩 확인하면서 본인의 개발습관에 맞추어 설정해 나가며 개발 속도와 편의성을 향상할 수 있을 것입니다.
AND