Last active
February 10, 2023 03:12
-
-
Save swiftui-lab/a873bf413770db6fd1a525fa424ce8cd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
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?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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
intowww
and changed the paths in theindex.html
to load the scripts relative to the HTML: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 thewww
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: