iPad가 Release되면서 새로추가된 Class로 사용자 TouchGesture를 인식하는 Class다.
오늘 ( 2010.11.17 )을 기준으로는 iPad(iOS 3.2) 에서 밖에 사용할 수 없는데,
iOS 4.2 에도 포함되었기에, 곧 iPhone 용 Project에서도 사용가능하다.


Detect 할 수 있는, Gesture의 종류는 위와 같다. 
CustomGestureRecognizer도 구현이 가능한데, 그것은 필요 할때 되서 스터디 할 생각이다.

일단은 UIPinchGestureRecognizer를 해볼생각인다.

UIGestureRecognizer의 Event 처리방식은 Delegate Pattern이 아닌, Target Action을 따른다.
Gesture라는게 여러 상황이 발쌩할 수도 있는데, Target Action Pattern을 이용한다는 뜻은, 
결국 한종류의 Event만을 사용하게는 뜻으로 보인다.

여러 종류의 Event를 처리 하고 싶으면, 여러 종류의 UIGestureRecognizer를 붙여야 할테다. 

UITouch 와 달리 Gesture는 UIResponder 가 아니라, UIView에서만 동작한다.

UIKIT_CLASS_AVAILABLE(2_0) @interface UIView : UIResponder<NSCoding> {

  @package

    CALayer        *_layer;

    id              _tapInfo;

    id              _gestureInfo;

    NSMutableArray *_gestureRecognizers;


Class 선언에서 보는것과 같이 View에 UIRecognizer를 add 시키면, 내부적으로 Array로 관리한다.
UIView에 UIGestureRecognizer 를 add 하고 remove하는 method들은 아래와 같다.


@interface UIView (UIViewGestureRecognizers)


@property(nonatomic,copy) NSArray *gestureRecognizers __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);


- (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);

- (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);


@end


UIGestureRecognizer Object를 생성하고,  특정 View에 그 Object를 Add 해주면 자동적으로
Target-Action Mechanism 으로 특정 Selector가 불러진다.
아래는 UIPinchGestureRecognizer 를 생성하고 추가 하는 예제 소스 코드이다.


- (void)viewDidLoad {

    [super viewDidLoad];

UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc]

initWithTarget:self action:@selector(handlePinchGesture:)];

    [self.view addGestureRecognizer:pinchGesture];

    [pinchGesture release];

}


이 것만 해주면 Pinch Gesture가 해당 View에서 일어나면, @selector(handlePinchGesture:) 가 invoke 된다.
아래 소스는 selector의 구현 예제다.


- (void)handlePinchGesture:(UIGestureRecognizer *)sender {

UIPinchGestureRecognizer *pinchGesture = (UIPinchGestureRecognizer *) sender;

NSLog(@"scale = %f", [pinchGesture scale]);

NSLog(@"velocity = %f", [pinchGesture velocity]);

if([pinchGesture velocity] > 0.0f) {

label.text = @"Outward Pinch";

}else {

label.text = @"Inward Pinch";

}

}


argument 로 UIGestureRecognizer 의 object가 던져 지는데,
UIPinchGestureRecognizer의 경우 scale ( 절대 값) 과 velocity ( 상대값 ) 이 들어 온다.
velocity가 + 값이면 확대 Gesture , - 값이면 축소하는 Gesture라고 보변 된다.

UIGestureRecognizer가 없을때는 UIResponder의 Touches값을 이용해서 이래저래 번거로이 구현했는데,
UIGestureRecognizer를 이용하면 쉽고, 빠르게 구현할 수 있다.

덤으로, "시작하세요! 아이폰 프로그래밍" 의 PinchMe 예제를 UIGestureRecognizer를 이용해서 재구현한 프로젝트를 
첨부하니, 참고용으로 사용하시기 바랍니다.

주의) 최신버전의 iOS SDK(3.2.4)에서는 동작하지 않고, 현재에는 Xcode 3.2.5 GM을 이용해서 해야 합니다.


iOS 에서 사용될 라이브러리를 만들때, 

Simulator 와 Device Arch Type이 달라서, 2가지 library 파일을 생성해야한다.

최근에 어떤 프로젝트 소스 코드를 본적이 있는데,

libxxxx-iphoneos.a libxxx-iphonesimulator.a 를 생성해두고,

Other Linker Flag 에서 -lxxx-$(EFFECTIVE_PLATFORM_NAME) 이런식으로
Platform 에 dependency하게 명명해두고, 사용하는것을 봤다.

