Я хочу иметь WKWebView с обработкой JavaScript в SwiftUI. Из Инициализация переменных Swift я делаю следующее: (я использую https://github.com/kylehickinson/SwiftUI-WebView, чтобы предоставить оболочку для WKWebView в SwiftUI, которая также добавляет ценные ограничения макета.)
struct ContentView: View {
var body: some View {
WebView(webView: myWebView)
.onAppear {
let url = Bundle.main.url(forResource: "index", withExtension: "html")!
myWebView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
myWebView.load(request)
}
}
class JSHandler : NSObject, WKScriptMessageHandler {
var contentView: ContentView?
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("documentReady")
contentView!.myWebView.evaluateJavaScript("<Long Script to Transfer Data>") { (result, error) in
}
}
}
let myJSHandler = JSHandler()
var myWebView: WKWebView = {
let config = WKWebViewConfiguration()
let controller = WKUserContentController()
controller.add(myJSHandler , name: "documentReady")
config.userContentController = controller
return WKWebView(frame: .zero, configuration: config)
}()
}
Но потом я узнал от Член экземпляра не может быть использован для типа, что это не работает, потому что замыкание не имеет ссылки на себя. Мне нужно, чтобы WKWebView имел выделенный объект конфигурации, поэтому я не могу просто использовать другой конструктор. Мне нужна ссылка на это, чтобы сделать evaluateJavaScript.
Как заставить это работать?
РЕДАКТИРОВАТЬ 1: Добавьте body и укажите структуру, используемую для переноса WKWebView.
Обновлено еще раз: Добавлен код, чтобы уточнить, что мне нужна двусторонняя связь от WKWebView к собственному приложению через WKScriptMessageHandler (чтобы получать уведомления, когда HTML-документ готов) и от собственного приложения к WKWebView через evaluateJavaScript (для передачи данных после того, как документ HTML готов) .
@Asperi В структуре WebView вы используете var webview: WKWebView = WKWebView(), но мне нужно использовать конструктор с пользовательской конфигурацией. Не столкнусь ли я тогда с той же проблемой? (Я уже использую github.com/kylehickinson/SwiftUI-WebView для переноса WKWebView в UIViewRepresentable.)
Вот альтернатива stackoverflow.com/a/60173992/12299030. Вы можете использовать то, что работает для вас.
@Asperi Спасибо за решение. Я хотел бы отметить, что, поскольку конфигурация была выполнена внутри оболочки, поэтому, если я хочу создать еще один WKWebView с другим набором методов JS, мне придется сделать другую обертку! Другая проблема заключается в том, что мне нужна ссылка на WKWebView в моем ContentView, чтобы я мог вызвать evaluateJavaScript!





Просто сделайте myWebView переменной lazy. Это гарантирует, что jsHandler инициализируется до WebView.
lazy var myWebView: WKWebView = {
let config = WKWebViewConfiguration()
let controller = WKUserContentController()
controller.add(myJSHandler , name: "documentReady")
config.userContentController = controller
return WKWebView(frame: .zero, configuration: config)
}()
Это не будет работать для представления SwiftUI, поскольку оно не имеет изменяемой личности.
Одним из возможных решений является настройка WKWebView в init
struct ContentView: View {
private var myWebView: WKWebView
init() {
let config = WKWebViewConfiguration()
let controller = WKUserContentController()
controller.add(myJSHandler , name: "documentReady")
config.userContentController = controller
myWebView = WKWebView(frame: .zero, configuration: config)
}
var body: some View {
WebView(webView: myWebView)
.onAppear {
let url = Bundle.main.url(forResource: "index", withExtension: "html")!
myWebView.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
myWebView.load(request)
}
}
class JSHandler : NSObject, WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
print("documentReady")
message.webView?.evaluateJavaScript("<Long Script to Transfer Data>") { (result, error) in
}
}
}
let myJSHandler = JSHandler()
}
Я только что нашел решение, переместив myWebView внутрь JSHandler, но тогда я не знаю, как назвать JSHandler, поскольку он не только обрабатывает обратные вызовы JavaScript, но также служит оболочкой для WKWebView. Это намного лучше. Кстати, спасибо за совет использовать message.webView? вместо ссылки на ContentView.
Вы просто не можете использовать ссылку на ContentView, потому что это не ссылочный тип, это будет копия.
Я не знал об этом. Я подумал, что добавление var contentView: ContentView? в JSHandler, как я изначально сделал, работает, поскольку ContentView? может быть nil.
Оберните
WKWebViewв UIViewRepresentable, как в stackoverflow.com/a/59790493/12299030.