【Swift】UITabBarControllerでタブ切り替え時に同一の画面を表示する方法

Pocket

tab_0

最近ある事情からCocos2d-xから離れてSwiftをいじったりしてます。
Swiftは最新の言語のひとつだけあって、かなりクールな感じがしますね(フォーマッタとかまだないけど)。

それはさておき、今回UITabBarControllerで「同一画面(インスタンス)をタブで切り替える」必要があったのですが、結構ハマったので備忘録として載せておきます。

やりたいこと

以下のことをしたいのです。

  • UITabViewControllerでタブの切り替えを行う
  • ただ、そのうちの2タブは同一のインスタンスを表示したい

要は、「タブ1」「タブ2」「タブ3」「タブ4」とある場合、タブ1とタブ2だけ同じ画面を表示するのですが、タブ1とタブ2で少しだけ表示の仕方を変えるようなことがしたいんですね。
なんだか簡単にできそうなのですが、無駄にハマってしまいました(´・ω・`)

ちなみに、こんなテストのプロジェクトを作成して検証してます。

tab_0このFirstViewとSecondViewがあって、それぞれRestorationIDを「first」「second」としてます。それで、secondを主に表示させてfirstは表示させないようなことをしてます。

やったことその1:片方を選択不可にする

swiftでUITabBarの特定のタブをタップした時にモーダルなどを参考に、UITabBarControllerDelegateを実装して「tabBarController#shouldSelectViewController」を使ってタブの選択の制御を行う方法を最初考えました。
具体的な説明は他ページに任せるとして、以下のようなコードを作りました。

public class TabController : UITabBarController {
    public override func viewDidLoad() {
        super.viewDidLoad();
        // 最初に表示するタブを決める
        self.selectedIndex = 1;
        // デリゲートを決定
        self.delegate = self;
    }
}

extension TabController : UITabBarControllerDelegate {
    // タブの選択を制御するデリゲートメソッド
    public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
        // タブのコントローラが[first]の場合、選択を取り消す
        if(viewController.restorationIdentifier == "first" &&
            selectedViewController!.restorationIdentifier == "second") {
                // SecondViewの中のラベル名を変えてます
                let label = selectedViewController!.view.viewWithTag(10) as! UILabel;
                label.text = "First View";
                return false;
        }else if(viewController.restorationIdentifier == "second"){
            // SecondViewの中のラベル名を変えてます
            let label = viewController.view.viewWithTag(10) as! UILabel;
            label.text = "Second View";
        }
        return true;
    }
}

以下のようになりました。

tab_1

まあ、そうですよねぇ。だって「選択を取り消し」てるんですから下のタブは選択になりませんよねぇ(´・ω・`)
このあとしばらくタブの選択状態を変える方法を模索したんですが、そういうメソッドはありませんでした。
(もしかしたらあるのかもしれないけど見つけられなかった。。。)

でも、無事にひとつの画面(SecondView)をタブで制御することはできたので、これを応用すれば
なんとかなりそうです。

やったことその2:表示するViewControllerを入れ替える

一応、TabBarControllerは以下のコードを呼び出すことでタブの選択を動的に切り替えることができます。

// タブの左側(=0)からの位置を設定する
self.selectedIndex = 1;

これを行うとどうやっても強制的にタブの切り替えが行われちゃいますが、「たぶん内部的にもインデックスで管理してるんだろうな」
と思ったので、表示する際にViewControllerを並び替えちゃうようにしてみました。

extension TabController : UITabBarControllerDelegate {
    // タブの選択を制御するデリゲートメソッド
    public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
        if(viewController.restorationIdentifier == "first") {
            let controllers = viewControllers;
            // インデックスを得る
            let index = controllers!.indexOf(viewController);
            // SecondViewの表示ラベルを変更する
            if(selectedViewController!.restorationIdentifier == "second") {
                let label = selectedViewController!.view.viewWithTag(10) as! UILabel;
                label.text = index == 0 ? "First View" : "Second View";
                
            }
            // ViewControllerを並び替える。
            self.setViewControllers([controllers![1] , controllers![0], controllers![2]], animated: false);
            return false;
        }
        return true;
    }
}

で、実行してみたのがこれ。

tab_2

・・・えー(´・ω・`)

うまくいきそうに見えたけど、タブの位置までが変わっちゃいました。
すごい親切設計ですね(´・ω・`) ViewControllerを入れ替えると自動でタブボタンの位置まで変わっちゃうみたいです。

やったことその3:ならタブの表示も変えちゃおう

ただ、さっきのは結構おしい気がしますね。

「タブの表示位置が変わるのなら、そのタブの表示も変えちゃえばいいじゃない」

ということで、やってみます。

extension TabController : UITabBarControllerDelegate {
    // タブの選択を制御するデリゲートメソッド
    public func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
        if(viewController.restorationIdentifier == "first") {
            let controllers = viewControllers;
            let index = controllers!.indexOf(viewController);
            if(selectedViewController!.restorationIdentifier == "second") {
                let label = selectedViewController!.view.viewWithTag(10) as! UILabel;
                label.text = index == 0 ? "First View" : "Second View";
                
            }
            self.setViewControllers([controllers![1] , controllers![0], controllers![2]], animated: false);
            self.tabBar.items![1].title = "Second";
            self.tabBar.items![1].selectedImage = UIImage(named: "second");
            self.tabBar.items![1].image = UIImage(named: "second");
            
            self.tabBar.items![0].title = "First";
            self.tabBar.items![0].selectedImage = UIImage(named: "first");
            self.tabBar.items![0].image = UIImage(named: "first");
            return false;
        }

        return true;
    }
}

なにこの汚いコード・・・。
ともあれ、実行してみます。

tab_3

って、できた\(^o^)/

これでいいのかわからないけど

なんというか、再利用性とかくそくらえ的なコードですが、なにはともあれやりたいことはできました。
(実際にはもう少しいろいろと制御しないと実用はできないですが)

こんなの簡単にできそうって思ってたのに、こんなことしないとダメなんですかね。。
もっとスマートにできる方法とかあったらどなたか教えてもらえると嬉しいです(´・ω・`)

一応、詳しいコードをGitHubに乗せておきます。

https://github.com/brbranch/SwiftSample/tree/master/UITabBarSample

【Swift】UITabBarControllerでタブ切り替え時に同一の画面を表示する方法” への2件のコメント

  1. この投稿にコメントではないのですが、できる方と信じて質問させてください。
    2枚のタブを持つアプリで初期は1枚目のみが閲覧でき、2枚目は開けない(有効化されない)。
    1枚目のタブで条件(何でもいいのですが)がそろうと、2枚目のタブが開くことができる(有効される)。
    このような動きを作りたいのですが、アドバイスをもらえますか?

    • そうですねぇ、やったことはないのですが
      UITabBarItemにはenabledというプロパティがあるので、その状態を変更することで実現できそうです。
      ・ 最初は2枚目のUITabBarItem#enabledをfalseにしておく
      ・ NSNotificationCenterに、2枚目のUITabBarItem#enabledをtrueにする処理を登録しておく
      ・ どこかで特定の条件が満たされたら、NSNotificationCenter#postNotificationで上記のメソッドを呼び出す
      でできるんじゃないかなって思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です