예전에, universal 한 static library 를 본적이 있는것 같아서, 언제나 처러 search 고고싱 ~!

lipo 라는 cli tool로 이런 multi arch type library를 merge 할 수 있는데, 

커맨드는 아래와 같다. 

$ lipo -create -output myLib.a myLib-arm6.a myLib-arm7.a myLib-i386.a


정상적으로 생성되었는지 확인하려면 'file'이라는 cli command로 확인가능하다.

$ file myLib.a myLib.a: Mach-O universal binary with 3 architectures

myLib.a (for architecture armv6): current ar archive random library 

myLib.a (for architecture armv7): current ar archive random library 

myLib.a (for architecture i386): current ar archive random library


자 이제 쓸데 없이 거추장 스러웠던 환경변수는 집어 던지고

하나의 library를 그냥 링크하면된다 



처음 가본 속초는 참 아름다운 곳이더이다.

taehoon.koo 2010.11

최근 아이폰 프로젝트를 아이폰/아이패드 Project로 Migration하는것을 스터디 하고 있는데,

제대로 마이그레이션 할려면, 웹개발자 들이 브라우저 별로 처리를 하듯이,
현재 Device체크를 해서 따로 둬야겠지만, 좀 더 고해상도로만 보여지더라도, iPad용으로 Release를 하는것은 꽤나 간단하게 할 수 있습니다.

첫번째로, Target설정을 Universal 로 바꿔줘야합니다.

Build Configuration 에서 Target Device Family 를 iPhone/iPad로 변경합니다.


그러면 iPad에서 실행 시켰을대 Application이 아이폰 App을 실행시켰을때와 달리, 큰 해상도로 나타나게 됩니다.
그리고 iPhone으로만 설정되어 있을때 와 달리, Executable에 iPad Simulator가 추가 되어서 노출됩니다.


이것으로 끝나게 되면 정말 좋을텐데, 문제가 하나 터집니다.
이걸 알아 내는라 몇시간 삽질했습니다. ;ㅂ; 

예를 들어서 NavigationBar가 존재 하는 어플리 케이션의 경우 우측 버튼이 동작하지 않는 경우가 발생합니다.

우측 상단에 있는 저 버튼이 아무리 해도 터치가 되지 않는데, 사실 저부분뿐 아니라 일정 영역이상에서는 모두 터치가 안됩니다.
그 이유는 UIWindow의 사이즈 에 있습니다.

iPhone으로 개발 된 프로젝트는 UIWindow사이즈가 iPhone에 맞춰서 설정 되어 있습니다.
특히 MainWindow.xib를 그대로 사용한 경우에도 마찬가지인데요.

그럼 MainWindow.xib를 따로 설정해주어야 하는건가 라는 생각을 할 수 있지만,
그냥 Window사이즈만 적절히 설정해주는것만으로도 완벽하게 동작합니다.

    CGRect  rect = [[UIScreen mainScreen] bounds];

    [window setFrame:rect];


해당 소스를 ApplicationDidFinishLaunching쪽에다가 넣우주시면

UIScreen에 맞게 윈도우 사이즈를 조절하게 됩니다.

사실 제대로 Universal을 만들려면, xib파일 분리가 불가피 하겠지만,
간단히 작업해서 우선 Release만 하는 선택을 할때 요긴하지 않을까 생각합니다.


XCode Project 용 CI 구축작업을 하고 있는데,
구축을 완료 하고, Tomcat을 자동 실행 시키려고 하니 이것 또한 참 귀찮다 ㅎㅎ

Mac OS X 에 Launch Daemon 에 등록시켜 주면 되는데,  방법은 아래의 plist파일을 
/Library/LaunchDaemons/ 에 작성하는것이다. (예 : /Library/LaunchDaemons/com.tomcat.run.plist )

<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"

        "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

        <key>Label</key>

        <string>com.tomcat-5.5.launched</string>

        <key>ProgramArguments</key>

        <array>

                <string>/Users/taehoonkoo/WorkSpace/severs/apache-tomcat-5.5.31/bin/catalina.sh</string>

   <string>run</string>

        </array>

        <key>RunAtLoad</key>

        <true/>

</dict>

</plist>


나 같은 경우에는 tomcat 5.5 version을 써서 catalina.sh 를 실행시켜줬지만,
apache wiki에 따르면 tomcat 6.x version에서는 startup.sh를 실행시켜야한다.

여기서 hudson webapp을 붙혀서 실행시키는데 여기서 또 말썽이다.
최초 한번은 잘되던데 2번째에는

