2012년 12월 6일 목요일

유용한 Mac OS X 쉘 커맨드

1. 프로그램을 ⌘ + H 로 숨겼을 때 Dock에 투명하게 표시해준다.
defaults write com.apple.Dock showhidden -bool YES && killall Dock

2012년 11월 21일 수요일

viewWillAppear에서 들어가는 중인지 복귀하는 중인지 구분하는 방법

Determine entering or coming back in UIViewController with UINavigationController

UINavigationController를 사용하다 보면 현재 ViewController로 들어올 때 이전 화면에서 들어오는지 다음 화면에서 복귀하는 중인지 구분을 해야 할 때가 있다. 물론 들어갈 때 ViewController를 생성을 하는 경우라면 viewDidLoad에서 들어갈 때의 동작을 하는 방법도 있을 수 있겠지만 이전으로 돌아올 때는 여전히 애매하다.
그래서 그까이꺼 만들어놨겠지 하고 API를 들쑤시다 보면 isMovingFromParentViewController, isMovingToParentViewController를 찾을 수 있는데 이 메소드의 이름이 참으로 오해하기 딱 좋다. 하나는 부모에서 들어올 때, 하나는 부모로 갈 때. 문서를 보면 movingTo는 viewXXXAppear에서, movingFrom은 viewXXXDisappear에서만 제 값을 돌려준다고 되어 있는데 가만 생각해보면 좀 이상하다.
부모에서 들어오는 중이면 현재 viewController가 나타나는 중일텐데 viewXXXDisappear에서만 확인할 수 있다?
부모로 가는 중이면 현재 viewController가 사라지는 중일텐데 viewXXXAppear에서만 확인할 수 있다?
아무리 생각해도 이해가 안된다. API 문서에는 메소드 이름 이상의 별다른 설명이 없다.
그래서 확인해 봤다. 각종 상황에서 뭐가 어떻게 값이 나오는지. 그러자 알기는 옳타쿠나 알기는 커녕 멘붕이 왔다;
그러던 중 WWDC2012 Sessions 동영상을 보았는데 iOS 6으로 오면서 viewController가 확 바뀐 부분에 대해 설명하는 부분에서 viewController가 child로 추가되고 제거될 때 어떤 일이 일어나는지 자세히 나왔다. 그순간 아하!
Parent와 Child의 의미를 잘못 이해했다;;
현재 viewController의 Parent는 이전 viewController가 아니라 UINavigationController였던 것이다!
당연한 걸 뭘 호들갑이냐고? 그렇다면 미안하다;; 난 몰랐다.. 당신의 이해력이 뛰어나서 그런거라고 대인배스럽게 넘어가주자.

isMoving으로 시작하는 두 property에서 말하는 "moving"은 다음화면 이전화면 하는 navigation의 의미가 아니라 한 viewController가 NavigationController와 같은 container view controller로 들어오고 나가는 "moving"이었던 것이다. (부끄럽지만 API 문서에 보면 이렇게 떡하니 써있다. 너무 당연한 말이기 때문에 별 의미가 없다고 생각했다. 뭐 핵심은 container view controller의 의미를 오해한 것이니;)

이것은 현재 viewController의 관점이기 때문에 응용해보면 다음과 같은 4개의 메소드를 만들어 가독성을 높일 수 있다.

// only available in viewWillAppear, viewDidAppear
- (BOOL)isNavigatingFromPrevious
{
    return self.isMovingToParentViewController;
}

// only available in viewWillAppear, viewDidAppear
- (BOOL)isNavigatingFromNext
{
    return (self.isMovingToParentViewController == NO);
}

// only available in viewWillDisappear, viewDidDisappear
- (BOOL)isNavigatingToPrevious
{
    return self.isMovingFromParentViewController;
}

// only available in viewWillDisappear, viewDidDisappear
- (BOOL)isNavigatingToNext
{
    return (self.isMovingFromParentViewController == NO);
}


본인은 이렇게 한번 감싸주지 않으면 머리속에서 번역이 안되더라;;
물론 근본적으로 들어가는 상황인지 나오는 상황인지 따위를 viewWillXXX 메소드에서 궂이 구분하지 않아도 되는 것이 이상적이다. 하지만 세상사 만들다 보면 그런 나이쓰한 구현이 잘 안될 때가 있으므로 그럴 때 요런거 쓰시면 되겠다.

