Had a Interview with The World

본 글은 2015년 8월 기준으로 작성되었습니다.

외국

구직을 시도해본 국가는 다음과 같습니다.

  • 독일 : CS학위 및 연봉만 일정 수준이 되면 EU블루카드가 나옵니다. 베를린이 스타트업으로 엄청나게 핫합니다. 요즘 외국인 개발자 유입이 많아서 비자업무에 대한 정보도 찾기가 쉽습니다. 면접 및 업무 언어는 영어입니다.
  • 중국 : 상해, 북경, 심천 등에 스타트업, 중견기업, 대기업 엄청 많습니다. 외국계 기업은 중국어 능력은 딱히 보지 않는 것 같습니다. 더군다나 중국 기업이라고 하더라도 내부적으로 영어를 공용어로 쓰는 경우도 많다고 합니다. 취업 비자 업무가 상당히 까다롭습니다.
  • 캐나다 : 좋다고 하는데, 이쪽은 아예 접수후 연락 조차 없어 딱히 아는 바가 없습니다.
  • 미국 : 비자 없으면 이력서 읽는 단계까지 가지도 못하는 경우가 꽤 많습니다. 이쪽도 접수 후 연락 조차 없어 딱히 아는 바가 없습니다.
  • 프랑스, 영국, 싱가포르 : 스타트업은 많은데, 이쪽도 접수 후 연락 조차 없습니다.

  • 영어 : 유창한 수준은 아니어도 되는 것 같지만, 최소한 자신이 개발하는 방식에 대한 설명은 가능한 수준이어야 할 것 같습니다. gitflow 라던가 tdd, agile 에 대해 자기 경험을 설명할 필요가 있으니까요. 다만 영어실력을 과신 하지는 마시기 바랍니다. 외국어를 머리 속에서 조합하는 과정의 특성상 말을 지어내기가 거의 불가능합니다.
  • 연봉 : 원하는 연봉을 말해달라고 할 때, 글래스도어의 평균값을 감안 하여 제시하시거나, 한국에서 받는 연봉을 그대로 달러화해서 불러도 좋습니다. 연봉은 같은 국가에서도 도시별로, 직급별로 차이가 많이 나는 편입니다.
    취직을 통해 얻는 이득을 이유로 연봉을 상대적으로 적게 제시하는 경우는 거의 없으니 걱정마시기 바랍니다. 일례로 독일에서 CS 학위소지자가 EU 블루카드를 얻기 위한 최소 연봉 기준액 (유로로 3만 얼마 정도)이 있습니다. 당신은 외국인이고 블루카드 신청을 도와줄테니 우리 회사는 해당 기준액만 줄 것 이다. 뭐 이런 식으로 말하는 경우는 제 경험상 없었다는 말입니다.
  • 이사 : 왜 굳이 외국으로 나오려고 하는가? 하는 질문은 반드시 나옵니다. 한번 잘 생각해보시기 바랍니다.

일반적 진행 방식

  1. 서류접수 : 링크드인이나 메일.
  2. 인사담당자 연락 및 인터뷰 : 연락 후 인사담당자와 인터뷰를 할 수도 안 할 수도 있습니다.
  3. 사전 기술 검증 테스트 : 극히 드문 경우지만 시험보는 경우도 있습니다.
  4. 기술 인터뷰 : 1~2회 정도.
  5. 코딩 테스트 : 있을 수도 없을 수도 있습니다.
  6. 실제 인터뷰 : 회사 방문하여 반나절 ~ 하루 종일 면접 진행
  7. 합격자 발표

서류 접수

링크드인을 통해 접수를 많이 했습니다. 한 30~50 군데 정도 한 것 같습니다. 몇몇 사이트를 통해 인사담당자에게 직접 메일을 보내기도 했습니다. 이력서를 굉장히 많이 고쳤는데 다음의 몇 가지가 참 중요한 것 같습니다.

  • 1페이지에 내용을 전부 넣을 것.
  • 링크드인의 경우, 추천사 부분도 꽤 중요한 것 같습니다.
  • 링크드인으로 바로 접수할 때, 문서상의 URL링크 등은 사라집니다.
  • 이력서의 오타에 조심하시고, 문법에 대한 부분은 일정 금액 지불하시더라도 반드시 교정받으시기 바랍니다.
  • 이력서는 최대한 달달 외워 두시는게 좋습니다.

인사 담당자 인터뷰

서류가 통과되면 인사 담당자의 연락이 옵니다. 시차를 감안해서 면접시간을 잘 잡으시길 바랍니다. 보통 다음과 같은 질문을 많이 합니다.

  • 이력서의 내용을 토대로 자기 소개를 해달라고 함.
  • 왜 이 나라로 옮기려고 하는지 물어봄.
  • 경력 상 회사를 옮겼다면 왜 옮겼는지 확인 함.
  • 몇몇 업무에 대해서 특별히 자세하게 물어보는 경우도 있음.
  • 원하는 연봉 수준을 제시해 달라고 함.
  • 회사의 서비스에 대해 알고 있는지 물어봄.
  • 만약 합격한다면 언제쯤 출근 가능한지, 현재 일하는 직장을 언제 그만 두는지 확인함.

이때, 기술적인 부분에 대해서는 좀 어려운 부분이라도 되도록 자세하게 묘사하는 것이 중요합니다. 인사 담당자라서 대강 뭉개도 될 것 같다고 생각하시면 오산인 것이 인사 담당자가 꽤 내공이 있을 수도 있고, 녹음을 하고 있을 수도 있으며, 스카이프 너머로 다른 사람이 더 있을 수도 있습니다.

사전 기술 검증 테스트

글래스도어를 검색해 보아도 꽤나 특이한 경우입니다.
시험을 볼 경우, 보통 1~2시간 정도 시간을 주고 특정사이트에서 시험을 보게 합니다. 문제 자체는 평이하지만 답은 iOS 개발 경험이 있어야 할 수 있는 것이 많아 그렇게 쉽다고는 못하겠습니다.

스카이프 기술 인터뷰

11가지 iOS 기술 인터뷰 단골 질문 from toptal.com

iOS 기술 인터뷰 질문 from raywenderlich.com

여기까지 오면 기술인터뷰에 들어갑니다. 기술인터뷰는 보통 두 번 정도 진행됩니다. 비슷한 급의 개발자와 실무적인 부분에 대해서 얘기를 나누게 되고, 좀더 급이 높은 개발자와 어려운 부분에 대해 얘기를 나누게 되는데 해당 내용은 상단의 링크를 참조하시기 바랍니다. 대체로 저정도 수준의 문제가 시니어 급에서는 많이 나옵니다. 그외에도 자주 물어보는 iOS 관련 기술에 대해 설명 드리자면 다음과 같습니다.

  • Objective-C 의 상속, 객체간 메시징, 프로퍼티에 의한 세터,게터에 대해 물어봄.
  • Core Data 사용에 대한 경험 혹은 ORM 에 대한 경험.
  • AutoLayout 에 대한 개발 경험 및 그와 관련된 몇 가지 문제를 제시.
  • 메모리 릭 관련 질문. 덧붙여 strong, weak, copy 등에 의한 소유권 관련 질문
  • Instruments 관련 해서 사용 경험에 대한 질문. 관련된 몇 가지 상황 제시 후 어떻게 대처할지 물어봄.
  • iOS9 베타를 쓰고 있는지, 애플워치에 대해 관심있는지 물어봄.
  • iOS 용어에 대한 발음에 유의하시길 바랍니다.
    • cocoa touch 는 미국식으로 발음시 “코코텃” 하는 정도로 밖에 들리지 않습니다. delegate 는 “딜리깃” 하는 식입니다.
    • 추천해드리고 싶은 방법은 CS193P 같은 영어 동영상 자료를 반복 청취하시는 것 입니다.

Big Nerd Ranch 시리즈 책이 많은 도움이 되었습니다. 기왕이면 원서로 읽으셔도 좋을 것 같습니다. 그외에도 RESTful과 REST의 차이와 같은 단순하지만 관심이 없다면 답을 하기 힘든 질문이 나오기도 합니다. 사실 다른 분야와 달리 iOS 쪽은 상당히 강점이 있는 것이 해당 플랫폼만의 용어를 굳이 한국어로 번역하지 않고 외국어를 그냥 사용한다는 것 입니다.

코딩 테스트

제가 경험해본 것은 다음의 두 가지 형태입니다.

  • 요구사항이 메일로 오고 지정된 시간 하에 앱을 만들어 제출. 시간은 90분 ~ 2주까지 다양함.
  • 메일로 웹사이트 주소및 아이디, 비번이 오고 해당 사이트에 들어가 문제를 풀도록 함. codility 같은 솔루션을 사용합니다.

실제 인터뷰