"AWT is not properly configured on this server. Perhaps you need to run your container with "-Djava.awt.headless=true"?"


라는 에러를 내뱉으며 hudson이 동작하지 않는다. -_ㅜ.

이때는 catalina property를 수정해줘야한다.
 TOMCAT_HOME/conf/catalina.properties 파일을 열어서 마지막 즈음에 #String Cache Configuration 을 지정하는 쪽에다가

java.awt.headless=true


를 추가 해두고 재시작하면 정상 동작하는 것을 볼 수 있다.

주의해야 할점은 root 계정으로 실행시키기 때문에 .huson 폴더를 root의 home ( /var/root ) 에 생성 시킴으로, 
따로 관리를 하려면, root 계정을 활성화해서 관리 해야 한다.



UIImagePickerController 를 사용해서, Image를 가져와야 하는 일이 생겼습니다.

iPhone내의 Photo Library 혹은 Camera를 통해서 Image를 가져와야 하는데, 문제는 iPod Touch등에선 Camera가 없다는 점입니다.

시뮬레이터-No Camera-에서 UIImagePickerController의 SourceType을 UIImagePickerControllerSourceCamera로 세팅하게 되면, App이 멈추는것을 미루어봤을때, 실제 Device에서도 마찬가지 일것입니다.

최초에 든 생각은 UIDevice Class내의 model property를 이용해서, Camera가 있는 Device와 없는 Device를 구분하는것이었습니다.

이 경우에는 또다른 종류의 Device ( iMat ?? :) ) 가 Apple사로 부터 발매 된다면 추가적인 작업을 해줘야 한다는게 걸립니다.

그래서 좀 찾아 보았는데, 역시나 있더군요 ㅎ

UIImavePickerController의 Class Methoad중 isSourceTypeAvailable이라는 Method가 있습니다.




위와 같이 코딩을 하면, App이 동작하는 Platform에서 Camera가 Support되는지 쉽게 알수가 있습니다.
간단한 일 하는 녀석치고, 코드길이가 길어져서 보기 불편한 단점이 있긴 하지만요. ㅎ

1. Target 생성

Application Test는 Logic Test와는 달리 실제로 실행을 하기때문에, Executable Target을 필요로 합니다.
아래와 같이 기존의 Executable Target을 Duplicate하여 원래 프로그램의 설정그대로 실행할수 있는 Target을 생성합니다.


이때, Target의 이름에 규칙이 있습니다. 그것은 '<application_name>' Testing 입니다.
prefix는 기호에 따라서 아무렇게 써도 상관이 없지만, suffix는 Testing이 되어야합니다.

2. iPhone Unit-test Bundle 생성

Logic Test와 마찬가지로, Unit-Test Bundle을 생성해주어야합니다.
단, Executable Target과 연동하는 부분이 Logic Test를 위한 bundle을 생성하는것과는 차이가 있습니다.

지난 포스팅을 참조하여, Unit-Test Bundle을 생성한후, 아래와 같이  <blah>Testing Target에 dependancy를 설정합니다.


그리고, MyAppTests를 build하면 나오는 MyAppTest.ocutest Binary를 <blah>Testing Executable Target의 Copy Bundle Resource로 등록해줍니다.

자 이제 Target & Bundle 생성 및 Dependay 설정이 완료 되었습니다.

3. TestCase 작성

LogicTest와 마찬가지로, Objective-C TestCase Class 파일을 생성합니다.
이때 Target Owrnership은 Executable Target이 아닌 Test Bundle이어야 합니다.


4. 실행

Application Test는 Device에서만 동작합니다.
Build Target을 Device 3.0 or Later로 설정을 한후 실행을 하면 Debugger Console 창에서 결과를 확인할수 있습니다.

기본적으로 AppDelegate 를 가져와서, AppDelegate Object가 nil 인지 아닌지 체크를 하는 테스트가 들어 있습니다.

실행 결과는 다음과 같습니다.


만약 Console창에 위와 같은 메세지가 나오지 않는다면, Target Dependancy 부분을 다시 한번 확인해보시기 바랍니다.


요새 개발하고 있는 Application이 SQLite를 사용하고 있습니다.
SQLite Interface Module을 OCUnit Test를 이용하여, TDD로 개발하였고, 1차 완료 하였습니다.
아직 익숙하지 않은 탓에 오히려 시간이 많이 걸리는것 같다는 느낌을 받고 있지만, 확실히
TestCase가 미리 작성된 탓에, API사용자 입장을 고려하여, Module을 Design 할 수 있어,
개발 초기 부터 단단한 프로그래밍이 가능해졌습니다.