사람은 동시에 생각할 수 있는 정보의 양이 그리 많지 않다. 많다고 해도 한정되어 있으므로 이것을 집중해야 할 부분에 잘 써야 한다. 저런 직관적인 표현으로 바꾸어 놓으면 저 메소드를 사용하는 부분에서 이게 뭔 뜻이지? 하고 생각하는 데에 드는 시간에 원래 하려던 것에 집중할 수 있고 오해도 방지할 수 있다.
성능을 고려하면 안 좋은 방법 아니냐고? 성능이 문제되면 그 때 최적화를 하면 된다. 가독성이 먼저다. 가독성, 기능, 성능을 동시에 고려해서 한방에 짠 하고 나이쓰하게 구현하는 것은 불가능하다. 만약 당신이 가능하다면 연락주시라. 왜냐고? 스승님으로 모시고 싶어서ㅋ

iOS / Mac OS X API들을 보다보면 해당 클래스의 개발자 관점의 네이밍이 좀 있는 것 같다.
그래도 저게 가장 정확한 표현인 것은 맞으니 뭐라고 하기도 애매하고.. 뭐 그렇네ㅋ

2012년 11월 12일 월요일

UINavigationController의 Back Button 이름 바꾸기

How to change NavigationBar Back Button's title

UINavigationController에서 Back Button의 기본 구현은 이전 ViewController의 Title을 이름으로 쓴다. 만들다 보면 이 이름을 바꾸는 경우가 생기는데 이게 좀 거시기하다.
알면 쉬운데 모르면 삽질하기 쉽다.(나도 방금 삽질 좀 했다)
딱 직관적으로 생각해보면
self.navigationItem.backBarButtonItem.title = "Back";
이런거 viewWillAppear 이런데다가 넣어주면 당연히 바뀔 것 같지 않은가?
self.navigationController.navigationItem.backBarButtonItem.title = "Back";
아니면 이런곳?
하지만 아쉽게도 이미 만들어진 Navigation Bar의 Back Button 이름을 바꿀 순 없는 것 같다.(내가 방금 찾아본 바로는.. 있으면 좀 알려주라)
저 navigationItem 이라는 property는 pushViewController 할 때 다음 장면에서 표시할 NavigationBar를 구성하면서 사용하는 것으로 보인다.
즉 push 하기 전에 미리 세팅되어 있어야 한다는 뜻이다.
게다가 심지어 push 되는 ViewController가 아닌 push 하는 ViewController 쪽에다가 설정해야 push 되는 ViewController가 나오는 장면에서 설정한게 보인다.
즉,
parentViewController.navigationItem.backBarButtonItem ...
[navigationController pushViewController:childViewController ...];
이런 식으로 해야 childViewController에서 원했던 Back Button을 볼 수 있단 말이다.

정리하면 이렇다.

FirstNavigationController, SecondNavigationController가 있고 각각의 title이 "First", "Second"라고 하자.
SecondNavigationController에서 Back Button의 title을 "First"가 아닌 "Back"으로 표시되게 하고 싶다.
그러면 First에서 Second를 push하는 부분을 아래와 같이 구현하면 된다.

- (void)onClickSecondButton
{
    self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWith...];
    SecondViewController *second = ...;
    [self.navigationController pushViewController:second animated:YES];
}

넘어갈 때 매번 Button을 생성하란 소리가 아닌 것 쯤은 알거라고 생각한다. 상황에 따라 알아서 하면 된다.
핵심은 push 하기 전에 미리 설정해야 한다는 것이다.

구현하는 입장을 생각해보면 이해가 되는 방식이지만 API인데 너무 오해하기 쉽게 만들어놨다는 점에서 만든분 나빠.

2012년 10월 24일 수요일

빨간약, 파란약.



" 이번이 마지막 기회다.
선택하면 다시 되돌릴 수 없어.
파란약을 먹으면, 여기서 끝난다.
침대에서 깨어나 니가 믿고 싶은 것만 믿고 살게 되지.
빨간약을 먹으면,
이상한나라에 남아 이 토끼굴의 끝을 보게 될거다. "

모피어스를 만나게 해준 고마운 MB.
아직도 매트릭스에 갇힌 수많은 이들이 있다.
12월 19일.
매트릭스를 파괴할 큰 한 걸음을 내딛자.

2012년 10월 4일 목요일

iOS 6에서 Auto Rotate 문제

iOS 6에서 Auto Rotate 동작이 좀 바뀌어서 상속하고 오버라이딩하고 이런 것 좀 추가해줘야 기존 iOS 5에서 실행한 것과 같이 동작한다.
바뀐 개념을 내가 이해를 아직 못해서인가? 직관적이지 않고 애플스럽지 않다.
하위 버전에서 돌아가던 코드를 망가뜨릴 정도로 중요한 변화인건가?
그렇게 중요하면 잘 좀 정리해서 알리던가 아님 쉽게 만들어 놓던가 이게 뭐임;
자세한 내용은 추후 정리하자. 정리가 되긴 되려나;;

 이 내용에 관해 공부를 좀 해보았다.