여기까지 살아남았다면, 회사는 당신에 대해 X 버튼을 눌러 경의를 표하며 왕복 비행기 표와 호텔 예약을 해줄 겁니다.
실제 인터뷰는 보통 반나절에서 하루 정도 걸립니다. 기술인터뷰, 팀인터뷰, HR인터뷰를 1시간 단위로 계속 합니다.

  • iOS 개발 관련 심도있는 질문을 하는데, 대체로 어떤 기술을 아는지 물어보고 해당 기술에 대해 특정 상황을 제시하고 해법을 물어봅니다.
  • gitflow 나 agile, scrum 에 대해 물어보고 사용해 본 적 있는지 어떤 경우에 사용을 했는지 어떤 느낌이었는지 자세히 물어봅니다.
  • 본인 이력에 비추어 궁금한 점을 물어보는데 꽤 심도있는 질문이 될 수 있으니 준비를 잘 하시길 바랍니다.
  • 다른 개발부서와의 협업에 대해, 그리고 여지껏까지의 개발 경험에 비추어 어떠했는지 확인합니다.
  • 창업을 했던 경험이 있다면, 어땠는지 왜 그만두었는지 등을 자세히 물어봅니다.
  • 아마도 제가 한국 사람이라서 물어본 것 같은데, “빨리 빨리”와 “야근 야근” 에 대한 본인의 생각을 물어봅니다.
  • JIRA, Confluence 같은 툴의 사용 경험이 있는지 확인 합니다.

이때, 참으로 중요한 것은 질문에 답만 해서는 안된다는 것입니다. 질문에 대한 자세한 정보를 알아내기 위해서 질문을 던지거나  궁금한 점이 있다면 적극적으로 물어보는 것도 중요합니다.

마치며

  • 기본적으로 모든 프로세스가 상호존중을 기본으로 삼고 있습니다. 주눅들게 할 일도, 주눅 들 일도 없습니다.
  • 질문을 받았을 때, 잘 모르겠다고 포기하지 말고 담당자와 잘 얘기를 해보는 걸 추천합니다. 기본적으로 해당 질문의 답을 얻을 수 있는 결정적 단서 까지를 제공합니다. 이것도 모르느냐고 갈구기 위해 질문 하는 것이 아닌 진정 아는지를 확인하기 위해 질문을 하는 것이기 때문입니다.
  • 대화중에 적극적으로 맞장구를 쳐줌으로써 상대방의 얘기를 경청하고 있음을 확실히 보여주는 것이 좋습니다.
  • 스카이프로 보통 진행하지만 어쩔 수 없이 국제전화로 진행이 될 수도 있습니다. 이 경우, 스카이프 보다 훨씬 어렵습니다. 통화품질도 이유가 될 수 있고, 대면하는 것보다 질문 파악이 힘들 수 있습니다.
  • 아무리 글로벌 기업이라도 외국인에 대한 취업비자를 작성하는 것은 꽤 부담이 가는 일입니다. 어느정도 규모가 되는 회사를 선택하는 것을 추천드립니다.
  • 글래스도어, 링크드인 등을 자세히 읽어보시면 해당 회사의 인터뷰 프로세스가 보입니다.
  • 창업을 했다가 망했거나, 회사를 자주 옮기는 것에 대해 이슈를 제기하거나 색안경을 끼고 바라보지는 않지만, 왜 옮겼는지에 대해 정확히 설명할 필요는 있습니다.
  • 질문하라고 기회를 줄 때, 본인의 경력이나 대화 능력에 대해 의문이 가는게 있느냐고 반문 하는 것도 정말 좋습니다. 솔직하게 말해줄 것이고, 솔직하게 답해주면 됩니다. iOS의 경우, 대체로 Swift에 대한 얘기를 많이 합니다.
  • 한국이 영어를 제2외국어로 쓰는 나라가 아니라는 것을 주지 시키면 (혹은 알고 있을 수도 있습니다.) 영어 발음 및 문법에 대한 기대는 그다지 크게 하지 않습니다. 다만, 기술 인터뷰와 팀 인터뷰에 대비해 몇몇 중요 용어들은 원어민 발음을 확실히 익혀두시기 바랍니다. 면접의 질이 달라집니다.

Add SegmentedController to Navigation Bar

앱스토어 앱을 베껴보았습니다.

UI나 UX 관련 포스팅은 되도록 자제하고 싶지만 어찌하다 보니 생각보다 고생한 부분이라 모쪼록 같은 문제로 고민하시는 분들이 이 글을 보고 덜 고생하시길 바라며 이 글을 씁니다.
오늘의 주제는 iOS 용 AppStore 앱 처럼 UINavigationBar에 UISegmentedController 를 넣는 법을 구현 하는 것입니다.
본 글에 사용된 샘플코드는 여기 있습니다.

Customizing Navigation Bar

네비게이션 콘트롤러를 고치는 것이 아닌 네비게이션 바를 고쳐야 합니다. 높이를 조절해야 하니 콘트롤러를 만져야 하는 것으로 오해할 수 있으나, 애플에서 제공하는 샘플 코드 자체가 UINavigationBar 의 클래스를 상속받아 몇몇 부분을 새로 구현하는 것으로 처리하고 있습니다. 다음의 애플에서 제공하는 샘플에 여러가지 경우의 네비게이션 바 커스텀 사례가 존재합니다. 원하는게 뭔지 모르겠다면 보는게 제일 빠르겠죠.

애플의 샘플코드

UINavigationBar 에 UISegmetedController 넣는 법

상기한 애플의 샘플코드에서 Extented Navigation Bar 혹은 Custom Navigation Bar 를 응용하면 될 것 같은데 여기서는 Custom Navigation Bar 를 사용했습니다. 이유는 후술 하겠습니다.

  1. CustomNavigationBar 라는 UINavigationBar 를 상속받은 객체를 생성합니다.
  2. 해당 객체를 스토리보드의 해당 네비게이션 콘트롤러의 네비게이션 바의 클래스로 잡아줍니다.
  3. @property 로 UISegmentedController 를 선언해 주고 setter 코드를 작성합니다. (샘플 참조)
  4. 다음의 코드를 작성합니다.
- (CGSize)sizeThatFits:(CGSize)size
{
    CGSize navigationBarSize = [super sizeThatFits:size];

    // 해당 네비게이션 바의 사이즈를 세그먼트 콘트롤러의 높이 만큼 키웁니다.
    CGSize segmentControllerSize = [self.segmentController sizeThatFits:CGSizeMake(size.width, size.height)];
    navigationBarSize.height += segmentControllerSize.height;

    return navigationBarSize;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // 세그먼트콘트롤러의 사이즈를 적당하게 잡아줍니다. (화면 회전에도 대응할 수 있도록 해야 합니다.)
    CGSize segmentControllerSize = [self.segmentController sizeThatFits:CGSizeMake(self.bounds.size.width, 0)];
    self.segmentController.frame = CGRectMake(10, self.bounds.size.height - segmentControllerSize.height - 5, self.bounds.size.width-20, segmentControllerSize.height-5);

    // 현재 네비게이션 바에는 세그먼트, 타이틀 순으로 그려져 있으므로 위치를 바꿔줍니다. 버튼도 마찬가지.
    [self setTitleVerticalPositionAdjustment:-segmentControllerSize.height forBarMetrics:UIBarMetricsDefault];
    [[UIBarButtonItem appearanceWhenContainedIn:[CustomNavigationBar class], nil] setBackgroundVerticalPositionAdjustment:-segmentControllerSize.height forBarMetrics:UIBarMetricsDefault];
}

마치며

스택오버플로우에서 찾은 내용

상단의 내용을 참조했습니다만, 굳이 Custom Navigation Bar 방식을 사용해서 텍스트와 세그먼트 콘트롤러의 위치를 바꿔준 이유는 translucent 속성 때문에 그랬습니다. 해당 부분이 상관없다면 다른 방법으로도 구현이 가능합니다.

오늘의 원 포인트 레슨

  • 네비게이션바의 타이틀위치는 일반적인 용례대로 뷰나 레이어의 오리진 값 변경으로는 바뀌지 않는다. setTitleVerticalPositionAdjustment 를 써야 한다. 고사기에도 그렇게 나와있다.

Face to Face Two Faced iOS app using WatchKit extension

본 문서는 iOS8.3 및  WatchOS 1.0.1 기준으로 작성된 문서이며, Xcode 의 버전은 6.3.2 기준입니다. 

당신의 앱에 왓치 앱을 추가하자