다음에 Writing TestCase라는 주제를 끝으로 OCUnit 을 통한 Unit Test에 대한 글을 마치려고 합니다.




NSDate를 SQLite DB에 저장을 해야 할일이 생겼습니다.

어떻게 할까 고민을 좀 해봤습니다.

NSDateFormatter를 이용해서, String으로 변환시킨후 저장을 하고, 다시 Formatter를 이용해서 NSDate로 Parsing하는 방법이 있는데,
Raw Data가 아닌 변환된값을쓴다는점과, Parsing에 들아가는 Cost등을 고려할때 좋은 방법이 아닐듯 싶습니다.

그래서 생각을 해보았는데, NSTimeInterval을 이용하기로 하였습니다.

NSDate에는 NSTimerInterval을 구해주는 여러 메소드가 있습니다.



timeIntervalSince1970을 이용하면, 1970년 1월 1일 부터의 duration을 second 단위로 얻을수 있습니다.

이걸 SQLite에 저장해두고, 다시 불러다가 쓰면 깔끔하게 구현이 될것 같습니다.

NSTimeInterval은 실제론 double 을 typedef 해서 정의된 자료형이기때문에, SQLITE의 column type을 'REAL'로 정의해서 저장하도록합니다.

DB Schema는 정의되어 있다고 가정하고, 아래는 NSTimeInterval을 DB에 삽입하는 코드 입니다.



그럼 DB에서 가져오는 코드를 보도록 하겠습니다.



첨에는 좀 까다롭지 않을까 싶었는데, 뭐 딱히 어려운게 없습니다 ㅎ
그냥 스윽~하면 스윽~되는거죠 ㅎㅎ

필요하시면 아래 예제 소스를 참조 하시고, 좋은 어플들 많이 개발하세요 ^^


이글은 iPhone Development Guide from Apple 을 보고 작성되었습니다.
OCUnit에 대한 글은 3부작(Logic Test, Application Test, Test Cases)로 나누어 진행됩니다.
이글에 나오는 Screen shot은 XCode 3.2.2 에서 Capture되었습니다.

저는 프로젝트를 진행할때, Unit 테스트용 코드를 따로 작성하는 편인데,

이제까지는, Build Configuration을 추가하는 형식으로 모듈 테스트 코드를 작성하곤 합니다.


이런식으로 테스트할 Module을 나누어서 했는데, 꽤나 원시적인 방식이라, 소스코드가 지저분해지기도 하고,
뭔가 깔끔하지 못하다는 생각이 많이 들어서,
Target을 나누어, 필요한 Class만 넣어서 Unit Test를 진행하는게 깔끔할것이라는 생각이 문득 들었습니다,

그러다 문득, 아이폰 OS 3.0 이 나왔을때 OCUnit이라는 녀석이 공식적으로 추가 되었다는것을 상기했습니다..

새로 진행하게 된 Project도 있고 하니, 어디 한번 시도 해보는거 나쁘지 않겠죠? ^^

Xcode는 2가지 Type의 Unit Test를 지원합니다.

Logic Test. App상이 아니라 Build Phase에서 Test진행, 말그래도 소프트웨어 Logic ( or algorithm )을 테스트하기 위한 Unit Test

Application Test. App상에 설치되어 Test가 진행됨. iPhone에 있는 Reousrce들을 이용한 모듈에 대한 Test가 가능함.


프로젝트에 OCUnit을 추가하려면, 'SenTestingKit' Framework을 추가 해주어야 합니다.
이 Framework은 /Developer/Library/Framework에 있다. ( Xcode가 Default Path로 설치된 경우 )

곧 알겠지만, Test별로 unit-test-bundle target을 추가 하게 되는데, SenTestingKit Framework의 owrnership을 이들 target으로 변경해주어야 합니다.


그럼  Logic Test를 수행하기 위한 절차를 살펴 보면,

1. 타겟 생성 ( iPhone OS unit-test-bundle type )
2. 생성된 타겟을 active Target으로,
3. TestCase Class 추가.
5. TestCase 작성
6. Build ( = Test ) 

의 순서로 진행 됩니다.

1. 타겟 생성


Target Group에서 우클릭(or cmd + click)한 후 Add->New Target을 선택합니다.


그리고 Cocoa Touch(iPhone OS) -> Unit Test Bundle을 선택한후, 적당한 이름으로 지정하여 생성합니다.

2. 새로 생성한 Target을 Active Target으로 설정합니다.


