Why is self unresolved in Playgrounds?
import Foundation
func startLoad() {
let url = URL(string: "https://www.apple.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
if let mimeType = httpResponse.mimeType, mimeType == "text/html",
let data = data,
let string = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
self.webView.loadHTMLString(string, baseURL: url)
}
}
}
task.resume()
}
startLoad()
Post
Replies
Boosts
Views
Activity
Is there such a setting?
Thank you
I'm trying to observe an audio element on a webpage. How do I best keep it's "JS" data in sync with SwiftUI?
Here's the start of my view:
struct ContentView: View {
// @State private var webAudioElementFound = false
@State private var paused = true
@State private var currentTime = 0.0
@State private var duration = 0.0
private let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
@StateObject private var webViewStore: WebViewStore
private let userContentController = WKUserContentController()
private let configuration = WKWebViewConfiguration()
private let userScript = WKUserScript(
source: """
function elementReady(selector) {
return new Promise((resolve, reject) => {
let el = document.querySelector(selector)
if (el) {
resolve(el)
}
new MutationObserver((mutationRecords, observer) => {
Array.from(document.querySelectorAll(selector)).forEach((element) => {
resolve(element)
observer.disconnect()
})
}).observe(document.documentElement, {
childList: true,
subtree: true,
})
})
}
window.webAudioPromise = elementReady("audio").then((webAudioElement) => {
window.webAudioElement = webAudioElement
})
""",
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
in: .defaultClient
)
init() {
userContentController.addUserScript(userScript)
configuration.userContentController = userContentController
configuration.mediaTypesRequiringUserActionForPlayback = []
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.customUserAgent = "Mozilla"
_webViewStore = StateObject(wrappedValue: WebViewStore(webView: webView))
}
the body:
WebView(webView: webViewStore.webView)
.onReceive(timer, perform: webViewTimer)
and a the webViewTimer method:
func webViewTimer(time: Date) {
checkPlayback()
if !webViewStore.isLoading && !paused {
webViewStore.webView.evaluateJavaScript("[webAudioElement.duration, webAudioElement.currentTime]",
in: nil, in: .defaultClient,
completionHandler: { result in
switch result {
case .success(let value):
guard let array = value as? [Double] else { return }
duration = array[0]
currentTime = array[1]
case .failure(let failure):
print("timer failure: \(failure.localizedDescription)")
break
}
})
}
}
As you can see, I'm using Timer, but surely there must be a better way no?
SwiftUI question: (How) Can the myClass instance in this View interact with a State variable?
Not working:
struct MyView: View {
@State private var hello = "world"
let myClass = MyClass()
class MyClass: NSObject, WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "hello"{
hello = message.body
}
}
// ...
}
by the way, I’m adding myClass to a webview’s configuration.userContentController. The userContentController in the class gets called when javascript calls window.webkit.messageHandlers.hello.postMessage("new world")
Thank you kindly
In the following code, I'm loading HTML pages into a WKWebView, only to get the contents of their first H1 tag. Other than what *any type of page* renders in its first H1 tag, I don't need anything else...
Is there perhaps a more efficient/performant way besides initializing a WKWebView for this?
(the .frame(width: 0, height: 0) modifier annoys me a bit, as well)
swift
import SwiftUI
import WebKit
import WebView /* add this Swift Package Dependency https://github.com/kylehickinson/SwiftUI-WebView */
struct ContentView: View {
@StateObject private var viewModel: ContentViewModel
init() {
let viewModel = ContentViewModel()
_viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
VStack(spacing: 30) {
Button("load plain H1 tag") {
viewModel.webViewStore.webView.loadHTMLString("h1H1/h1", baseURL: nil)
}
Button("load body where h1 is appended by JavaScript") {
viewModel.webViewStore.webView.loadHTMLString("body/bodyscript(function(){ let h1 = document.createElement('h1'); h1.textContent = 'H1 JavaScript'; document.body.appendChild(h1); })()/script", baseURL: nil)
}
Button("load apple.com/iphone") {
viewModel.webViewStore.webView.load(URLRequest(url: URL(string: "https://apple.com/iphone")!))
}
Button("load hackingwithswift.com") {
viewModel.webViewStore.webView.load(URLRequest(url: URL(string: "https://hackingwithswift.com")!))
}
Group {
Text("the first H1 element on ") Text(viewModel.webViewStore.webView.url?.description ?? "...").foregroundColor(.accentColor)
+ Text(" is")
}
.font(.subheadline)
Text(viewModel.h1)
.font(.largeTitle)
.foregroundColor(.green)
WebView(webView: viewModel.webViewStore.webView)
.frame(width: 0, height: 0)
}
}
}
class ContentViewModel: NSObject, WKScriptMessageHandler, ObservableObject {
@Published var h1 = "..."
@Published var webViewStore: WebViewStore
override init() {
let userContentController = WKUserContentController()
let configuration = WKWebViewConfiguration()
let userScript = WKUserScript(
source: """
function elementReady(selector) {
return new Promise((resolve, reject) = {
let el = document.querySelector(selector)
if (el) {
resolve(el)
}
new MutationObserver((mutationRecords, observer) = {
Array.from(document.querySelectorAll(selector)).forEach((element) = {
resolve(element)
observer.disconnect()
})
}).observe(document.documentElement, {
childList: true,
subtree: true,
})
})
}
elementReady("h1").then((titleElement) = {
window.webkit.messageHandlers.h1.postMessage(titleElement.textContent)
})
""",
injectionTime: .atDocumentStart,
forMainFrameOnly: false,
in: .defaultClient
)
userContentController.addUserScript(userScript)
configuration.userContentController = userContentController
let webView = WKWebView(frame: .zero, configuration: configuration)
webViewStore = WebViewStore(webView: webView)
super.init()
userContentController.add(self, contentWorld: .defaultClient, name: "h1")
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == "h1" {
h1 = message.body as! String
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In Playgrounds on iPadOS, what’s the difference between these 2 types of playgrounds?
For the life of me I cannot find any documentation about this.
I've updated Xcode to 13.2 and suddenly the code completion that used to turn numberOfRowsInSection into
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//...
}
...inside a UITableViewController is no longer working.
Anyone else experiencing this?
Do I really have to use Bundle to use resources? Or can it be done easier?
I have this JSON file resource in my Playground and am wondering about the least amount of code necessary to load its data into a dictionary.
I’m currently using this Bundle extension:
extension Bundle {
func decode(_ file: String) -> [String: MyType] {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) form bundle.")
}
let decoder = JSONDecoder()
guard let loaded = try? decoder.decode([String: MyType].self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
This type:
struct MyType: Codable, Identifiable {
let id: String
let name: String
let description: String
}
Using this to load it into a struct:
let myData = Bundle.main.decode("MyData.json")
There must be an easier way to access resources in Playgrounds… like image assets for example, it’s just Image(“My Image File Name Here”) and done…
It happens a lot that code evaluation in Playgrounds (the labels that say you have compile bugs or warnings in your code) gets stuck. So in order to fix them I sometimes must force quit the whole Playgrounds app. Sometimes I even have to completely restart iPadOS.
I know triggering Build in Xcode usually fixes those problems in Xcode... so I wonder: is there, or, why isn’t there… a Playgrounds alternative to Xcode’s Build command?
(Using Playgrounds 4.0.2 on iPadOS 15.4.1 running on the M1 iPad Pro)