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인데 너무 오해하기 쉽게 만들어놨다는 점에서 만든분 나빠.