Изменения в ObservedObject не обновляют UIViewRepresentable

Самый простой пример ниже. Прекрасно работает в предварительном просмотре (текст UITextView обновляется до "ой"). Но запустите его в приложении (т.е. добавьте как rootView с помощью sceneDelegate), и UITextView не обновится.


class ModelObject : ObservableObject{
    @Published var text = "Model Text"
}

struct MyTextView : UIViewRepresentable {
    @ObservedObject var modelObject : ModelObject

    func makeUIView(context: Context) -> UITextView {
        let result = UITextView()
        result.isEditable = true
        return result
    }
    func updateUIView(_ view: UITextView, context: Context) {
        view.text = modelObject.text
    }
}

struct BugDemoView : View{
    @ObservedObject var modelObject : ModelObject
    var body : some View{
        VStack{
            MyTextView(modelObject: modelObject)
            Button(action: {
                self.modelObject.text = "ouch"
            }){
                Text("Button")
            }
        }
    }
}

#if DEBUG

var mo = ModelObject()

struct BugDemoView_Preview: PreviewProvider {
    static var previews: some View {
        BugDemoView(modelObject: mo)
    }

}
#endif

4 ответа

Похоже на какую-то ошибку SwiftUI, но есть два обходных пути:

  1. Передать строку как @Binding к MyTextView:
struct MyTextView : UIViewRepresentable {
    @Binding var text: String

    func makeUIView(context: Context) -> UITextView {
        let result = UITextView()
        result.isEditable = true
        return result
    }
    func updateUIView(_ view: UITextView, context: Context) {
        view.text = text
    }
}

struct BugDemoView : View{
    @ObservedObject var modelObject = ModelObject()
    var body : some View{
        VStack{
            MyTextView(text: $modelObject.text)
            Button(action: {
                self.modelObject.text = "ouch"
            }){
                Text("Button")
            }
        }
    }
}
  1. Передать строку в MyTextView:
struct MyTextView : UIViewRepresentable {
    var text: String

    func makeUIView(context: Context) -> UITextView {
        let result = UITextView()
        result.isEditable = true
        return result
    }
    func updateUIView(_ view: UITextView, context: Context) {
        view.text = text
    }
}

struct BugDemoView: View{
    @ObservedObject var modelObject = ModelObject()
    var body: some View{
        VStack{
            MyTextView(text: modelObject.text)
            Button(action: {
                self.modelObject.text = "ouch"
            }){
                Text("Button")
            }
        }
    }
}

С помощью операторов печати я заметил, что makeUIView больше не вызывается после успешной загрузки первого URL-адреса. Таким образом, после изменения URL-адреса мой WebView оставался на первом URL-адресе.

Я изменил свой метод updateUIView, который вызывался при обновлении URL-адреса после первой успешной загрузки, чтобы проверить, есть ли разница между активным URL-адресом и новым URL-адресом. Если была разница, я обновил страницу с правильным URL-адресом.

Вот мой пример метода updateUIView:

      let request: URLRequest

func updateUIView(_ uiView: WKWebView, context: Context) {
    if uiView.canGoBack, webViewStateModel.goBack {
        uiView.goBack()
        webViewStateModel.goBack = false
    } else {
        if(uiView.url?.absoluteString == request.url?.absoluteString){
            print("The urls are equal")
        } else {
            print("The urls are NOT equal")
            uiView.load(request)
        }
    }
}

У меня были смешанные результаты, используя следующее. Я почти уверен, что есть какая-то ошибка сUIViewRepresentable

Это сработало для некоторых представлений, но затем я снова нажал то же представление, и модель представления не обновлялась. Очень странный...

Надеюсь, они скоро выпустят SwiftUI TextView.

struct TextView: UIViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UITextView {
        let myTextView = UITextView()
        myTextView.delegate = context.coordinator
        myTextView.font = UIFont(name: "HelveticaNeue", size: 15)
        myTextView.isScrollEnabled = true
        myTextView.isEditable = true
        myTextView.isUserInteractionEnabled = true
        myTextView.backgroundColor = UIColor(white: 0.0, alpha: 0.05)
        return myTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator: NSObject, UITextViewDelegate {

        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }

        func textViewDidChange(_ textView: UITextView) {
            print("text now: \(String(describing: textView.text!))")
            self.parent.text = textView.text
        }
    }
}

В моем случае мне нужно передать наблюдаемый объект в uiViewRepresentable, так же как и в случае, упомянутом Риверой... Когда свойство @Published этого наблюдаемого объекта изменяется, updateUIView вызывается в симуляторе, но не на реальном устройстве. Я использую последнюю версию Xcode 11.4, но, по общему признанию, на устройстве под управлением iOS 13.3 (13.4.1 еще не установлена, поэтому я не проверял, была ли устранена эта ошибка). Моя проблема решена следующим образом: преобразовать структуру MyTextView в последний класс (последнее ключевое слово важно), а затем добавить инициализатор. Нет необходимости даже вызывать.objectWillChange.send() для наблюдаемого объекта прямо перед тем, как сработает изменение опубликованной переменной.

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