Swift2 - クラスの継承とプロトコルの適合(採用)の両方の条件を満たす条件をジェネリクスで書く
Swift2.2, Xcode7
modal で呼び出す画面は、どの ViewController からも呼び出される可能性がある為、ページ遷移用の Utility に定義する様にしています。その時に困るのが、 UIViewController を継承して、かつ、モーダルを閉じる delegate ( protocol )を採用しているケースです。例として以下のようなケースを考えたいと思います。
- modal を呼び出す ParentViewController 。 delegate で modal を閉じる。
- modal で呼び出される側は ChildViewController
storyboard は ViewController と 1 対 1 で分割している
ParentViewController
protocol ChildViewControllerDelegate: class { func modalDismiss() } class ParentViewController: UIViewController, ChildViewControllerDelegate { ... func modalChild() { // TransitionUtility は ChildViewController を modal で呼び出す処理 TransitionUtility.modalChild(self) } func modalDismiss() { self.dismissViewControllerAnimated(true, completion: nil) }
- ModalViewController
class ModalViewController: UIViewController { weak var delegate: ChildViewControllerDelegate? ... func close() { self.delegate.modalDismiss() } }
このような状況で modal を表示する処理を書こうと思うと UIViewController の継承と EditEmailViewControllerDelegate の Protocol の採用でちょっと困ったことになります。
struct TransitionUtility { static func modal(viewController: UIViewController) { let storyboard = UIStoryboard(name: "Child", bundle: nil) guard let modalViewController = storyboard.instantiateViewControllerWithIdentifier("Child") as? ChildViewController else { return } modalViewController.delegate = viewController // ↑ viewController は ChildViewControllerDelegate を採用していないので delegate に指定できないよと怒られる。 viewController.presentViewController(modalViewController, animated: true, completion: nil) } }
struct TransitionUtility { static func modal(viewController: EditEmailViewControllerDelegate) { let storyboard = UIStoryboard(name: "Child", bundle: nil) guard let modalViewController = storyboard.instantiateViewControllerWithIdentifier("Child") as? ChildViewController else { return } modalViewController.delegate = viewController viewController.presentViewController(modalViewController, animated: true, completion: nil) // ↑ viewController は EditEmailViewControllerDelegate なので UIViewController の持つ presentViewController は利用できない。 } }
このような時に利用できるのがジェネリクスです。ジェネリクスを利用することで UIViewController を継承しており、かつ、ChildViewControllerDelegate を採用しているクラスを引数に定義することができます。実装方法は以下です。
static func modalChild<T where T: UIViewController, T: ChildViewControllerDelegate>(viewController: T) { let storyboard = UIStoryboard(name: "Child", bundle: nil) guard let modalViewController = storyboard.instantiateViewControllerWithIdentifier("Child") as? ChildViewController else { return } modalViewController.delegate = viewController viewController.presentViewController(modalViewController, animated: true, completion: nil) }
これで ChildViewController の modal の呼び出しは TransitionUtility.modalChild(self)
といった形で呼び出せるようになります。