조사해보니 WWDC 2012 Sessions에서 이것에 대해 설명을 하긴 했다. 제목은 "The Evolution of View Controllers on iOS"
하지만 사람들이 자막도 없는 저 비디오를 다 보는 것도 아니고 기존 어플 이상동작 일으키는 변화 치고는 개발자들한테 알리는데 약간 소홀히 한 면이 있지 않나 싶다.
 내용을 살펴보자.
 이런 큰 변화가 생긴 이유는 최초 UIViewController의 AutoRotate 관련 동작을 잘못 디자인한 데 있다. 기존에는 해당 ViewController의 AutoRotate 동작에 대한 결정을 각 ViewController가 했다. 그런데 가만 보니 이게 문제가 있는거다. Session Video에서 예를 든 것처럼 iPad의 PopOver 같은 ViewController가 스스로의 Orientation을 결정하는 것이 맞는 것인가 하는 생각이 든 것이다. 여기에 대해 Apple이 내린 결론이 현재 윈도우의 최상위 RootViewController나 현재 전체화면으로 보여지고 있는 ViewController가 스스로 결정하는 것이 맞다는 것이다. 더이상 하위 ViewController가 이 결정에 참여하지 않는다. 그래서 기존에 각 ViewController에다 구현해 놓은 메소드가 불려지지 않기 때문에 원하는 방향을 지정해 주려면 어떤 식으로든 코드 변경이 불가피하다. 기존에 방향에 관한 조작을 하려면 반드시 오버라이딩해야 했던 메소드 shouldAutorotateToInterfaceOrientation: 는 무려 deprecated.

 구체적인 코드나 방법은 직접 해보고 찜쪄먹은 뒤에 포스팅해야 겠다.
 처음에는 이런 무례한 변경이 애플스럽지 않다고 생각했는데 다시 생각해 보니 자신들의 기존 생각이 잘못되었다는 것을 알고 나서는 이를 바로 잡기 위해 기존 동작을 틀어버리는 과감(?)한 행보가 더 애플스러운 것 아닌가 하는 생각이 든다. 너무 애플빠스런 발언인가?ㅋ 물론 코드 고쳐야 하는 나로서는 좀 짜증나지만 길게 봐야 하지 않겠는가? 스스로의 판단이 잘못 된 것을 깨달았을 때는 과감히 인정하는것이야말로 대인배잖은가?

 이런 저런 과정을 거치고 나니 WWDC 2012 Sessions를 마저 다 봐야겠다는 생각이 든다.
역시 기본이 없이 막 만든 코드는 모래성이다. 빨리 대충 결과물 나오면 짝짝짝 하는 사람들이 생각난다. 그러지말자.

2012년 9월 24일 월요일

View Based NSTableView 선택 영역 배경색 바꾸는 방법

How to change view Based NSTableView's selection background color

 개인적으로는 Mac OS X 어플은 Alfred와 같은 프로그램처럼 완전히 다른 느낌으로 Graphical하게 만들게 아니라면 어설프게 UI에 그림을 붙여대는 것은 좋지 않다고 본다.
 하지만 역시나 월급받는 현실세계에선 맘에 안들어도 어쩔 수 없는 상황이 있으므로 할건 해줘야지 뭐. 프로그래머가 모르는 디자인의 세계가 있을지도?

 이번에는 NSTableView의 선택영역의 배경색상을 특정 색상으로 지정을 해왔다. 아래 그림에서 파란색 부분을 좀더 연한 색으로 해달란다. 직관적으로 생각해보면 NSTableView에 setSelectionBackgroundColor 따위가 있을 것 같지만 그런것 따위는 없다. 역시 Cocoa Touch와는 다르게 Cocoa는 참 심오(?)하다.
 아래는 NSTableView의 구조을 정말 잘 표현한 Apple 문서의 그림이다. 첨부터 이 그림을 봤으면 오래 안걸렸겠지만 여러 방해공작들과 함정들로 멀고도 먼길을 돌아 이 그림에 당도했다. 중요한건 한가지, NSTableViewRow의 무엇인가를 바꿔주면 된다는 것.


 NSTableView는 View-Based Table View와 Cell-Based Table View로 나뉜다.
 배경을 간단하게 요약하면, 처음에 NSTableView는 각각의 행과 열이 NSCell의 서브클래스로 구성되어 있었는데 프레임웍에서 제공되는 NSCell 종류로 해결되지 않을 때 NSCell 상속해서 서브클래스 만드는 것이 어렵고 복잡했다. 이에 비해 커스텀 뷰를 만드는 것이 더 쉽고 간단하기 때문에 View-Based Table View가 추가된 것이다.
 이런 연유로 두가지가 공존하고 있는데 어느 방식을 사용하고 있냐에 따라서 같은 효과라도 사용방법이 다를 수 있는 약간 깔끔하지 않은 모양새다.
 여기서 다룰 방법은 View-Based Table View의 선택영역 색상을 바꾸는 방법으로 Cell-Based Table View일 경우 NSTableView를 상속하여 highlightSelectionInClipRect:를 Override하는 방법을 사용해야 한다(물론 다른 방법이 있을 지도 모르지만 직접 확인한건 저거다).

 크게 두 가지를 해야 한다. TableRowView 상속과 TableView 델리게이트 구현.
 아래와 같이 상속하여 Override 한다. selected에 바꾸고자 하는 색상을 넣어주면 된다. 포커스가 다른 뷰에 있을 때 색상을 다르게 지정하려면 selectedWithoutFocus에 지정하면 된다. 이것의 Default는 회색이다.

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

