turbo-iosでモーダル

October 9, 2021

Turbo Native for iOS には Path Configuration という機能があります。(Androidにもあるようですが触ってないです)

https://github.com/hotwired/turbo-ios/blob/main/Docs/PathConfiguration.md

Turbo NativeのDemoサーバーだとこんな感じのjsonです。

https://github.com/hotwired/turbo-native-demo/blob/main/public/path-configuration.json

これにならってpresentationをmodalにすればOK……というわけではありません。

1
  "presentation": "modal", 

Demoアプリのこの辺を見るとわかるのですが、 turbo-iosがやってくれるのはjsonのparseだけで挙動を変えるのは自前でやる必要があります。

https://github.com/hotwired/turbo-ios/blob/cb6c33b260ee322a2eb32619363ae610777856f1/Demo/SceneController.swift#L85

Quick Start から最低限の変更でやるとしたらこんな感じでしょうか。

SceneDelegate.swift

  • mainとmodalでSessionをわける
  • sharedProcessPoolでCookieなどを共有
  • pathConfigurationを見てmodalなURLならモーダル表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 import UIKit
 import Turbo
+import WebKit
 
 class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+    private static var sharedProcessPool = WKProcessPool()
+
+    private let rootURL = URL(string: "http://localhost:3000")!
+
     var window: UIWindow?
     private lazy var navigationController = UINavigationController()
 
     func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
         guard let _ = (scene as? UIWindowScene) else { return }
         window!.rootViewController = navigationController
-        visit(url: URL(string: "http://localhost:3000")!)
+        visit(url: rootURL)
     }
     
-    private func visit(url: URL) {
-        let viewController = VisitableViewController(url: url)
-        navigationController.pushViewController(viewController, animated: true)
-        session.visit(viewController)
+    private func visit(url: URL, modal: Bool = false) {
+        let viewController = ViewController(url: url)
+        if modal {
+            let modalNavController = UINavigationController(rootViewController: viewController)
+            navigationController.present(modalNavController, animated: true)
+            modalSession.visit(viewController)
+        } else {
+            navigationController.pushViewController(viewController, animated: true)
+            session.visit(viewController)
+        }
     }

-    private lazy var session: Session = {
-        let session = Session()
+    private lazy var session = makeSession()
+    private lazy var modalSession = makeSession()
+
+    private func makeSession() -> Session {
+        let configuration = WKWebViewConfiguration()
+        configuration.processPool = Self.sharedProcessPool
+
+        let session = Session(webViewConfiguration: configuration)
         session.delegate = self
+        session.pathConfiguration = pathConfiguration
         return session
-    }()
+    }
+
+    private lazy var pathConfiguration = PathConfiguration(sources: [
+        .server(rootURL.appendingPathComponent("path-configuration.json"))
+    ])
 }

 extension SceneDelegate: SessionDelegate {
     func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
-        visit(url: proposal.url)
+        visit(url: proposal.url, modal: proposal.properties["presentation"] as? String == "modal")
     }
     
     func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) {

ViewController

モーダルを閉じるボタンを表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 import UIKit
+import Turbo
 
-class ViewController: UIViewController {
+class ViewController: VisitableViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
         // Do any additional setup after loading the view.
-    }
 
+        if presentingViewController != nil {
+            navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissModal))
+        }
+    }
 
+    @objc func dismissModal() {
+        dismiss(animated: true)
+    }
 }

うまくいきました。

意外とSwift書くんだな、という印象です。

まぁでもアプリごとに変えなくてもいいような処理が多そうなので、汎用的なテンプレートが1つあれば、それを少し変えるだけで済む気はします。 (Demoアプリを元に変えてね?ということなのかしら)