Post

Replies

Boosts

Views

Activity

Use of unresolved identifier ’self’ in Playgrounds for iPad
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()
2
0
1.9k
Jun ’20
How keep JavaScript data in sync with SwiftUI?
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?
0
0
903
Feb ’21
Can a class instance in a View struct interact with the view's State variable?
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
1
0
1.4k
Apr ’21
Using WKWebView to sync data out of any page with SwiftUI - but is there a better approach?
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() } }
0
0
2k
Apr ’21
Xcode 13.2 and autocompletion
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?
4
0
2.8k
Dec ’21
Loading resources
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…
1
0
1.1k
Apr ’22
Refresh Playgrounds 4 code evaluation
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)
1
0
1.1k
Apr ’22