당신의 iOS 용 앱에서 File > New > Target > (iOS) Apple Watch > WatchKit App 을 선택해 WatchKit  Extension 및 WatchKit App을 추가 할 수 있습니다. Extension 에는 소스 관련 리소스가 있고, App 쪽에는 .storyboard 같은 UI 리소스가 존재합니다. InterfaceController 는 WKInterfaceController를 상속받은 객체입니다.
이것으로 완료 된 것 이지만, 빌드시 바로 빌드가 되지 않을 가능성이 높습니다. 문서 하단의 에러#1에러#2 를 참조하시기 바랍니다.

테스트#1 : 와치앱과 iOS 앱 간의 통신

애플 와치 앱을 켜면 다음과 같은 화면이 나옵니다.

IMG_2013

  1. 해당 화면의 하단 Randomize! 버튼을 클릭하면 랜덤함수를 통해 난수를 발생시킵니다.
  2. 해당 난수를 iOS 앱으로 보냅니다.
  3. iOS 앱에서는 화면에 전달 받은 난수를 표시합니다.
    IMG_2015
  4. iOS 앱에서는 그 숫자보다 작지만 가장 큰 소수(Prime Number)를 찾아 와치 앱으로 보내주도록 합니다.
  5. 와치앱은 해당 정보를 받아 표시합니다.
    IMG_2014

애플 와치 앱에서 iOS 앱으로 데이터를 보낼 때는 다음의 메소드를 호출 합니다.

[WKInterfaceController openParentApplication:sendData reply:^(NSDictionary *replyInfo, NSError *error) {
  NSLog(@"%@ %@",replyInfo, error);
  [self.primeNumberLabel setText:[replyInfo objectForKey:@"primeNumber"]];
}];

iOS 앱에서 애플 와치가 보낸 데이터를 받는 것은 AppDelegate 의 다음 메소드를 통해 받습니다.

- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
  [[NSNotificationCenter defaultCenter] postNotificationName:@"changeLabel" object:nil userInfo:userInfo];
  int randomNumber = [[userInfo objectForKey:@"randomNumber"]intValue];
  for (int i = randomNumber; i > 1; i--) {
    if([self isPrime:i]) {
      reply(@{@"primeNumber":[NSString stringWithFormat:@"%d",i]});
      return ;
    }
  }
  reply(@{@"primeNumber":@"2"});
}

해당 delegate 에서 생성된 데이터는 reply() 를 통해 전달하며 해당 reply()는 openParentApplication: reply: 의 reply블럭을 통해 돌아옵니다.

테스트#2 : 와치앱에서 CoreGraphics는 작동하는가?

작동합니다. 다만 WKInterface 로 시작하는 UI component 에는 layer 및 view 라는 프로퍼티가 없으며, 몇몇 WKInterface UI Component에는 사이즈에 대한 세터는 존재하지만 게터는 존재하질 않습니다. 그러므로 CoreGraphics 나 CoreAnimation을 작동시키는 것은 가능하겠지만 정교하게 그리는 부분은 힘들 것으로 예상됩니다.

IMG_2013

상단의 와치앱의 중간에 있는 것은 WKInterfaceImage 객체에 CoreGraphics 로 만든 이미지를 붙여 넣은 것입니다. 코드는 다음과 같습니다.

//선언
@property (weak, nonatomic) IBOutlet WKInterfaceImage *image;

//image에 이미지 세팅
[self.image setImage:[self textureWithVerticalGradientofSize:CGSizeMake(320,30) topColor:topColor bottomColor:bottomColor]];

//UIImage 만드는 함수
- (UIImage*)textureWithVerticalGradientofSize:(CGSize)size topColor:(CIColor*)topColor bottomColor:(CIColor*)bottomColor
{
    CIContext *coreImageContext = [CIContext contextWithOptions:nil];
    CIFilter *gradientFilter = [CIFilter filterWithName:@"CILinearGradient"];
    [gradientFilter setDefaults];
    CIVector *startVector = [CIVector vectorWithX:size.width/2 Y:0];
    CIVector *endVector = [CIVector vectorWithX:size.width/2 Y:size.height];
    [gradientFilter setValue:startVector forKey:@"inputPoint0"];
    [gradientFilter setValue:endVector forKey:@"inputPoint1"];
    [gradientFilter setValue:bottomColor forKey:@"inputColor0"];
    [gradientFilter setValue:topColor forKey:@"inputColor1"];
    CGImageRef cgimg = [coreImageContext createCGImage:[gradientFilter outputImage]
                                              fromRect:CGRectMake(0, 0, size.width, size.height)];
    return [UIImage imageWithCGImage:cgimg];
}

테스트#3 : 애플와치의 하드웨어 장비에 접근 가능한가?

불가능합니다. CoreLocation 은 사용이 불가능 합니다. 아마도 WatchKit 용이 공개되기 전까지는 불가할 것 같습니다. 현재는 iOS 앱과 통신해서 값을 가져오는 방법 밖에 없습니다. 또한 마이크 같은 장비에 직접 접근하는 것도 현재는 불가합니다.

테스트#4 : ForceTouch 사용하기

(일부) 가능합니다. WKInterfaceController를 상속받은 클래스에서는 다음의 메소드를 사용할 수 있습니다.

- (void)addMenuItemWithImage:(UIImage *)image title:(NSString *)title action:(SEL)action;
- (void)addMenuItemWithImageNamed:(NSString *)imageName title:(NSString *)title action:(SEL)action;
- (void)addMenuItemWithItemIcon:(WKMenuItemIcon)itemIcon title:(NSString *)title action:(SEL)action;

해당 메소드를 사용하면 해당 InterfaceController 에서 화면을 세게 누르면 다음과 같은 화면이 뜹니다. 버튼을 눌렀을때는  action에 선언된 selector 가 불립니다.

IMG_1996

에러#1 : WatchKit apps must have a deployment target equal to iOS 8.2 (was 8.3).

iOS 앱에 WatchKit Extension 을 추가하면 각 빌드세팅이 다음과 같이 존재합니다.

스크린샷 2015-06-01 16.38.47

빌드 세팅은 각각의 타겟을 위해 존재하므로, Deployment Target도 각각을 위해 존재합니다. WatchKit 관련된 Extension 및 App 의 Deployment Target 을 iOS 8.2로 고치도록 합니다. iOS App 의 Deployment Target 은 상관없습니다.

에러#2 : The value of CFBundleShortVersionString in your WatchKit app’s Info.plist (1.0) does not match the value in your companion app’s Info.plist (1.0.0). These values are required to match.

해당 에러는 iOS app의 버전과 watch app 의 버전이 다르기 때문에 발생하는 에러입니다. iOS 혹은 watch app 의 버전에 맞게 단일화를 시켜야 합니다. 여담입니다만, 빌드 세팅은 다를 수 있으나 버전 정보는 같아야 한다는 것은 과연 무슨 의미일까요?

마치며

지난 달에 구매한 애플와치를 써보고 앱도 만들어 보고 이렇게 글도 써봅니다. 당장에는 딱히 만들어야 하는 기능인가 싶을 정도로 기능이 적어서 공부할 필요가 있는가에 대해서 좀 회의적입니다만,  WWDC 2015 에서 iOS9 에 기능이 좀 추가된다면 가치는 더욱 올라가지 않을까 하는 생각이 듭니다. 사실 CoreAnimation 과 OpenGL도 해보려 했는데 앞서 설명한 바와 같이 UI 사이즈에 대한 게터가 없어서 급 귀찮아 졌다고 말씀드리고 싶습니다.

본 글에 쓰인 애플와치 샘플 앱은 제 깃헙에 존재합니다. 링크는 여기 입니다.

Apple Watch is Can Do Kid

애플 와치를 구매하고 딱 일주일이 지났습니다

그 사이에 애플 와치 익스텐션 들어간 앱도 만들어보고, 개발 버전 앱 설치 하러 오는 아이폰 사용자에게 자랑하기도 하고 (보통 뭔지도 모름) ,  애플 와치 전용 스탠드라던가 액정 필름이라던가 심지어 케이스 까지 국내에 이미 팔고 있고 가끔 품절되기도 한다는 것을 알 수 있었습니다.

