-
-
Save swiftui-lab/a873bf413770db6fd1a525fa424ce8cd to your computer and use it in GitHub Desktop.
| import SwiftUI | |
| import WebKit | |
| import Combine | |
| class WebViewData: ObservableObject { | |
| @Published var loading: Bool = false | |
| @Published var scrollPercent: Float = 0 | |
| @Published var url: URL? = nil | |
| @Published var urlBar: String = "https://nasa.gov" | |
| var scrollOnLoad: Float? = nil | |
| } | |
| #if os(macOS) | |
| struct WebView: NSViewRepresentable { | |
| @ObservedObject var data: WebViewData | |
| func makeNSView(context: Context) -> WKWebView { | |
| return context.coordinator.webView | |
| } | |
| func updateNSView(_ nsView: WKWebView, context: Context) { | |
| guard context.coordinator.loadedUrl != data.url else { return } | |
| context.coordinator.loadedUrl = data.url | |
| if let url = data.url { | |
| DispatchQueue.main.async { | |
| let request = URLRequest(url: url) | |
| nsView.load(request) | |
| } | |
| } | |
| context.coordinator.data.url = data.url | |
| } | |
| func makeCoordinator() -> WebViewCoordinator { | |
| return WebViewCoordinator(data: data) | |
| } | |
| } | |
| #else | |
| struct WebView: UIViewRepresentable { | |
| @ObservedObject var data: WebViewData | |
| func makeUIView(context: Context) -> WKWebView { | |
| return context.coordinator.webView | |
| } | |
| func updateUIView(_ uiView: WKWebView, context: Context) { | |
| guard context.coordinator.loadedUrl != data.url else { return } | |
| context.coordinator.loadedUrl = data.url | |
| if let url = data.url { | |
| DispatchQueue.main.async { | |
| let request = URLRequest(url: url) | |
| uiView.load(request) | |
| } | |
| } | |
| context.coordinator.data.url = data.url | |
| } | |
| func makeCoordinator() -> WebViewCoordinator { | |
| return WebViewCoordinator(data: data) | |
| } | |
| } | |
| #endif | |
| class WebViewCoordinator: NSObject, WKNavigationDelegate { | |
| @ObservedObject var data: WebViewData | |
| var webView: WKWebView = WKWebView() | |
| var loadedUrl: URL? = nil | |
| init(data: WebViewData) { | |
| self.data = data | |
| super.init() | |
| self.setupScripts() | |
| webView.navigationDelegate = self | |
| } | |
| func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | |
| DispatchQueue.main.async { | |
| if let scrollOnLoad = self.data.scrollOnLoad { | |
| self.scrollTo(scrollOnLoad) | |
| self.data.scrollOnLoad = nil | |
| } | |
| self.data.loading = false | |
| if let urlstr = webView.url?.absoluteString { | |
| self.data.urlBar = urlstr | |
| } | |
| } | |
| } | |
| func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { | |
| DispatchQueue.main.async { self.data.loading = true } | |
| } | |
| func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { | |
| showError(title: "Navigation Error", message: error.localizedDescription) | |
| DispatchQueue.main.async { self.data.loading = false } | |
| } | |
| func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { | |
| showError(title: "Loading Error", message: error.localizedDescription) | |
| DispatchQueue.main.async { self.data.loading = false } | |
| } | |
| func scrollTo(_ percent: Float) { | |
| let js = "scrollToPercent(\(percent))" | |
| webView.evaluateJavaScript(js) | |
| } | |
| func setupScripts() { | |
| let monitor = WKUserScript(source: ScrollMonitorScript.monitorScript, | |
| injectionTime: .atDocumentEnd, | |
| forMainFrameOnly: true) | |
| let scrollTo = WKUserScript(source: ScrollMonitorScript.scrollTo, | |
| injectionTime: .atDocumentEnd, | |
| forMainFrameOnly: true) | |
| webView.configuration.userContentController.addUserScript(monitor) | |
| webView.configuration.userContentController.addUserScript(scrollTo) | |
| let msgHandler = ScrollMonitorScript { percent in | |
| DispatchQueue.main.async { | |
| self.data.scrollPercent = percent | |
| } | |
| } | |
| webView.configuration.userContentController.add(msgHandler, contentWorld: .page, name: "notifyScroll") | |
| } | |
| func showError(title: String, message: String) { | |
| #if os(macOS) | |
| let alert: NSAlert = NSAlert() | |
| alert.messageText = title | |
| alert.informativeText = message | |
| alert.alertStyle = .warning | |
| alert.runModal() | |
| #else | |
| print("\(title): \(message)") | |
| #endif | |
| } | |
| } | |
| class ScrollMonitorScript: NSObject, WKScriptMessageHandler { | |
| let callback: (Float) -> () | |
| static var monitorScript: String { | |
| return """ | |
| let last_known_scroll_position = 0; | |
| let ticking = false; | |
| function getScrollPercent() { | |
| var docu = document.documentElement; | |
| let t = docu.scrollTop; | |
| let h = docu.scrollHeight; | |
| let ch = docu.clientHeight | |
| return (t / (h - ch)) * 100; | |
| } | |
| window.addEventListener('scroll', function(e) { | |
| window.webkit.messageHandlers.notifyScroll.postMessage(getScrollPercent()); | |
| }); | |
| """ | |
| } | |
| static var scrollTo: String { | |
| return """ | |
| function scrollToPercent(pct) { | |
| var docu = document.documentElement; | |
| let h = docu.scrollHeight; | |
| let ch = docu.clientHeight | |
| let t = (pct * (h - ch)) / 100; | |
| window.scrollTo(0, t); | |
| } | |
| """ | |
| } | |
| init(callback: @escaping (Float) -> ()) { | |
| self.callback = callback | |
| } | |
| func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { | |
| if let percent = message.body as? NSNumber { | |
| self.callback(percent.floatValue) | |
| } | |
| } | |
| } | |
@alelordelo Good that you simplified your project. When I open your project, it opens with "iOS" as the build target which I changed to "macOS".
I then moved the p5.js into www and changed the paths in the index.html to load the scripts relative to the HTML:
<script src="./p5.js"></script>
<script src="./sketch.js"></script>After doing that, Xcode complained that it didn’t find the index.html.
I then removed the www folder and once again did Right Click → Add files to … and added the www folder. Then it worked. TBH I don’t understand how these folders are supposed to work but managing them through Finder alone doesn’t seem to be working correctly?
Here’s the result:
Thanks again @getflourish!
I tried about 30 times (not kidding), with all possible configs: adding folders again like you mentioned, clean derived data, clean build folder, restart macOS, etc... Nothing works! 🥵
Would you mind sharing your project?
now worked @getflourish !
awesome, thanks man!
@alelordelo Great! Mind sharing what you’re working on where you want to run p5.js inside a Swift app? Access to native features?
sure, happy to share @getflourish . Do you use slack, or other message app?



thanks @getflourish
But did you get it working with some HTML/JS, or just the Hello World?
I tried, but got this error:
Hello World
File not found
SwiftUIWebP5js/ContentView.swift:18: Fatal error: Unexpectedly found nil while unwrapping an Optional value
2022-02-07 11:51:58.661144+0100 SwiftUIWebP5js[17460:720071] SwiftUIWebP5js/ContentView.swift:18: Fatal error: Unexpectedly found nil while unwrapping an Optional value
(lldb)
Which is weird, because my HTML opens fine when clicked:
https://gyazo.com/d64b5771aa89d5e03bafe068545a0cd3
I pushed updated changes:
https://github.com/alelordelo/SwiftUIWebP5js
And here is the www folder with the HTML/JS
https://drive.google.com/drive/folders/1jkgIW5mSy-Xlxsb2bsfQ2UL1xO8NfddL?usp=sharing
Could totally be the case that I am doing something stupid, as I am zero with anything web/HTML/JS.☺️