Веб-представление SwiftUI не отображается с первого раза

Я хотел бы использовать веб-просмотр с HTML и JavaScript для рендеринга чего-либо. Ниже приведен полный код для воспроизведения проблемы. Я ожидаю, что TestViewPreview сначала отобразит текст «hello testview», а через 5 секунд отобразит «hello2 testview». Однако отображается только второй. В первом случае выдает некоторую ошибку, не уверен, связана ли она с ней:

AssessmentJavaScript: render('привет testview'); TestView.updateUIView ошибка: Error Domain=WKErrorDomain Code=4 «Исключение JavaScript произошло" UserInfo = {WKJavaScriptExceptionLineNumber=0, WKJavaScriptExceptionMessage=TypeError: undefined не является функцией, WKJavaScriptExceptionColumnNumber=0, NSLocalizedDescription=A Произошло исключение JavaScript}

Не могли бы вы взглянуть на код ниже?

struct MyState {
  var text: String = "abc"
}

struct TestView: UIViewRepresentable {
  let htmlPath: String
  @Binding var state: MyState
  
  func makeUIView(context: Context) -> WKWebView {
    if let htmlPath = Bundle.main.path(forResource: htmlPath, ofType: "html") {
      do {
        let htmlString = try String(contentsOfFile: htmlPath)
        WebViewHelper.webView.loadHTMLString(htmlString, baseURL: Bundle.main.bundleURL)
        WebViewHelper.webView.scrollView.showsHorizontalScrollIndicator = true
        print("TestView.makeUIView: Finished inital loading.")
      } catch {
        print("TestView.makeUIView: Error loading HTML file: \(error)")
      }
    }
    return WebViewHelper.webView
  }
  
  func updateUIView(_ webView: WKWebView, context: Context) {
    let script = "render('\(state.text)');"
    print("evaluateJavaScript: \(script)")
    webView.evaluateJavaScript(script) { _, error in
      if let error = error {
        print("TestView.updateUIView error: \(error)")
      }
    }
  }
}

struct TestViewPreview: View {
  @State private var state = MyState(text: "hello testview")
  
  var body: some View {
    TestView(htmlPath: "test_html", state: self.$state)
      .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
          state.text = "hello2 testview"
        }
      }
  }
}

#Preview {
  TestViewPreview()
}

test_html.html

<!DOCTYPE html>
<head>
  <meta charset = "UTF-8">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
</head>
<body>
  <div id = "output"></div>
  <script>
    function render(text) {
      document.getElementById("output").innerHTML = text
    }
  </script>
</body>
</html>
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Прежде чем вызывать функцию render в JS, вам следует дождаться, пока веб-представление завершит загрузку HTML.

Вам следует использовать метод делегата didFinish в WKNavigationDelegate, чтобы определить, что загрузка веб-представления завершилась. Затем вы можете вызвать обратный вызов, который передается в TestView.

struct TestView: UIViewRepresentable {
    let htmlPath: String
    @Binding var state: MyState
    let didLoad: () -> Void
    
    class Coordinator: NSObject, WKNavigationDelegate {
        var didLoad: () -> Void
        
        init(didLoad: @escaping () -> Void) {
            self.didLoad = didLoad
        }
        
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            // if the web view will load multiple pages, 
            // add a flag and check it here so that didLoad only gets called once
            didLoad()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        .init(didLoad: didLoad)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        webView.navigationDelegate = context.coordinator
        if let htmlPath = Bundle.main.path(forResource: htmlPath, ofType: "html") {
            do {
                let htmlString = try String(contentsOfFile: htmlPath)
                webView.loadHTMLString(htmlString, baseURL: Bundle.main.bundleURL)
                webView.scrollView.showsHorizontalScrollIndicator = true
                print("TestView.makeUIView: Finished inital loading.")
            } catch {
                print("TestView.makeUIView: Error loading HTML file: \(error)")
            }
        }
        return webView
    }
    
    func updateUIView(_ webView: WKWebView, context: Context) {
        context.coordinator.didLoad = didLoad
        let script = "render('\(state.text)');"
        webView.evaluateJavaScript(script) { _, error in
            if let error = error {
                print("TestView.updateUIView error: \(error)")
            }
        }
    }
}

Вместо onAppear вам следует установить @State в замыкании didLoad:

@State private var state = MyState(text: "") // the initial value here is just a "dummy"

var body: some View {
    TestView(htmlPath: "foo", state: self.$state) {
        // set it to the initial value you want here
        state.text = "hello testview"
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            state.text = "hello2 testview"
        }
    }
}

Обратите внимание, что ошибка, которую вы видели, все равно будет напечатана, поскольку веб-представление не загрузится при первом вызове updateUIView, но это не имеет значения. Это просто не работает render('') (начальная фиктивная пустая строка). updateUIView будет вызван снова для запуска render('hello testview') после того, как веб-представление завершит загрузку HTML.


P.S. Я надеюсь, что эта render функция — всего лишь фиктивная функция, которую вы написали для минимально воспроизводимого примера, и что ваш реальный код не такой. Вы вообще не очищаете параметр render, и это огромная уязвимость, связанная с внедрением кода.

спасибо! код работает с небольшой задержкой по сравнению с другим содержимым в представлении. Интересно, знаете ли вы, как я могу ускорить загрузку? также спасибо за замечание. это не настоящий код, но интересно, какую проверку работоспособности мне следует выполнить?

user1385809 10.06.2024 11:03

Кроме того, мне нужно сохранить начальное значение состояния в моем реальном коде, поэтому для принятия вашего решения у меня есть state = state при его вызове. это выглядит немного глупо, поэтому мне интересно, есть ли у вас лучший обходной путь?

user1385809 10.06.2024 11:07

@ user1385809 Ну, конечно, будет задержка. Какой веб-браузер может загружать контент так же быстро, как нативное приложение? То, как вам следует очищать строку, во многом зависит от вашего фактического варианта использования, поэтому я не могу комментировать это более подробно. Установка state на самом деле просто для того, чтобы снова вызвать updateUIView, поэтому все остальное, что будет достигнуто, будет работать. Вы можете передать Bool в поле зрения и toggle при необходимости.

Sweeper 10.06.2024 11:14

Другие вопросы по теме