3. TestCase Class를 추가합니다.



iPhone OS -> Cocoa Touch Class -> Object-C test case class 를 선택하여, File을 생성합니다.



여기서 주의 할점이, Target Owrnership이 Execute Targe이 아니라, 새로 추가한, Test-bundle target이어야 한다는 점입니다

4. TestCase 작성

자동으로 생성되는 코드를 살펴보면, App. Test,Logic Test에 관한 코드가 둘다 담겨져 있는데,
기본적으로 App Test용으로 Compile되게 되어 있으니, Logic Test용으로 바꿔주어야 합니다.

.h 파일에서
#define USE_APPLICATION_UNIT_TEST 1

으로 되어 있는 부분을 찾아서 1 -> 0 으로 바꿔 주면 Logic Test용 코드가 동작하게 됩니다.

.m 파일에서 testMath()라는 Method가 자동적으로 생성되어 있는데, 1+1 == 2 ? 라는 테스트가 되게 되어 있습니다.

일단 실패 하는 결과를 보는게 우선임으로, 1+1 == 2 를 1+1 == 3 으로 바꿔 봅시다.

5. Build (Test)

Active Target/ Target Owrner ship 등이 제대로 설정되어 있다면, Build(= Test)를 수행하면 아래와 같이 Error가 나타날것입니다.



혹시 Build Success 가 된다면, Active SDK 가 iPhone Simulator 3.0 or later 인지 확인하자. Logic Test는 Simulator일때만 동작합니다.


보신바와 같이, 그리 어렵지 않게 Logic Test를 추가 할수 있고, App 을 실행하지 않아도 되니, 빠르게 결과를 확인할 수 있습니다.

분명 Unit Test를 통한 Test자동화는 개발 프로세스에 효율을 가는데, 가끔 이런 도구에 너무 집착하여 개발 효율을 오히려 저해하는 경우도 꽤나 많습니다.

언제나 그렇듯 도구는 수단이 되어야지 목표가 되어서는 안된다고 생각합니다. ^^



회사를 다니지 않다 보니, 삽질을 한게 얼마만인가 싶네요 ㅎ

얼마전에 아이폰 알바를 했는데, 게임을 하고, 서버에 점수를 등록해서, 1,2,3등에게 경품을 주는 광고용 어플을 개발했습니다.

여기서 문제가 마이크로 사이트를 작업한 곳에서, 한글 Encoding이 잘안된다고 얘길했었는데,

그런곳도 못해 하면서, 우리가 보겠다고 했다.
 
근데 이게 왠걸, iPhone쪽 소스에 문제가 있었나보다.

AppStore의 바이너리를 갈아치우는게 가장 좋은 방법이겠지만, 그럴만한 시간이 없었다.

완전 대박 사고! -_ㅜ .

문제는 Cocoa FrameWork의 NSString Class String 'UTF8String'이라는 녀석을 사용해서 Encoding 했는데, 그게 제대로 안되는 모양이다.

사실 다른 Encoding Method를 사용하려 했는데, TestCode가 그대로 남아 있었던 모양이다 ( 테스트시에도 제대로 안됐음 ㅜ_ㅜ )

그래서 제대로된 UTF8이 나오지 않았기에, Javascript로 UTF8Decode를 아무리 해도 제대로 안나온다 -_ㅜ.

그래서 든 첫번쨰 생각은,

UTF8String Method를 분석해서, Javasciprt로 구현하는것이다.

다행히 GNUStep에 Core Foundation, Foundation Framework 소스가 있기에, 보았는데 완전 대박.
이거 옮기는 시간이 Binary를 새로 올리는것 보다 더 오래 걸릴 기세였다.

이것도, 옵션이 안되고, 그래서 Proxy를 둬서 새로 Encoding을 해서 넘겨 주는것으로 하였다.

OSX에 Web Server를 올리고 ( 정말 감사하게도, apache2가 기본 설치 되어 있다 -_ㅜ )

bash shell 을 cgi 로 사용해서, objc 프로그램을 실행시켜, Core Foundation을 통해서 잘못된 Decode를 풀고,
새로 Encode 하게 하는 script를 짜고,

ASP 상의 XMLHTTPRequest를 통해서, syncronous method로 변환하게 했다.

일단 동작은 하는데 겁나 찜찜하다 ㅜ_ㅜ.

우선 Review Path하면 Patch 해서 App Store에 다시 등록해야 할듯 -_ㅜ

아.. 오래간만에 shell script도 짜고, 간만의 삽질 ㅎㅎ

+ Recent posts