画面遷移はViewControllerからじゃないとできない？


# 前提・実現したいこと

Swift3.0でTableViewのセルをタップして **別のStoryboardにあるViewController** に画面遷移させたいのですが，  
画面遷移させる以下のメソッド

[code]

        present(viewController, animated: true, completion: nil)
    
[/code]

がTableViewクラスで実装できません．  
このような場合はTableViewControllerを使うしか方法はないのでしょうか？  
ご回答いただけると助かります。

# 発生している問題・エラーメッセージ

[code]

        Use of unresolved identifier 'present'
    
[/code]

# 該当のソースコード

[code]

        /*
         * FolderTableView.swift
         */
        import UIKit
    
        class FolderTableView: NSObject, UITableViewDelegate, UITableViewDataSource {
    
            // 途中省略
    
            func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
                let storyboard: UIStoryboard = UIStoryboard(name: "Task", bundle: nil)
    
                let taskListViewController =  storyboard.instantiateViewController(withIdentifier: "Task")
    
                self.present(taskListViewController, animated: true, completion: nil)
                 ↑このメソッドで画面遷移させたい
            }
    
        }
    
        /*
         * FolderListViewController.swift
         */
        import UIKit
        import RealmSwift
    
        class FolderListViewController: UIViewController {
    
            @IBOutlet weak var folderTableView: UITableView!
    
            let folderTable = FolderTableView()
    
            override func viewDidLoad() {
                super.viewDidLoad()
    
                folderTableView.delegate = folderTable
                folderTableView.dataSource = folderTable
    
            }
    
            // 省略    
    
        }
    
[/code]

# 試したこと

・TableViewを呼び出しているFolderListViewController内でpresentメソッドを実装  
→セルをタップした時にfolderTableView.delegate = folderTableの行で以下のエラーが発生

[code]

    fatal error: unexpectedly found nil while unwrapping an Optional value
    
[/code]

・presentメソッド用のViewControllerクラスを作成し，FolderTableViewでメソッドを呼び出す  
→セルをタップした時に以下のエラーが発生

[code]

    Warning: Attempt to present <skillup7.TaskListViewController: 0x7f98eb423650> on <skillup7.TestViewController: 0x7f98eb522230> whose view is not in the window hierarchy!
    
[/code]

↓↓用意したViewControllerクラス↓↓

[code]

    /*
     * TestViewController.swift
     */
    import UIKit
    
    class TestViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func send(viewController: UIViewController) {
            self.present(viewController, animated: true, completion: nil)
        }
    
    }
    
[/code]

# 補足情報(言語/FW/ツール等のバージョンなど)

言語：Swift 3.0




**_画面遷移はViewControllerからじゃないとできない？_**  
→Yes

**_このような場合はTableViewControllerを使うしか方法はないのでしょうか？_**  
(`UITableViewController`の意味なら)→No  
(`UITableView`を表示しているViewControllerの意味なら)→Yes

**_・TableViewを呼び出しているFolderListViewController内でpresentメソッドを実装_**

現在提示されているコードには、 **FolderListViewController内でpresentメソッドを実装**
している部分がないのでなんとも言えません。

**・presentメソッド用のViewControllerクラスを作成し，FolderTableViewでメソッドを呼び出す**

これは必ず失敗します。`present`メソッドを呼び出すViewControllerクラスのインスタンスは、必ず「現在画面表示を担当しているもの」でないといけません。たとえ同一クラスであっても、画面表示を担当しているインスタンスとは別のインスタンスを作成してしまうと失敗します。

* * *

上記のように試したことの中身にはよくわからない点もあるのですが、要は`present`メソッドは「現在画面表示を担当しているViewControllerインスタンス」から呼ばないといけないので、そのためのプロパティを`FolderTableView`に付け加えると言うのが手っ取り早いでしょう。

なお、コメントに示したようにViewではないクラスに`...View`と言うクラス名をつけておくのはあまりにも気持ち悪いので、以下のコード例では勝手に`FolderTable`にリネームしてあります。

[code]

    /*
     * FolderTable.swift
     */
    import UIKit
    
    class FolderTable: NSObject, UITableViewDelegate, UITableViewDataSource {
    
        ///現在画面表示を担当しているViewControllerインスタンスを保持しておくプロパティ
        weak var owner: UIViewController?
    
        // 途中省略
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            let storyboard: UIStoryboard = UIStoryboard(name: "Task", bundle: nil)
    
            let taskListViewController =  storyboard.instantiateViewController(withIdentifier: "Task")
    
            //現在画面表示を担当しているViewControllerインスタンスに対して`present(_:animated:completion:)`メソッドを呼ぶ
            owner?.present(taskListViewController, animated: true, completion: nil)
        }
    }
    
    /*
     * FolderListViewController.swift
     */
    import UIKit
    import RealmSwift
    
    class FolderListViewController: UIViewController {
    
        @IBOutlet weak var folderTableView: UITableView!
    
        let folderTable = FolderTable()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            //`folderTable`に「現在画面表示を担当しているViewControllerインスタンス」(=== self)を設定しておく
            folderTable.owner = self
    
            folderTableView.delegate = folderTable
            folderTableView.dataSource = folderTable
    
        }
    
        // 省略
    
    }
    
[/code]

* * *

私的には、そんなに苦労してまで`UITableViewDelegate`,
`UITableViewDataSource`を別クラスに実装するのは、メリットとデメリットを比べてデメリットの方がはるかに多いように思われます。iOSプログラミングでは、ViewControllerが肥大しがちなので、それを避けたかったのかもしれませんが、Swiftではextensionが非常に使いやすくなっていますから、その方がはるかにコードはスッキリすると思うのですが。

[code]

    class FolderListViewController: UIViewController {
    
        @IBOutlet weak var folderTableView: UITableView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            folderTableView.delegate = self
            folderTableView.dataSource = self
    
        }
    
        // 省略
    
    }
    
    extension FolderListViewController: UITableViewDelegate, UITableViewDataSource {
    
        // 途中省略
    
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
            let storyboard: UIStoryboard = UIStoryboard(name: "Task", bundle: nil)
    
            let taskListViewController =  storyboard.instantiateViewController(withIdentifier: "Task")
    
            self.present(taskListViewController, animated: true, completion: nil)
        }
    
    }
    
[/code]

extensionにはプロパティなどは宣言できませんが、`UITableViewDelegate`,
`UITableViewDataSource`を実装しやすく構成したモデルクラスをしっかり設計して別クラスにすることで、大きな問題にはならないだろうと思います。

