yo_waka's blog

418 I'm a teapot

UINavigationControllerのタップ制御がムズい

使い勝手のためにコンテンツの表示領域を広く取れるように、タブバーをスクロール時に閉じて、ナビゲーションバー含む画面領域タップで再表示するようにしたい。
なので、UINavigationControllerをUITapGestureRecognizerでタップ制御しようとしたんだけど、予想外にめんどくさかった。

- (void) setScrollGesture: (UIView *)view
{
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(handleTap:)];
    view.userInteractionEnabled = YES;
    [view addGestureRecognizer: tapGesture];
}

- (void) handleTap: (id)sender
{
    [self showTabBar: YES];
}

// NavigationControllerにジェスチャを設定
[self setScrollGesture: self.navigationController];

こうすると戻るボタンもジェスチャにイベントが奪われて実行されなくなってしまう。
navigationBar.subViewsからUIButtonのみ除いてジェスチャをセットしたいのだけど、UINavigationController内の背景ビュークラス等はなんと非公開クラスのため"isKindOfClass"が使えない。。。

なので強引だけどsubViewのクラス名を文字列でチェックしてジェスチャに設定する。

- (void) viewDidAppear: (BOOL)animated
    NSArray *subViews = self.navigationController.navigationBar.subviews;
    UIView *navigationBgView = nil;

    for (UIView *subView in subViews) {
        if (subViews.count > 1) {
            if ([[[subView class] description].lowercaseString rangeOfString: @"uinavigationitemview"].location != NSNotFound) {
                navigationBgView = subView;
            }
        } else {
            if ([[[subView class] description].lowercaseString rangeOfString: @"uinavigationbarbackground"].location != NSNotFound) {
                navigationBgView = subView;
            }
        }
        if (navigationBgView) {
            break;
        }
    }
    if (navigationBgView) {
        [self setScrollGesture: navigationBgView];
    }
}

これでタイトル部分のみにジェスチャを効かすことができる。
ボタンが複数あるときはタイトル部分しかジェスチャ効かないけど、仕方ないか。
タイトルを表示しない場合はちょっと違うやり方を探したほうがよさそう。
ひょっとして他によいやり方あったりするのかな?


タブバーをアニメーション付きで下からニョキッと出したり隠したりするのはこちらの記事が参考になります
自分の環境でもUIViewControllerのhiddenTabBarにBOOL入れるだけじゃダメだった。

記事では画面の高さが決め打ちになっていてiPhone5に対応していないので、そこはアプリのFrameを使うように変えてあげればいい。

- (void) showTabBar: (BOOL)animated
{
    CGRect bounds = [[UIScreen mainScreen] applicationFrame];
    
    if (animated == YES) {
        [UIView beginAnimations: nil context: NULL];
        [UIView setAnimationDuration: 0.4];
    }
    
    for (UIView *view in self.tabBarController.view.subviews) {
        CGRect rect = view.frame;
        if([view isKindOfClass: [UITabBar class]]) {
            rect.origin.y = bounds.size.height - 29;
            [view setFrame: rect];
        } else {
            rect.size.height = bounds.size.height - 29;
            [view setFrame: rect];
        }
    }
    
    if (animated == YES) {
        [UIView commitAnimations];
    }
    
    self.hiddenTabBar = NO;
}

- (void) hideTabBar: (BOOL)animated
{
    CGRect bounds = [[UIScreen mainScreen] applicationFrame];
    
    if (animated == YES) {
        [UIView beginAnimations: nil context: NULL];
        [UIView setAnimationDuration: 0.4];
    }
    
    for (UIView *view in self.tabBarController.view.subviews) {
        CGRect rect = view.frame;
        if([view isKindOfClass: [UITabBar class]]) {
            rect.origin.y = bounds.size.height + 20;
            [view setFrame: rect];
        } else {
            rect.size.height = bounds.size.height + 20;
            [view setFrame: rect];
        }
    }
    
    if (animated == YES) {
        [UIView commitAnimations];
    }
    
    self.hiddenTabBar = YES;
}