-
-
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.☺️