SwiftUI:执行函数时如何从观察到的对象中捕获变化的值
SwiftUI: How can I catch changing value from observed object when I execute function
我对 SwiftUI 中的观察对象有疑问。
我可以在 View 结构上看到观察对象的值不断变化。
但是在 class 或函数中,即使我更改了 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)
}
}
}
}
请帮帮我。
非 SwiftUI 代码
- 仅对 SwiftUI 使用
ObservedObject
,您的函数/其他非 SwiftUI 代码将不会对更改做出反应。
- 使用像
Sink
这样的订阅者来观察任何发布者的变化。 (每个 @Published
变量都有一个发布者作为包装值,您可以通过前缀 $
符号来使用它。
SwiftUI
视图对 class 属性 更改没有反应的原因:
struct
是一种值类型,因此当它的任何属性发生变化时,struct
的值也会发生变化
class
是一个引用类型,当它的任何属性发生变化时,底层的 class
实例仍然是相同的。
- 如果您分配一个新的 class 实例,那么您会注意到视图会对更改做出反应。
方法:
- 使用单独的视图并接受
codeTwoText
作为 @Binding
这样当 codeTwoText
更改时视图将更新以反映新值。
- 您可以将模型保留为 class,因此不会有任何变化。
例子
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)
}
}
它用于两个 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
...
我对 SwiftUI 中的观察对象有疑问。 我可以在 View 结构上看到观察对象的值不断变化。 但是在 class 或函数中,即使我更改了 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)
}
}
}
}
请帮帮我。
非 SwiftUI 代码
- 仅对 SwiftUI 使用
ObservedObject
,您的函数/其他非 SwiftUI 代码将不会对更改做出反应。 - 使用像
Sink
这样的订阅者来观察任何发布者的变化。 (每个@Published
变量都有一个发布者作为包装值,您可以通过前缀$
符号来使用它。
SwiftUI
视图对 class 属性 更改没有反应的原因:
struct
是一种值类型,因此当它的任何属性发生变化时,struct
的值也会发生变化class
是一个引用类型,当它的任何属性发生变化时,底层的class
实例仍然是相同的。- 如果您分配一个新的 class 实例,那么您会注意到视图会对更改做出反应。
方法:
- 使用单独的视图并接受
codeTwoText
作为@Binding
这样当codeTwoText
更改时视图将更新以反映新值。 - 您可以将模型保留为 class,因此不会有任何变化。
例子
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)
}
}
它用于两个 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
...