SwiftUIのビューをUIViewControllerに配置する

Xcode 11.2.1が最新版です。ちょっと思い立って、SwiftUIの画面ショットが必要になり、おなじみのAppleのチュートリアルを開いてみました。あれ?すでにこの通りにならない。半年くらい前に動きの悪いXcodeで必死に追っかけたチュートリアルですが、数ヶ月でその通りでなくなっています。手順通り、プロジェクトを作成する時にiOSのSingle View Appを選択すれば、その次に「Swift UI」のチェックボックスがあるはずなのですが、なくなっています。

快技庵 高橋政明@houheiさんに指摘いただきましたが、User Interfaceのところで、Storyboardではなく、SwiftUIを選択すると、以前のSwiftUIのチェックボックスを選択したのと同じ状態のプロジェクトが作れます。(2019-12-8 21:20)

仕方ないので、そのまま進みます。作ったプロジェクトにはSwiftUIのファイルはありません。この先、SwiftUIのビューを定義するにはどうするかと言うと、FileメニューのNewからFileを選択するなどして、ファイルの追加を行い、SwiftUI Viewの項目を選択して新たにファイルを作ります。この方法でファイルを開いた時、右側にプレビューのCanvasが出ないなら、EditorメニューのCanvasのチェックが入っているかを確認してください。

これで、SwiftUIのビューのコードファイルが用意されているので、作り込んでいくことはできます。なお、現状のXcodeでも、Target Device(プロジェクトアイコンを選択し、アプリケーションを選択して、Generalを選んだところで指定できる)にMacを選択したら、プレビューは動かなくなります。残念!

さて、本題は、こうして作ったアプリケーションを実際にRunさせても真っ白な画面しか出てこないです。つまり、SwiftUIはXcode上のキャンバスでないと動かないわけです。ちなみに、Appleのチュートリアルで作ったものは、ちゃんとRunしても画面に出てくるようになっているのですが、このプロジェクトは、Storyboardは使っていません。SceneDelegateクラスの最初の方に、UIWindowを生成し、UIHostingControllerを生成して、SwiftUIのViewをルートにしています。今後もしかしたら、この手法がメジャーになるのかも知れませんが、今はやはりStoryboardを中心にして作りたいと思いますよね(色々な意見はあるとは思いますが…)。

上記のチュートリアルのコードを見る限りは、UIViewControllerを継承したUIHostingControllerを使えばなんとかなりそうです。あれこれやってみて、こうすれば動くことがわかりました。元々使われているビューコントローラのViewControllerクラスをこのように書き直します。SwiftUIのビューは、既定の名前であるSwiftUIViewのままにしてあります。

import SwiftUI

class ViewController: UIHostingController<SwiftUIView> {

    required init?(coder: NSCoder) {
        super.init(coder: coder, rootView: SwiftUIView())
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
}

要するにUIHostingControllerを継承しますが、このクラスにはジェネリックが記述されているの、そこにはViewを記述します。しかし、Viewはプロトコルなので、生成可能なクラスか構造体の名前を書く必要があります。ここでは、SwiftUIViewをそのままビューコントローラーのルートにするとして、そのクラス名を記載しました。

これだけではダメで、UIHostingController関するAppleのドキュメントを睨みます。ここで、init?(coder:rootView:)をオーバーライドしてうまくいくかなとあれこれいじっていたら、Xcodeのメッセージで「init?(coder:)を実装して、そこでスーパークラスのinit?(coder:rootView:)を呼び出しなさい」というのに出会いめでたく上記のようなコードにたどり着きました。もちろん、この初期化メソッドはStoryboardの定義に従って生成した時に初期化のために自動的に呼び出されるものです。StoryboardでViewControllerをインスタンス化する時に呼び出すコードの中で、ルートビューのインスタンスを作ります。これで、Runしたアプリケーションで、SwiftUIのビューが動くようになりました。

Storyboardは最初の状態から特に変更はしていません。View Controllerの直下のViewに対するCustom Classの設定でSwiftUIViewクラスを選択できるようになっていれば、上記のようなことは不要なように思うのですが、まだ、Xcodeはそこまで開発は進んでない模様です。SwiftUIのViewは選択できませんし、無理にキータイプして入れてもエラーになってビルドができません。もちろん、Canvasに見えるプレビューは、Storyboardの中では見えません。もうちょい先、つまり、SwiftUIのプレビューをmacOSをターゲットに入れても動くようになってからなのかなと思ったり。

Storyboardの編集機能をいじっていたら、Hositing View Controllerと言うのがあるのに気付きました。しかしながら、メッセージを見ると、その中にいれるSwiftUI Viewについては、プログラムで記述しろとなっているので、このページに書いてある方法でSwiftUIのViewを追加するのが、Xcode 11での方法になります。やはり、Storyboard上ではまだ全ての作業はできないと言うことです。(2019-12-15 10:05)