iOS 장비 사용자가 애플 와치를 장비하면 생기는 신박한 일

  • 애플 와치 차고 다녀도 아무도 모릅니다. 아. 무. 도.
  • 정기 모임시, 애플 와치 차고 오는 가죽 주머니 정도의 대우를 받을 수 있습니다.
  • 애플 와치로 전화를 받거나, 시리에게 지시를 하거나, 문자를 보내고 있노라면 마치 전격Z작전의 데이빗 핫셀호프가 된 느낌을 받을 수 있습니다.
  • 액티비티 트래킹의 정확도는 여타 장비와 비슷하지만 집요함이 남다릅니다. 오래 앉아있으면 일어나라고 한다던가, 일주일 마다 푸쉬로 지난 주 운동량 체크를 해준다던가 하는 것이 미스핏, 조본과는 많이 다릅니다. 특히 장비로부터 데이터를 동기화 하는 부분의 안정성이 비교 불가입니다.
  • 이 글 쓰는 지금도 너무 오래 앉아있었다고 일어나라고 하는 군요. 일어나서 돌아다니고 다시 앉으면 잘했다고 칭찬해 줍니다. 다마고치가 된 느낌.
  • IMG_1975
  • 시계 앱에 초침이 오토매틱 시계마냥 스르륵 움직이는데, 초단위로 척척 움직이는 기능이 있었으면 좋겠습니다. 시계 모양의 조합은 가능하지만 페이스 자체가 다양하지가 않아서 좀 실망스럽고, 미키 넣었으면 구피나 도널드덕은 왜 없는지 기왕 디즈니 넣을거면 판권 소유하고 있는 어벤져스나 스타워즈도 넣어주었으면 하는 그런 소소한 불만이 있습니다. 그리고 포스터치 기능이 이 앱에 적용되어 있어서 새로운 UX를 익힐 수 밖에 없도록 짜여져 있습니다.
  • 나이키 런닝, 스트라바 등의 피트니스 앱은 이미 리모트 콘트롤 같은 기능을 제공하고 있습니다.
  • 미스핏도 애플 와치를 대응하는데, 자체 장비를 가진 앱의 대응이 놀라운데, 앱의 내용자체는 운동 도우미 입니다. 30초간 푸쉬업하라고 하는 등의 내용.
  • 리모컨 앱을 이용해 애플TV 등을 제어할 수 있습니다.
  • iOS 장비의 건강앱에 심박수가 매우 풍성해집니다.
    심박 센서에 대해 얘기를 안 할 수가 없는데, 시계 후면에서 그린 라이트를 비춰 피부 아래 혈관의 혈류량을 보고 심박수를 판별 하는 방식인데 (앱스토어의 카메라 LED 사용한 심박계 생각하시면 됩니다.) 이게 다른 액티비티 트래커와는 달리 조용히 시간 별로 계속 심박 체크를 하고 있습니다. 건강 앱에 조용히 계속 쌓이고 있습니다.
    정확도와 신속함에 신경을 많이 쓴 듯 합니다. 특히 신속함. 차고 있다가 초록불 들어올 때, 시계줄 풀어서 공중에 흔들어도 심박수 값을 가지고 있는 상태입니다. 심박수는 운동 후 칼로리 계산에 적용한다고 합니다.
  • 라인카카오톡도 메시지 받아서 해당 메시지에 대한 답장을 할 수 있는데, 이모티콘을 보낼 수 있도록 되어 있는 라인과는 달리 그냥 정직하게 애플에서 제공한대로 예, 아니오, 음성 입력만 되도록 되어 있는 카카오톡의 기능은 아쉬운 부분입니다.
  • 무전기 앱인 젤로는 애플 와치로 하는게 더 재밌을 줄 알았으나 와치 앱에서 와치의 마이크에 접근할 방법이 없는 관계로 결국 애플 와치로 젤로 앱을 활성화 하는 리모콘 기능밖에 없습니다.
  • 에버노트 앱의 메모 기능은 아주 맘에 듭니다. 켜보면 노트 추가 버튼과 검색 버튼, 최신 노트 리스트만 나옵니다. 노트 추가 누르면 바로 시리 음성 입력 기능이 켜지는데, 기능이 꽤 간편하긴 합니다만, 제가 비오는 날 메모 추가하는 모습 본 사람들 말로는 좀 거시기 해보였다고..
  • 상기와 마찬가지로, 음악 앱도 리모콘 기능 밖에 없습니다. 카메라 앱은 리모콘 기능이지만 신박하게도 현재 iOS 장비가 보고 있는 장면을 와치로 (조금 끊기지만) 볼 수 있습니다.
  • 노티피케이션의 경우, iOS 장비에서 받은 푸쉬는 모두 와치로도 오지만 장비에서 먼저 확인 하거나 장비가 켜져있는 상태에서는 와치로 날아오지 않습니다. 진동이 울리는게 아니라 꾹 하고 누르는 느낌으로 알려줍니다. 손목에서 플레이스테이션 듀얼쇼크 진동하듯 울리는 웨어러블 장비를 썼던 저로써는 감동스러울 따름.
  • 비행기 모드, 방해금지 모드는 iOS 장비에 따라 작동하도록 되어 있는데 비행기 모드를 해제했는데 와치가 비행기 모드인 경우, iOS 장비가 임의로 비행기 모드가 되기도 하는 등 뭔가 이상합니다.
  • 배터리. 하루 쓰면 충전해야 됩니다. 앱 개발 할 때나, 노티가 엄청 날아오는 날에는 오전사용만으로도 70% 미만으로 떨어지기도 했습니다. 미스핏 샤인이나 플래쉬 처럼 버튼전지 달고 6개월씩 갔으면 참 좋겠습니다.
  • 개인적으로 나토밴드를 좋아하는데 후면 센서 때문에 과연 장착 가능할 지 약간 의문을 가지고 있습니다. (고리끼리 다리 놓듯이 연결하면 되려나?)
  • 충전케이블 길이가 엄청 깁니다. 왜 그런지 잘 모르겠음. 외국 스타일은 침대 옆 테이블 에 두기 때문인가 싶긴 한데. 아무튼 엄청 깁니다.

개발자가 애플 와치로 할 수 있는 것

  • 해당 앱 푸쉬 노티피케이션 관리.
  • 스토리보드로 버튼, 테이블 등의 UI 컴포넌트 배치를 하거나 동적으로 생성하거나 할 수 있음.
  • 와치 -> iOS 장비를 부를 수 있으며, 해당 메소드 호출시 iOS 앱쪽의 UIApplicationDelegate의 handleWatchKitExtensionRequest: 가 불림.
  • 작성 중인 샘플 코드

개발자가 애플 와치로 할 수 없는 것

  • (현재) 포스 터치, 디지털 크라운 API 공개 안되어 있음. 다만 포스 터치의 경우, WKInterfaceController통해서 사용은 가능한데 그렇다고 특수한 콜백이 있다거나 하지는 않습니다.
  • (현재) 애플 와치로 CoreLocation, CoreMotion API 사용 불가. 해당 API는 아이폰에서 부르고 처리해서 와치로 데이터 넘기는 것만 가능.
  • (현재) 심박 센서 API 공개 안되어 있음.
  • (현재) 상기와 동일하게 내장된 입력 장비, 그러니까 마이크등에 직접 접근할 방법도 없습니다.

개발자가 애플 와치에 짜증낼게 확실한 것

  • 선으로 연결되어 있는 iOS 장비와는 달리 iOS 장비에서 앱을 설치해 줘야하는 애플 와치의 경우, 디버깅 하는데 좀 짜증나는 부분이 있습니다. 가끔 해당 앱의 pid 를 못잡는지 디버깅 위치쯤 오다가 바람개비 돌면서 애플 와치가 멈춰 버리는 경우도 종종 생김.
  • 애플 와치의 앱은 어디까지나 iOS 앱의 extension 이므로 iOS 앱이 없는 스탠드 얼론은 불가능 합니다.
  • 상기의 사항에 덧붙여, 애플 와치는 iOS 장비에 기능 처리를 부탁하고 내용을 돌려받는 식으로 작동하고 있으므로 애플 와치 화면 뿌리는 것만 만들 뿐이라고 개발자간에 이해하고 넘어가는게 오해의 소지가 없을 것 같습니다.
  • 새로 빌드하고 iOS에 앱을 띄워도 애플 와치에 있는 앱은 새 앱이 아닙니다. 조금 (많이) 기다리면 업데이트를 하는데, 답답하면 지웠다 다시 깔거나 해야 합니다. 생각보다 되게 귀찮고 시간도 많이 잡아먹습니다. 애플 와치에 앱 설치하려면 iOS 에 있는 애플 와치 앱을 통해서만 가능합니다.
  • 애플의 활동 앱 처럼 원형 그래프가 움직이는 걸 만드는데 이상하게 프레임이 낮아지는 사태가 발생합니다.
  • 외국쪽 힙스터들이 다 스위프트로 작성하니 애플와치가 스위프트로만 돌아간다고 알고 계신 분도 있는데 그런거 아닙니다. 옵씨로도 다 되요.

마치며

wwdc 2015 에서 기능 제공 얘기 나오면 사야지 하고 있었는데 너무 일찍 사버린 것 같아 후회가 들기는 합니다. 개발자가 애플 와치 가지고 할 수 있는게 그다지 없는 관계로 할 말도 별로 없고..
포스터치 기능의 경우, 꽤 신박해서 차기 iOS 장비에 꼭 들어갔으면 합니다. 안드로이드의 롱 프레스에 비견될 iOS 만의 기능이 되지 않을까 합니다.
애플 와치 스포츠에 사용된 7000 시리즈 알루미늄은 꼭 차기 iOS 장비에 사용되었으면 합니다. 작아서 그런지 모르겠지만 훨씬 단단한 느낌이라 맘에 듭니다.
제 것만 그런지는 모르겠으나 후면 센서부를 덮고 있는 돔 형태 유리의 마무리가 아주 깔끔하지는 않습니다. 약간 기포 들어간 느낌.

