SwiftUI: Как я могу поймать изменение значения наблюдаемого объекта при выполнении функции - PullRequest
1 голос
/ 21 апреля 2020

У меня проблема с наблюдаемым объектом в SwiftUI. Я могу видеть изменение значений наблюдаемого объекта в структуре View. Однако в классе или функции, даже если я изменяю текстовое значение TextField (который является наблюдаемым объектом), но "self.codeTwo.text все еще не изменился.

вот мой пример кода (это мой ObservableObject)

class settingCodeTwo: ObservableObject {

private static let userDefaultTextKey = "textKey2"
@Published var text: String = UserDefaults.standard.string(forKey: settingCodeTwo.userDefaultTextKey) ?? ""

private var canc: AnyCancellable!

     init() {
        canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
        UserDefaults.standard.set(newText, forKey: settingCodeTwo.userDefaultTextKey)

    }
}


   deinit {
     canc.cancel()
   }

}

и главная проблема в том, что ... "self.codeTwo.text" никогда не менялся!

class NetworkManager: ObservableObject {


@ObservedObject var codeTwo = settingCodeTwo()
@Published var posts = [Post]()


func fetchData() {
    var urlComponents = URLComponents()
    urlComponents.scheme = "http"



    urlComponents.host = "\(self.codeTwo.text)" //This one I want to use observable object




    urlComponents.path = "/mob_json/mob_json.aspx"
    urlComponents.queryItems = [
        URLQueryItem(name: "nm_sp", value: "UP_MOB_CHECK_LOGIN"),
        URLQueryItem(name: "param", value: "1000|1000|\(Gpass.hahaha)")
    ]

    if let url = urlComponents.url {
        print(url)
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { (data, response, error) in
            if error == nil {
                let decoder = JSONDecoder()
                if let safeData = data {
                    do {
                        let results = try decoder.decode(Results.self, from: safeData)

                        DispatchQueue.main.async {
                            self.posts = results.Table
                        }   
                    } catch {
                        print(error)
                    }
                }
            }
        }
        task.resume()
    }

}
}

, и это представление, я могу уловить изменение значения в этом

import SwiftUI
import Combine



struct SettingView: View {

  @ObservedObject var codeTwo = settingCodeTwo()
  var body: some View {
    ZStack {
        Rectangle().foregroundColor(Color.white).edgesIgnoringSafeArea(.all).background(Color.white)
        VStack {


            TextField("test", text: $codeTwo.text).textFieldStyle(BottomLineTextFieldStyle()).foregroundColor(.blue)

            Text(codeTwo.text)
        }
    }
}
}

Помогите мне, пожалуйста.

Ответы [ 2 ]

1 голос
/ 21 апреля 2020

Используется два различных экземпляра SettingCodeTwo - один в NetworkNamager другой в SettingsView, поэтому они не синхронизируются, если создаются одновременно.

Здесь подход для сохранения этих двух экземпляров самосинхронизированными (возможно, потому что они используют одно и то же хранилище - UserDefaults)

Протестировано с Xcode 11.4 / iOS 13.4

Измененный код ниже (см. также важные комментарии в строке)

extension UserDefaults { 
    @objc dynamic var textKey2: String { // helper keypath 
        return string(forKey: "textKey2") ?? ""
    }
}

class SettingCodeTwo: ObservableObject { // use capitalised name for class !!!

    private static let userDefaultTextKey = "textKey2"
    @Published var text: String = UserDefaults.standard.string(forKey: SettingCodeTwo.userDefaultTextKey) ?? ""

    private var canc: AnyCancellable!
    private var observer: NSKeyValueObservation!

    init() {
        canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
            UserDefaults.standard.set(newText, forKey: SettingCodeTwo.userDefaultTextKey)
        }
        observer = UserDefaults.standard.observe(\.textKey2, options: [.new]) { _, value in
            if let newValue = value.newValue, self.text != newValue { // << avoid cycling on changed self
                self.text = newValue
            }
        }
    }
}

class NetworkManager: ObservableObject {

    var codeTwo = SettingCodeTwo() // no @ObservedObject needed here
    ...
1 голос
/ 21 апреля 2020

Код не-SwiftUI

  • Используйте ObservedObject только для SwiftUI, ваша функция / другой код не-SwiftUI не будет реагировать на изменения.
  • Используйте такого подписчика, как Sink наблюдать за изменениями у любого издателя. (Каждая переменная @Published имеет издателя в качестве обернутого значения, вы можете использовать его с префиксом $.

Причина SwiftUI Представление не реагирует на изменения свойств класса:

  • struct является типом значения, поэтому при изменении любого из его свойств значение struct изменяется
  • class является ссылочным типом, когда любое из его свойств измените, базовый экземпляр class остается прежним.
    • Если вы назначите новый экземпляр класса, вы заметите, что представление реагирует на изменение.

Подход:

  • Использовать отдельное представление, которое принимает codeTwoText в качестве @Binding таким образом, когда codeTwoText изменяет представление, обновится, чтобы отразить новое значение.
  • Вы можете сохранить модель как класс, поэтому никаких изменений там нет.

Пример

class Model : ObservableObject {

    @Published var name : String //Ensure the property is `Published`.

    init(name: String) {
        self.name = name
    }
}

struct NameView : View {

    @Binding var name : String

    var body: some View {

        return Text(name)
    }
}

struct ContentView: View {

    @ObservedObject var model : Model

    var body: some View {
        VStack {
            Text("Hello, World!")
            NameView(name: $model.name) //Passing the Binding to name
        }
    }
}

Тестирование

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {

        let model = Model(name: "aaa")

        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            model.name = "bbb"
        }

        return ContentView(model: model)
    }
}
...