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) | |
} | |
} | |
} |
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 Sure: https://github.com/getflourish/SwiftUIWebP5js