@interface TableRowViewCyan : NSTableRowView

@end

TableRowViewCyan.m
#import "TableRowViewCyan.h"

@implementation TableRowViewCyan

- (void)drawSelectionInRect:(NSRect)dirtyRect
{
    NSColor *selected = [NSColor colorWithCalibratedRed:(CGFloat)183/255 green:(CGFloat)215/255 blue:(CGFloat)228/255 alpha:1.0];
    NSColor *selectedWithoutFocus = selected;
   
    if (self.emphasized) {
        [selected set];
    } else {
        [selectedWithoutFocus set];
    }
    [NSBezierPath fillRect:dirtyRect];
}

- (NSBackgroundStyle)interiorBackgroundStyle
{
    // This makes text color dark.
    return NSBackgroundStyleLight;
}

@end

 두번째 오버라이딩한 메소드 interiorBackgroundStyle은 내가 지정한 색상이 밝은 색이기 때문에 글자색이 어두운 색으로 유지되도록 하기 위한 것이다. 안하면 눌렀을 때 글자가 흰색이 되서 잘 안보인다. 해보진 않았지만 NSBackgroundStyleDark 따위로 해주면 반대로 되겠지 뭐.

 다음으로 NSTableView의 델리게이트에서 아래처럼 위에서 상속한 클래스의 인스턴스를 생성하여 리턴하는 메소드를 구현해 준다.

- (NSTableRowView *)tableView:(NSTableView *)tableView rowViewForRow:(NSInteger)row
{
    TableRowViewCyan *rowView = [[TableRowViewCyan alloc] init];
    return rowView;
}

 이렇게 하면 끝.
 상속할 때 drawSelectionInRect:에 저렇게 칠하는 것 말고 다른 것을 해도 되므로 둥그런 버튼 등의 다양한 방식의 커스터마이징도 가능하다.
  안드로이드나 iOS나 역시 끝판왕은 드로잉이구만.

Syntax Highlighting Example

for (int i; i < 5; i++) {
    printf("yeah");
}

2012년 9월 14일 금요일

ScrollView의 길이가 가변일 때 아래에 있는 View(footer)가 잘리거나 안보이는 문제


ScrollView의 부모 View를 LinearLayout, Vertical로 했을 때 단순히 생각하면 그 안에
ImageView(header), ScrollView(contents), ImageView(footer)를 wrap_content로 배치하면 두 ImageView는 보이고 ScrollView가 적절히 조절되어 나올 것 같지만 그렇지 않다.

이는 LinearLayout이 먼저 나오는 뷰의 원하는 사이즈부터 충족시켜 주기 때문인 것으로 보이며 ScrollView는 안의 내용 전체 길이만큼 요구할 것이므로 이후의 ImageView는 LinearLayout 밖으로 밀려나게 된다.

이를 해결하기 위해선 ScrollView에 아래의 속성을 하나 추가하면 된다.
android:layout_weight="1"
layout_weight의 default가 0인데 저것을 1로 해주는 것이 왜 ScrollView의 크기가 적절하게 조절되게 만드는지는 살짝 미스테리(?)하긴 하지만 원인은 시간나면(!) 밝혀 보기로 하고 일단 이렇다는 것만 알아두자.

2012. 1. 12