사실 스와치 시스템51 유럽가서 싸면 하나 사야지, 라미 사파리 유럽가서 싸면 하나 사야지 했던 계획이 어쩌다 애플 와치로 홀랑 바뀌었는지는 귀국한 지금도 뭐가 뭔지 하나도 모르겠는 그런 심정.. 개선문 사진 올리고 애플 와치사러 파리 왔어요. 이거는 사실 그냥 허세 부린거였습니다. 용서해 주세요. 애플 스토어에 전시만 할 뿐이지 팔지 않는다는 거는 런칭일에 검색해서 이미 알고 있었습니다. 다만 그냥 2시간 정도 줄서면 살 수 있을 줄은 꿈에도 생각을 못했을 뿐.

How Can I Buy a Apple Watch on Paris?

파리 루브르 애플 스토어에 가다

이번 여행기간에 다른 곳도 많이 갔지만 프랑스 파리에 도착하자마자 가본 곳이 바로 파리 루브르 박물관에 있는 애플 스토어 입니다. 샌프란시스코, 홍콩, 스페인 이후 4번째로 가본 곳이네요. 오페라 지점 및 라파예트 백화점 시계 진열관은 휴무로 가보지 못해 아쉽습니다.

IMG_9826

루브르 박물관으로 들어가기 전 지하에 애플스토어가 존재합니다.

IMG_9817

애플 와치는 다음과 같이 진열되어 있습니다. 38mm, 42mm 나누어져있고 스포츠, 와치, 에디션 순으로 진열되어 있으며 유리로 막혀있어 보는 것 만이 가능합니다. 여기서 애플 지니어스에게 애플 와치 시착을 의뢰하면 애플 ID 를 입력하고 순서를 기다려야 합니다.

IMG_9818

본인 순서가 되면 애플 지니어스가 부르는데 상단의 테이블로 이동합니다. 본인이 차고 싶은 사이즈 및 색상과 스포츠 혹은 일반 버전인지, 차고 싶은 줄은 있는지 확인 후 해당 모델을 시착하게 해줍니다.

IMG_9815

저는 42mm 스포츠 블랙 모델을 시착해 보았습니다. 즐겨차는 타이멕스 위켄더 보다 많이 크더라구요. 해당 시착 모델은 데모만 돌아가는 버전으로 아무 입력을 할 수 없습니다.

나는 애플와치 사고 싶다. 파리에서. 지금 당장.

사실 여기까지만 보고 가려고 했는데 데모 버전에서 포스 피드백이 툭툭 누르는 느낌을 받고 나니 아 이걸 사야겠구나 하는 생각을 하게 되었습니다. 그래서 애플 지니어스에게 이렇게 물었습니다.

나 : 이거 사고 싶은데 지금 살 수 있나?
지니어스 (이하 ‘지’) : 매장에는 시착 모델만 있고 판매 모델은 없다. 구입은 스토어를 통해 하도록 되어 있다.
나 : 그럼 내 애플스토어 아이디 주소가 한국인데 이거 프랑스 호텔 주소로 바꾸고 구입해도 내가 여행 끝나기 전에 받을 수 있다는 거냐?
지 : 예약을 걸어야 하고 아마 5월에는 다 받을 수 있을거다.
나 : 난 지금 돈내고 물건 받아서 가고 싶은데 방법 없냐? 오페라 지점이나 라파예트 가면 파는거냐?
지 : 내가 알기로 오페라에는 진열이 아예 안되어 있고, 라파예트 백화점에 있는거는 판매용 매대에 진열된게 아니다. 우리처럼 시착만 된다.
나 : 그럼 돈내고 물건 못 가져가냐? 방법 없나?
지 : 여기서 한 두어 블럭 가면 콜레뜨 라는데에서 몇 개 판다고 들었다. 그거 말고 어떤 애플스토어에서도 실물로 판다는 얘기는 따로 못 들었는데 중국 스토어에서 판다는 얘기는 들은거 같긴 하다. 자세히 알고 싶으면 알아봐 주겠다.
나: 중국인 아니다. 아까 말한게 콜레뜨? 코제뜨?
지 : 콜렛뜨.  Colette

결국 파리에서 애플 와치를 사다.

조금 더 차고 있다가 가격도 안물어보고 그냥 나왔습니다. 알아보니 스포츠 모델 38mm : 399 유로 / 42mm : 449 유로 더군요. 북미 가격은 38mm 349 달러 / 42mm 399 달러 입니다. 유럽 가격이 훨씬 비쌉니다. 달러와 유로가 거의 1:1 환율인걸 생각해보면 비싼거죠. 사실 보고 나서 가격 보고 42mm 크기도 그렇고 해서 사지말까 고민하고 있었는데 다른 관광 다니고 하다보니 눈에 아른 아른 하더라구요. 살 수 있을때 사긴 사야겠어서 결국 콜레뜨를 찾았습니다.
어디 있느냐 하면 뛸르리 역에서 오페라 역 가는 길에 있습니다. 나름 굉장히 유명한 패션샵이라고 하더군요. 아침에 갔는데 이미 가게 앞에 줄을 엄청 섰더라구요.

IMG_9808

가서 저도 얼른 줄 섰습니다. 한 시간 반정도 기다렸나.. 제 순서가 오긴 오더군요. 콜렛뜨가 사람이 많이 오는 편집샵이긴 하고 나름 명품도 많이 팔지만 사람이 줄서서 들어가지는 않습니다. 사진의 우측 줄은 오로지 애플와치를 사기 위한 줄이며 건물 돌아서 주욱 이어집니다. 기다리다 보니 3년전에 WWDC 가서 밤새 줄섰던 기억도 나고 하더라구요.
아무튼 한참 기다려서 결국 구매했습니다. 38mm 스포츠 모델 검은색.

애플와치를 현재 살 수 있는 방법은 다음과 같습니다.

1. 애플스토어에서 산다. 
웹사이트에서만 팝니다. 그러므로 주소가 1차 출시국의 주소여야 합니다. 1차 출시국에 갈 일이 있다면 해당 호텔로 배송시키고 가서 받는 방법도 가능할 것 같은데 시도해보기에는 정확한 배송일을 알 수 없는 문제가 있습니다.

2. 현재 1차 출시국에 리셀러샵에서 산다.
제가 선택한 방법입니다. 한국의 A# 처럼 애플와치만 따로 파는 리셀러 샵이 몇 군데 있다고 합니다. 콜렛뜨가 애플와치만을 취급하는 리셀러 샵입니다. 4월 24일 런칭일에 줄이 엄청 길었다고 하고, 제가 구입한 날은 5월 2일인데도 사람이 꽤 많았습니다.

3. 한국 출시를 기다린다.
애플와치내 메뉴는 다 한글화 되어 있으며, 시리도 잘 작동합니다. 예상해보자면 올 6월정도에는 출시 되지 않을까 싶긴 한데 늦으면 9월까지도 가지 않을까 싶기도 하고, 애플TV의 전례를 볼때 출시가 안 될 가능성도 있지 않을까 싶습니다.

그로부터 약 3일간 사용하다

사고 나서 여행지에서 하루 정도 사용해보고 비행기에서 에어플레인 모드로 차고 귀국해서 오늘까지 사용해보고 있습니다. 현재 느낀 점은 다음과 같습니다.

  • 알림이 울리면 포스피드백이 툭 하고 치는데 그 느낌이 진동 오는 것과는 아주 다릅니다. 알림 오는 느낌이 굉장히 좋습니다. 누가 툭툭 치는 것 같은 그런 느낌.
  • 추가된 UX에 세게 꾹 하고 눌러주는 것이 있는데 차기 iOS 장비에 들어가지 않을까 싶습니다.
  • 패스북 앱은 이미 애플워치에 대응합니다. 해당 QR코드를 리더기로 읽는게 가능합니다. 패스북 앱에 있는 비행기 표를 애플와치로 열어서 그거 스캔해서 비행기 탔습니다.
  • 38mm가 좀 작긴 합니다. 제 취향에 사이즈는 적당한데, 기본 글자 크기가 좀 작은 느낌입니다. 글자를 키우면 되긴 하지만 뭐.. 근데 42mm는 동양인 손목에는 너무 크지 않은가 싶긴 한데, 빅페이스가 유행이니 그건 뭐라 못하겠습니다.
  • 배터리는 확실히 빨리 소모되는 것 같습니다. 아침 8시에 나와서 이 글 쓰는 오후 2시에 배터리가 80% 정도 남았습니다. 10시간 비행 하고도 50%이하로 안떨어지기는 했지만 그래도 배터리 용량이 많지는 않은 것 같습니다. 그냥 알림만 받는게 아니라 앱 사용하고 그러면 회사에도 집에도 충전기 놔야 할지도 모르겠습니다.
  • 의외로 충전독 장사가 꽤 잘 될 것 같습니다. 저만해도 당장 이쁜 거 하나 나오면 사고 싶을 정도.
  • 스포츠 모델 기본 줄이 생각보다 되게 보드랍습니다. 부드러운게 아니고 보드랍습니다.
  • 제가 사용해본 미스핏 샤인, 플래쉬, 조본 업 등과 비교했을 때, 나름 궤를 달리하기는 하는데 동기화나 기본 제공 기능같은 편의성을 봤을때, 시장 다 잡아먹을 괴물아닌가 싶긴 합니다.
  • 미러링이라고 해서 iOS 장비의 에어플레인 모드, 방해금지 모드에 바로 대응하도록 되어 있는데 이게 좀 제대로 작동하질 않는 것 같습니다.
  • 앱 설치하는데 시간이 꽤 걸립니다. 애플 와치 기능 붙일때는 용량 최적화에 신경을 써야 할 것 같습니다.
  • 애플이 애플스토어에서만 그것도 웹사이트에서만 파는 것에 대해 파리 패션 전문가가 라파예트 매대에 엄청 고가의 명품시계도 시착하고 살 수 있는 판에 399유로짜리 시계가 돈 있어도 못 사는게 전략인지 배짱인지는 잘 모르겠다는 이야기를 한 바 있습니다. 근데 라파예트는 결국 못가봐서리..

사실 구경만 좀 하고 이번 WWDC 에서 공개되는 기능 좀 보고 구입하던가 다음 세대에나 사던가, 한국 출시되면 사야지 했는데 눈에 보이니 그냥 구입해 버리고 말았습니다. 이 글을 보는 아이폰, 아이패드 사용자들은 아마 다들 그러실거에요. 아마..
원래는 앱이나 좀 만들어보려고 했는데 줄 장사나 충전독 장사 하는게 더 돈 잘 벌 방법 아닌가 싶은 그런 느낌적인 느낌입니다.

BSXPCMessage received error for message: Connection interrupted

CoreGraphics 라고 로우레벨단의 그래픽 처리 관련 API를 쉽게 사용할 수 있도록 해주는 프레임워크가 있는데, 해당 API 중에 CoreImage 라고 image 처리에 특화된 클래스가 있다. 해당 기능을 이용해 간단한 그래디언트 이미지를 만드는 중에 발생한 에러메시지이다.

코드는 다음과 같다.

CGSize size = CGSizeMake(width, height);
CIColor *topColor = [CIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
CIColor *bottomColor = [CIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0];
CIContext *coreImageContext = [CIContext contextWithOptions:nil];
CIFilter *gradientFilter = [CIFilter filterWithName:@"CILinearGradient"];
[gradientFilter setDefaults];
CIVector *startVector = [CIVector vectorWithX:size.width/2 Y:0];
CIVector *endVector = [CIVector vectorWithX:size.width/2 Y:size.height];
[gradientFilter setValue:startVector forKey:@"inputPoint0"];
[gradientFilter setValue:endVector forKey:@"inputPoint1"];
[gradientFilter setValue:bottomColor forKey:@"inputColor0"];
[gradientFilter setValue:topColor forKey:@"inputColor1"];
CGImageRef cgimg = [coreImageContext createCGImage:[gradientFilter outputImage] fromRect:CGRectMake(0, 0, size.width, size.height)];

이 부분이 iOS8 장비에서 불리기만 하면
“BSXPCMessage received error for message: Connection interrupted”
라는 메시지가 로그창에 뿌려진다.

CIContext *context = [CIContext contextWithOptions:nil];

해당 메시지는 위의 CIContext 에서 옵션값을 nil로 주면 @{kCIContextUseSoftwareRenderer : @(NO)} 로 들어가기 때문에 발생하는 문제이다.

CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(YES)}];

해당 메시지를 안보이게 하기 위해서는 해당 옵션값을 YES로 주면 해결 된다.

근데 척보면 알겠지만 kCIContextUseSoftwareRenderer 라는 옵션값은 해당 필터가 CPU 베이스로 작동하도록 하는 설정값이다. 해당 값을 YES로 하는 순간, 해당 필터를 그리면서 프레임이 엄청나게 떨어지는 문제가 발생한다.

에러 로그 한 줄 잡으려다 초가삼간 태워먹는 셈이다.  

또한, iOS8에서만 발생하는 단순 버그라는 설도 있다.
확실한 것은 iOS7 테스트를 해봐야 알 것 같다.
(수정 : 2015년 6월 29일) iOS7.1.1 장비에서 테스트 해 본 결과, 해당 에러는 출력되질 않았다. 고로 iOS8 관련 단순 버그일 가능성이 매우 높다.

My iPad shows crazy color when opens pdf that made by CMYK color space

발단

국내 뉴스가판대 전용 앱이 열 몇개인 판에 이런 글 올려 무엇하겠는가 싶지만 그래도 열과 성을 다해 올려본다. 오늘의 주제는 CMYK로 작업한 결과물을 아이패드에서 열면 색이 이상하게 나오는 이유와 그 해결책은 무엇인가 이다.
일단 시작은 이러했다. 뉴스가판대 앱을 프로토타입으로 만들 필요가 있었고, 뉴스가판대 앱을 만드는 방법은 viggio soft의 iOS News Stand Tutorial 을 참조해 금방 만들 수 있었다. 그리고 PDF 리더는 근성가이 vfr 형님pdf 뷰어를 사용해 붙여보았다.  사실 iOS에 Quick Look 프레임워크가 기존재하므로 이걸로 해도 되지만 근성가이를 존경하는 사심을 듬뿍 담아 사용해 보았다. (아 물론 전혀 모르는 사람입니다.)

갈등

문제는 다 만들고 나서 발생했는데, 전달받은 pdf를 여는 순간 초록색과 붉은색 계열이 형광색으로 보여지는 현상이 발생했다. 이 파일을 피씨 혹은 맥에서 열었을때는 전혀 문제가 없었다. 얼핏 줏어들은 바가 있어 색공간 문제 인가 하고 해당 오픈소스에 문제인가 하고 color space 같은 설정을 바꾸는게 있는가 하고 한참 찾았다가 다음과 같은 글을 발견했다.

스택오버플로우 – 아이패드로 pdf를 볼 때 색깔이 이상합니다.

어? 하는 마음에 ibooks, dropbox 등으로 해당 파일을 열어보았다. 역시나 색깔 문제가 있었다. 상기의 스택오버플로우에서는 CMYK로 작업된 문서는 iOS장비가 CMYK를 지원하지 않으므로 문제가 발생하니 sRGB, adobeRGB를 사용하라고 추천하고 있다.

해결

정말일까?
알아보니 Cyan, Magenta, Yellow, Key 로 이루어진 CMYK 방식은 일반적으로 출력물을 만들 때 사용하는 방식이라고 한다. 찾고 보니 확실히 pdf를 볼때, 초록색 계열은 시안 계열로 붉은색 계열은 마젠타 계열로 번쩍번쩍 빛났던 것 같다. 자 그럼 CMYK PDF를 sRGB로 바꾸어 보자.

  • $ brew install ghostscript
  • $ gs -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -dCompatibilityLevel=1.4 -dColorConversionStrategy=/sRGB -dProcessColorModel=/DeviceRGB -dUseCIEColor=true -sOutputFile=output.pdf input.pdf

output.pdf 와 input.pdf 만 적절히 바꿔주도록 하자.

평화

고스트 스크립트를 사용해 적절히 넘겼지만, 혹시라도 프로젝트가 PDF를 지속적으로 처리해야 하는 업무라면 출력물을 만들 때는 CMYK로 작업하고 InDesign등을 사용해 생성할 때 CMYK 방식과 sRGB 방식으로 생성해 달라고 요청하는 것이 좋을 것 같다.

  • sRGB로 작업한 작업물은 실제 인쇄시 문제 발생, iOS 장비에서 문제 없음
  • CMYK로 작업한 작업물은 실제 인쇄시 문제 없음. sRGB로 변환하면 iOS 장비에서 문제없음.

그러므로, 인쇄가 필요하다면 CMYK로 작업 후,  sRGB 파일을 요청하는게 훨씬 나은 방법이므로 이렇게 결과물을 달라고 요청하는게 나을 것 같다. 아님 받을 때마다 고스트 스크립트를 써서 변환해 주어야 하니까..

