I've created a minimum reproducible example of this problem I'm facing.
First, I created a WKWebView
housed in a UIViewRepresentable
to be used with SwiftUI. Then, I set up a WKUserContentController
and a WKWebViewConfiguration
so the WKWebView
can send messages to native code. In this case, I have a <textarea>
that sends over its value
on input.
The value sent over through WebKit is assigned to a @State
variable which causes the view to update. Unfortunately, the WKWebView
is deselected whenever the view updates. How can I work around this? The WKWebView
needs to stay selected until the user deliberately chooses to hide the keyboard.
This is a minimal example of what's happening:
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
let html: String
let configuration: WKWebViewConfiguration
func makeUIView(context: Context) -> WKWebView {
.init(frame: .zero, configuration: configuration)
}
func updateUIView(_ webView: WKWebView, context: Context) {
webView.loadHTMLString(html, baseURL: nil)
}
}
struct ContentView: View {
final class Coordinator: NSObject, WKScriptMessageHandler {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
guard let text = message.body as? String else { return }
self.text = text
}
}
@State var text = ""
var body: some View {
WebView(
html: """
<!DOCTYPE html>
<html>
<body>
<textarea>\(text)</textarea>
<script>
const textarea = document.querySelector('textarea')
textarea.oninput = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
</script>
</body>
</html>
""",
configuration: {
let userContentController = WKUserContentController()
userContentController.add(Coordinator(text: $text), name: "main")
let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
return configuration
}()
)
}
}
I would recommend (as I see the simplest in this scenario) to change handling event to
textarea.onblur = () =>
webkit.messageHandlers.main.postMessage(textarea.value)
otherwise a complex refactoring is needed to keep away WK* entities from SwiftUI representable wrapper, or changing model to avoid using @State
or anything resulting in view rebuild, or both of those.