추가:
오랜 시간이 지나고 댓글을 발견하고 이해가 안가서 좀 찾아봤더니 이런 글이 있었습니다. http://www.soen.kr/book/android/book/book2/3-2-4.htm
간단히 요약하면 0은 사이즈를 나누는 것에 관여하지 않고 자기가 원하는 크기를 가지고 1 이상은 이후 남은 영역을 숫자 비율대로 나눠가진다는 것이네요.
그래서 weight가 0인 스크롤 뷰가 가지고 있는 컨텐츠 전체의 길이만큼 가져가 버려서 이 현상이 생긴것이고 스크롤 뷰의 weight을 1로 하면 0인 이미지뷰들이 먼저 가져가고 남은 영역을 비율대로(여기서는 혼자) 가져가므로 위 아래 이미지뷰가 보일 수 있게 스크롤뷰의 사이즈가 결정되는 것이었습니다.

NSButton 글자 색 바꾸기

How to change NSButton's text color

NSButton은 기본적으로 글자 색 바꾸는 기능이 없다.

Mac OS 자체의 Theme에 따라가고 뻘짓 하지 말라는 Apple의 Guideline인 것은 아닐까 추측된다.

하지만 현실세계에서는 전체 System의 일관된 겉보기 따위는 개나 줘버리고 App 내의 Button의 배경그림과 글자색을 다 바꾸라는 변태스러운 요구가 빈번하기 때문에 어쩔 수 없이 바꿔야 한다.

검색해보면 글자색 바꾸는 것 까지는 찾기 쉬운데 그것을 적용하면 xib에서 지정했던 가운데 정렬이 없어지는 문제가 있었다. (뭐 다른것도 없어지긴 하겠지만 일단 대부분의 Button에서 중요한 이것!)

그래서 요리조리 해보니 setAlignment를 또 따로 해줘야 하더라.

이대로 두면 눌렀을 때 글자색이 원하지 않은 색으로 나울 수 있으므로 그것을 또 따로 지정하려면 setAttributedAlternateTitle: 을 사용하면 된다.

이렇게 알아낸 방법들을 짬뽕하여 아래와 같은 Category를 만들었다.

<NSButton+TextColor.h>
#import <Foundation/Foundation.h>

@interface NSButton (TextColor)

- (void)setTextColor:(NSColor *)color;
- (void)setHighlightedTextColor:(NSColor *)color;
+ (void)button:(NSButton *)button setTextColor:(NSColor *)color;
+ (void)button:(NSButton *)button setHighlightedTextColor:(NSColor *)color;

@end

<NSButton+TextColor.m>
#import "NSButton+TextColor.h"

@implementation NSButton (TextColor)

- (void)setTextColor:(NSColor *)color
{
    [NSButton button:self setTextColor:color];
}

- (void)setHighlightedTextColor:(NSColor *)color
{
    [NSButton button:self setHighlightedTextColor:color];
}

+ (void)button:(NSButton *)button setTextColor:(NSColor *)color
{
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    [style setAlignment:button.alignment];
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                button.font, NSFontAttributeName,
                                color, NSForegroundColorAttributeName,
                                style, NSParagraphStyleAttributeName,
                                nil];
    NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:button.title
                                                                  attributes:attributes];
    [button setAttributedTitle:attrStr];
}

+ (void)button:(NSButton *)button setHighlightedTextColor:(NSColor *)color
{
    NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
    [style setAlignment:button.alignment];
    NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                button.font, NSFontAttributeName,
                                color, NSForegroundColorAttributeName,
                                style, NSParagraphStyleAttributeName,
                                nil];
    NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:button.title
                                                                  attributes:attributes];
    [button setAttributedAlternateTitle:attrStr];
}

@end


난 글자색 하나 바꾸려 했을 뿐;;

2012년 9월 10일 월요일

인생이란..


인생이란 가장 슬픈 날

가장 행복하게 웃는 용기를 배우는것.

- <리버보이>, 팀 보울러
- theprodiqy

어디서 줏어봤다. 그래 맞다. 난 바로 이렇게 살고 싶은거다.

맥 키보드 청소할 때 유용한 툴

키보드 닦을 때 키가 눌려서 원하지 않은 동작이 일어나지 않게 하려고 메모장을 열어 놓고 했던 경험이 누구나 한번쯤 있을 것이다.

바로 요 때 유용한 툴이 있다.

KeyboardCleanTool

맥용이고 무료.

매직마우스와 트랙패드에 관한 맥 기본 환경설정의 부족함을 채워주는 BetterTouchTool을 만든 회사에서 나온 툴이다.

이 회사는 정말 유용한 것들을 만든다.