Box2D – the physics engine

들어가며

지난 2015년 3월 13일 오후 6시가 다 되어서야 기쁜 소식을 알릴 수 있었는데,  바로 햇수로 3년째 잡고 있던 Box2D 영문 매뉴얼을 한글로 번역 완료했다는 것이었다. 챕터 3~4 정도 했을 즈음에 한글 번역이 꽤 존재함을 많이들 알려주어서 (매우 고맙게도 –^) 멘탈 확 접히고 한 6개월 정도 쉬었다가 다시 시작했다가 내버려뒀다가 약간 배운 재주로 이것저것 만들어보다가 또 접었다가를 반복하다가 결국에 번역을 끝내고 말았는데, 어찌어찌하다 보니 팀 내 전폭적인 지원끝에 결국 하루에 한 챕터 번역이라는 말도 안되는 속도를 내며 결국 완료해버리고 말았다. 약 2년여 전에 2.2.0 버전 매뉴얼로 시작했는데 현재도 2.3.1 정도 밖에 안 나온 것은 다행 아닌 다행이다.

Box2D 2.2.0 한글 매뉴얼

Box2D 공식 사이트

번역후기를 시작하겠습니다. 근데 박스2디는 먼가요.

Box2D는 물리엔진이다. 이전부터 2D 게임에서 많이들 사용했던 것인데 갑자기 확 유명해진데는 아무래도 GDC에서 있었던 앵그리버드 사건의 훈훈함 덕분이지 않을까 싶긴하다. 나도 이 사건 전까지는 Box2D가 뭔지도 몰랐으니까. 무식하게도 이 엔진이 얼마나 도움이 될지를 당시에 몰라서 뉴스만 알아두고 넘어갔었고, 차후 블리자드사 디아블로3의 물리엔진 개발자로 취직한 에린 카토(Box2D 개발자)의 근성 넘치는 GDC 강연을 보고 그때부터 한글문서를 만들어야 겠다는 생각을 했다. (선후관계가 정확하지는 않습니다.)

번역 시작의 계기

당시에 프로젝트가 휘청할 때였고, 본인의 삶은 피폐해졌고, 친한 친구들이 하나 둘 회사를 뜨면서 뭔가 정신없이 매달릴 뭔가가 필요했던 그런 시기였다. 당시에 마크다운이라던가, 하루패드에 관심이 있었고, doc로 된 문서를 보면서 이거 왜 이렇게 배포하는가 하는 맘도 좀 있었고 하다 보니 마크다운으로 바꾸는 욕심도 있었었다.
생각해보면 나름 개발자라고 자뻑이 좀 있었는데 실제 내가 하는 일이 회사에서 돈이 안되니 접으라는 말에 한 방에 접힐 수 있는 일이었으며, 일을 끌고나가는 매니저가 빠지는 것만으로 나락으로 떨어지는 일이었음에 좀 절망했던 것 같다. 같이 일하는 동료들도 하나둘 나가고 그 큰 회사에 점심 같이 먹자는 사람도 없어서 에라 모르겠다라고 강제 1일1식이 되어버린 그런 시기였으니까. 열심히 만들던게 와르르 무너져서 그랬던가 도저히 코드는 보고 싶지도 않았고 딱히 뭔가 하라는 것도 없어서 단순히 기댈 수 있었던 마지막 희망이었달까.

마크다운과 함께

애초에 에린 카토의 doc 파일이 그다지 어려운 레이아웃이 아니었던 관계로 마크다운 파일로 옮기는 작업은 생각보다 금방이었다. 하루패드로 양쪽 레이아웃 보면서 작업하는 것도 생각보다 쉬웠고 해서. 결국 마지막 번역까지 함께 했던 툴은 하루패드 뿐이었다. 양원님께 감사할 따름.
애초에 깃헙에 올린 이유는 마크다운 파일을 보여줄 때 제일 이쁘게 보여지는 사이트가 거기 밖에 없어서 였다. 작업이 쉽다, 협업한다 뭐 그런거는 아니었고..

넘어지고 또 넘어지고

그러다가 정말 회사가 훅 가버렸다. 정말로. 프로젝트를 옮겨서 뭔가 좀 이제 일할만 하다 했더니 다들 또 별 말 없이 위에서부터 주르륵 나가버렸고 이번에도 그 더러운 기분이 뭔가 찜찜하게 남아서 결국 매뉴얼 작업은 절반 정도 완료된 상태에서 정지하고 말았다. 다만 마크다운화한 영어부분만 올려놓고 진짜로 작업 정지. 프로젝트를 닫은 것도 유지하는 것도 아닌 것을 두개 나 내 이름 앞에 놓고 입사한지 3년도 안된 회사에서 나갈테냐 말테냐를 놓고 고민하는데 그 기분이란 정말이지.
그 이후로 여기저기 원서넣으면서도 뭔가 부끄러웠다. Box2D 영문 번역을 하고 있다고 하고 설명은 신나서 하는데 부끄러웠고 사실 이걸 왜 얘기해야 되는지도 모르겠고. 결론적으로 잉여짓일 뿐이라는 아주 날카로운 얘기를 몇 군데에서 들었고 “고맙습니다만..” 이라는 내용의 거절 메일을 몇 통 받고서야 정신이 확 들었고, 어느 순간부터 다음 주에는 챕터 하나 정도를 일주일 걸려서 끝내야 겠다 하는 생각도 안하게 되었고, 깃헙 레파지토리 업데이트도 한 동안 안하게 되었다.

구르고 또 구르고

사람이 참 웃긴게, 2000년 초반에 피쳐폰용 스포츠 게임 만들 때, 정말로 너무도 필요했던 것이 물리엔진이었는데 그런게 뭔지도 모를 때는 그렇게 만들고 싶었던 것인데 실제로 나에게 다가왔을 때는 그 가치를 전혀 모르겠을 시점이었다는 것이었다. 회사 생활의 결말이 어찌될 지 알았더라면 하는 생각도 하지만, 당장 내가 퇴근하고 뭘 할지도 모르는 판에 뭐가 그렇게 중요할지를 모르겠다.
업무적으로 구르고 또 굴렀으면 차라리 지금 뭐라도 한다고 하겠는데 그런 것도 아니어서 깃헙에 가끔 옵씨나 자바 코드나 좀 올리던 찰나에 계속 눈에 밟히던 Box2D를 다시 번역해야 겠다고 생각했다. 너무 늦지 않았나 하는 생각이 들긴 했지만 그래도 안하느니 보다는 낫다고 생각했고 번역에 대한 관점을 좀 달리하기로 했다.

스탠스 변화와 전술의 관계

버추어 파이터에서 잭키는 스탠스를 좌우로 바꿀 수가 있는데, 이게 캐릭터가 좌측에 있느냐 우측에 있느냐에 따라 상대방과 붙을 수 있는 거리가 조금 다른 관계로 전술적으로 중요하고 소울칼리버에서 나이트메어의 자세 바꾸기는 약간 횡이동을 하는지라 호밍하지 않는 기술에 대해 약간 전술적 우위가 있는 식으로, 스탠스를 그때 그때 바꾸는게 아주 중요하다는 생각을 했다.
왜 나는 내가 좋아하는 게임 개발과 관련된 아주 중요한 물리엔진이라는 분야에 대한 번역을 중도에 포기 했는가? 하는 질문에 내가 답을 해야 했다.

용어의 어려움

오일러를 영어로 Euler 라고 한다는 사실을 처음 알았다. rigid body 를 설명할 때 body 라고 하는 부분만 나오면 이걸 강체로 번역하면 참 편하겠지만 문맥상 아닐 때도 있다. time step 은 시간 단계라고 번역하고 싶지만 멋드러진 번역은 아니다. 델타 타임이라고 봐야 되는데 이렇게 적으면 뭔가 너무 무책임하고.
결론적으로 처음에 너무 어깨에 힘이 들어갔다. 나오는 물리용어를 무슨 교과서 채택이라도 된냥 너무 열심히 번역하려고 노력을 했다. 그러기엔  애초에 물리학과를 나오지도 않았고 수학도 잘하는 편이 아니었다.

커밋 결벽증

깃헙에 기껏 올려놓고 작업은 드랍박스에 있는 파일을 고쳤다. 왜 그랬는가 하면 깃헙 커밋을 한 챕터당 한번만 하고 싶어서. 대강 다음과 같이 작업을 하는데

  • 영어 부분 위에 한글 추가
  • 한글 부분 마크다운 레이아웃 추가
  • 영어 부분 삭제

