From a48aebc78cbe4ea300f8c69efa9331af44b632d8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 14 Dec 2025 05:14:26 +0000 Subject: [PATCH] iOS: Fix canvas touch events and auto-hide status bubble - Disable scroll on WKWebView to allow touch events to reach canvas - Add WKNavigationDelegate to intercept clawdis:// deep links from canvas - Wire up onDeepLink callback to handle taps on canvas buttons - Auto-hide status bubble after 3 seconds --- apps/ios/Sources/Model/NodeAppModel.swift | 8 ++++ .../ios/Sources/Screen/ScreenController.swift | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/apps/ios/Sources/Model/NodeAppModel.swift b/apps/ios/Sources/Model/NodeAppModel.swift index c6735013d..718f25e0c 100644 --- a/apps/ios/Sources/Model/NodeAppModel.swift +++ b/apps/ios/Sources/Model/NodeAppModel.swift @@ -35,6 +35,14 @@ final class NodeAppModel { let enabled = UserDefaults.standard.bool(forKey: "voiceWake.enabled") self.voiceWake.setEnabled(enabled) + + // Wire up deep links from canvas taps + self.screen.onDeepLink = { [weak self] url in + guard let self else { return } + Task { @MainActor in + await self.handleDeepLink(url: url) + } + } } func setScenePhase(_ phase: ScenePhase) { diff --git a/apps/ios/Sources/Screen/ScreenController.swift b/apps/ios/Sources/Screen/ScreenController.swift index 6d53ec338..509a9e37f 100644 --- a/apps/ios/Sources/Screen/ScreenController.swift +++ b/apps/ios/Sources/Screen/ScreenController.swift @@ -7,14 +7,19 @@ import WebKit @Observable final class ScreenController { let webView: WKWebView + private let navigationDelegate: ScreenNavigationDelegate var mode: ClawdisScreenMode = .canvas var urlString: String = "" var errorText: String? + + /// Callback invoked when a clawdis:// deep link is tapped in the canvas + var onDeepLink: ((URL) -> Void)? init() { let config = WKWebViewConfiguration() config.websiteDataStore = .nonPersistent() + self.navigationDelegate = ScreenNavigationDelegate() self.webView = WKWebView(frame: .zero, configuration: config) self.webView.isOpaque = false self.webView.backgroundColor = .clear @@ -23,6 +28,11 @@ final class ScreenController { self.webView.scrollView.contentInset = .zero self.webView.scrollView.scrollIndicatorInsets = .zero self.webView.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + // Disable scroll to allow touch events to pass through to canvas + self.webView.scrollView.isScrollEnabled = false + self.webView.scrollView.bounces = false + self.webView.navigationDelegate = self.navigationDelegate + self.navigationDelegate.controller = self self.reload() } @@ -195,6 +205,11 @@ final class ScreenController { statusEl.style.display = 'grid'; if (titleEl && typeof title === 'string') titleEl.textContent = title; if (subtitleEl && typeof subtitle === 'string') subtitleEl.textContent = subtitle; + // Auto-hide after 3 seconds + clearTimeout(window.__statusTimeout); + window.__statusTimeout = setTimeout(() => { + statusEl.style.display = 'none'; + }, 3000); } }; })(); @@ -203,3 +218,32 @@ final class ScreenController { """ } + +// MARK: - Navigation Delegate + +/// Handles navigation policy to intercept clawdis:// deep links from canvas +private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate { + weak var controller: ScreenController? + + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void + ) { + guard let url = navigationAction.request.url else { + decisionHandler(.allow) + return + } + + // Intercept clawdis:// deep links + if url.scheme == "clawdis" { + decisionHandler(.cancel) + Task { @MainActor in + self.controller?.onDeepLink?(url) + } + return + } + + decisionHandler(.allow) + } +}