이 부분이 커밋에 남는게 왜인지 너무 싫었다. 이런 뭔가 커밋관련 결벽증은 그냥 눈딱감고 포기하는 식으로 넘어가게 되었다. (한 1년 정도 걸린 듯)
아, 결국 완전 포기하게 된 계기는 불과 얼마전에 내가 회사에서 올린 부분과 집에서 올린 부분과 깃헙에 있는 부분이 충돌이 나면서 였다.

막판 스퍼트

참으로 신기한 것은 몇 가지 원칙을 무시하고 진행하니 일이 너무도 쉬워졌다. 그냥 별 부담없이 끄적끄적 하다보니 하루가 훌쩍 가고, 정신 차리고 보니 한 챕터가 뚝딱. 이거 잘해서 원서 번역하는 일도 할 거에요 뭐 이런 야망을 버리고 보니 재밌게 일을 쭉쭉 진행 할 수 있었다. 그 바람에 단순히 중력과 충격주기만 존재하던 개인 프로젝트도 자석이나 조인트 같은게 추가되기 시작했으니..

이 작업을 통해서 배우게 된 점

  • 비록 서비스도 뭣도 아니지만 몇 년이나 붙잡고 있었으며, 그럼에도 없어지지 않고 쭉 나오고 있다는 사실이 참.. 끈기와 뚝심 그리고 근성에 대해 생각해보는 좋은 경험이었다.
  • 비록 유니티나 하복 같이 저 하늘의 별처럼 반짝이는 무언가는 아니지만 그래도 이 부분에 대해 공부하고 붙잡고 있다보니 나름 좀 알게된지라 으쓱한 부분도 있었다. 특히 얼마전에 cocos2d + chipmunk 로 된 프로젝트를 완전 접고 spritekit 으로 바꿔버리며, 그냥 물리쪽 API 가져다가 바꿔주기만 하면 그냥 뿅 하고 돌아가는걸 보면서 “아 이거 개꿀” 하는 때에 좀 기분이 좋았던 것도 있다.
  • 유니티에서 만든 콜리더가 저 하늘 위에서 생성되서 바닥을 통과해서 저 밑바닥까지 떨어지는 것을 눈만 껌뻑이며 쳐다보지 않게 되었다. (아 그렇다고 제가 게임개발자라던가 유니티로 벌어먹고 산다던가 하는건 아닙니다.)
  • 천조국 교육계에서는 단순한 물리법칙에 대해 접근하는 방식 자체가 다르다는 것을 깨달았다. 조인트에서 도르래 구현이 나오며, block and tackle 구현에 고생했음이 나오는데 한국인 입장에서는 고정도르래, 움직도르래, 복합도르래 밖에 모르니 이게 뭔가 싶기도 하고 번역도 안나오고 위키 페이지를 봐도 이게 뭔가 싶은 부분인데, 교육의 기본 방향이 “개념 > 실제 현상 혹은 자연 현상 > 응용 > 법칙 도출” 이렇게 되어있는 천조국과 달리 “개념 > 법칙 > 시험” 이것만 반복해온 인생이 허무해지는 부분이 군데군데 있었다.

결론

본의 아니게 Box2D의 코어 부분까지 파고 들면서, 오일러 적분법을 코드로 확인하기도 하고(근데 지금도 이해가 안가는게 이게 왜 빠르다는건지?) 한참 손놓고 있던 openGL도 다시 보기도 했고, 점점 확장되어 언리언 엔진 구독 신청해서 한달치 사용료까지 내기도 했는데 슬슬 흥분은 가라앉히고 본업으로 넘어갈 때가 되어 정리차 이렇게 글을 씁니다.
번역을 오며가며 해서 왠지 존댓말, 반말이 오고 갈지 모르는데 이건 뭐 길어져서 그런거니 이해를 바랍니다. 본 글도 왔다갔다 하니까 뭐..
사실은 챕터 뭐에는 뭐가 있고 뭐 이런 글을 쓰려했으나 워낙 오래 붙잡고 있던지라 그냥 개인적 신변이 많이 변화하여 이런 식으로 쓰는게 개인적으로 더 가치가 있는 것 같습니다.
물론 그이후로도 바다 위 난파선 탄 상태는 여전하며 매일 고민에 고민을 하는 상태이지만 Box2D 매뉴얼을 보고 개인 프로젝트에 접목해보곤 하는게 너무 즐거워 번역이 끝난 지금도 손이 간질간질한 상태가 계속 되고 있다는게 일상의 변화가 아닌가 싶습니다. Box2D 매뉴얼은 그냥 일반적인 물리엔진 적용법 내지는 물리엔진의 기능 소개 정도로 생각하시고 일독하시면 아마 프로젝트의 물리엔진이 달리 보이지 않을까 싶습니다. 그럼 이만 줄이겠습니다.

Disable Back Swipe Gesture in UINavigationController above iOS7

iOS7 이상부터는 UINavigationController 에서 안쪽으로 들어갔다가 돌아올 때 굳이 상단의 뒤로 버튼을 누르지 않더라도 돌아올 수 있는 제스쳐가 생겼다. 좌측 테두리서부터 우측까지 주욱 밀면 이동되는데 스와이프 기능을 쓰는 페이지가 나올 경우, 문제가 생길 수 있다. 해서 아예 꺼버리는 방법을 올려본다.

UINavigationController 가 사용되는 최초 VC 에서 선언하길 추천한다. 중간에만 껐다 켰다 하는 식은 작동을 안하는 듯 함.  iOS8에서는 viewDidAppear: 에서 enable 이나 delegate 에 세팅을 해도 작동을 하지 않는 문제가 있어 viewWillLayoutSubviews 를 사용하였다. 찜찜할 경우, 버전으로 체크하는 조건식을 추가하시길 바람.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Disable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    // Disable iOS 8 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    // Enable iOS 7 back gesture
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return NO;
}

Ignoring xcodeproj-0.17.0 because its extensions are not built

들어가며

cocoapods 를 한번 만들어 봐야겠다는 생각에 pod lib create 하는 순간 제목의 에러가 나며 cocoapods가 작동하질 않았다.

자세한 전문은 다음과 같다.

Ignoring xcodeproj-0.17.0 because its extensions are not built. Try: gem pristine xcodeproj –version 0.17.0
/Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’: cannot load such file — xcodeproj/prebuilt/universal.x86_64-darwin14-2.0.0/xcodeproj_ext (LoadError)
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj/ext.rb:6:in `rescue in <top (required)>’
from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj/ext.rb:3:in `<top (required)>’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj.rb:30:in `<top (required)>’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/lib/cocoapods.rb:2:in `<top (required)>’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:54:in `require’
from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/bin/pod:32:in `<top (required)>’
from /usr/bin/pod:23:in `load’
from /usr/bin/pod:23:in `<main>’

cocoapods?

맥용 라이브러리 관리툴이라고 보면 될 것 같다. 자바쪽에서 쓰는 gradle 이나 maven 같은 디펜더시 관리형 라이브러리 툴이 Xcode 에는 존재하질 않는지라 등장했던 개념인데 나름 컬트적인 인기를 얻어 많은 개발자들이 사용하고 있다.
배포는 루비 젬 형태로 되고 있으므로 사용하려는 경우 루비 사용을 위한 환경이 구축되어 있어야 한다.

문제의 원인

찾아보니 Xcode6 과 OSX 10.10 업데이트와 함께 생긴 일이라고 한다. 다음과 같이 시도해보자.

  1. Xcode6 열기
  2. Preference > Location > 하단의 Command Line Tools 를 현재 버전에 맞게 설정함. (없으면 다운로드)
  3. cocoapods 젬 삭제
    $ sudo gem uninstall cocoapods
  4. xcodeproj 젬 삭제
    $ sudo gem uninstall xcodeproj
  5. 다시 인스톨
    $ sudo gem install xcodeproj
    $ sudo gem install cocoapods
  6. pod update 후 pod –version 으로 확인.

이래도 해결이 안될 수 있다. 내가 그랬다. 진짜 문제의 원인은 다른데 있다. 그렇다고 저 에러메시지에 있는 Try : gem pristine xcodeproj –version 0.17.0 도 해결책이 아니며, 디펜던시가 있다는 0.17.0 버전을 깐다고 해결되는 것도 아니었다.

rvm or rbenv

rbenv 를 쓰고 있다면 저 방법으로 해결이 안된다. rbenv 로 된 PATH 설정을 제거하고 다음을 실행해보자.

  1. rvm stable 버전으로 설치
    $ curl -L https://get.rvm.io | bash -s stable –ruby
  2. rvm 사용하겠다고 선언.
    $ source /Users/<your_user_name>/.rvm/scripts/rvm
  3. $ rvm reload
  4. $ rvm rubygems latest –force

이렇게 하고서 상단의 젬 재설치를 따라해 보자. 레일즈 하느라 깔았던 rbenv 가 이렇게 부메랑으로 돌아올 줄 몰랐다.