SwiftUI - 当改变一个带有@Published属性的结构体时,是否可能触发didSet?

24

我刚刚升级到XCode 11.4,我的一些代码停止工作了。我有一些@ Published结构变量在一个ObservableObject中。以前,当我更新结构上的属性时,didSet方法会触发已发布的属性,但现在不再是这种情况。在最新版本的Swift中,这种行为是否已经被设计改变了呢?

这里是一个微不足道的例子:


import SwiftUI

struct PaddingRect {
  var left: CGFloat = 20
  var right: CGFloat = 20
}

final class SomeStore : ObservableObject {
  @Published var someOtherValue: String = "Waiting for didSet"

  @Published var paddingRect:PaddingRect = PaddingRect() {
    didSet {
      someOtherValue = "didSet fired"
    }
  }
}

struct ObserverIssue: View {
  @ObservedObject var store = SomeStore()

  var body: some View {
    VStack {
      Spacer()

      Rectangle()
        .fill(Color.yellow)
        .padding(.leading, store.paddingRect.left)
        .padding(.trailing, store.paddingRect.right)
        .frame(height: 100)

      Text(store.someOtherValue)

      HStack {
        Button(action: {
          // This doesn't call didSet
          self.store.paddingRect.left += 20

          // This does call didSet, ie. setting the whole thing
//          self.store.paddingRect = PaddingRect(
//            left: self.store.paddingRect.left + 20,
//            right: self.store.paddingRect.right
//          )

        }) {
          Text("Padding left +20")
        }

        Button(action: {
          self.store.paddingRect.right += 20
        }) {
          Text("Padding right +20")
        }
      }

      Spacer()
    }
  }
}

struct ObserverIssue_Previews: PreviewProvider {
    static var previews: some View {
        ObserverIssue()
    }
}

属性值被更新,但是didSet方法没有被触发。

是否有可能使结构体的嵌套属性触发发布者的didSet方法?


很奇怪,直到我升级之前它都在我的代码中工作正常。我将更改问题的标题,因为你可以看到我的实际问题是“是否可能获取嵌套结构的属性以触发发布者的didSet方法”。 - codewithfeeling
1
@Asperi:我也遇到了同样的问题......而且 didSet 在我的更新之前被调用 - 这是真的。也许以前有错误......但它起作用了 ;) 我喜欢这个功能,现在我很想念它.... - Chris
看这个:https://dev59.com/JVIH5IYBdhLWcg3wfu1c#59391476。然后它就能再次工作了 ;) - Chris
我甚至无法使未发布的变量工作,所以问题可能出在ObservableObject上。 - Cristi Băluță
2个回答

38
你可以订阅类本身的@Published值流。
final class SomeStore: ObservableObject {
    @Published var someOtherValue: String = "Waiting for didSet"
    @Published var paddingRect: PaddingRect = PaddingRect()
    private var subscribers: Set<AnyCancellable> = []
    
    init() {
        $paddingRect.sink { paddingRect in
            print(paddingRect) // 
        }.store(in: &subscribers)
    }
}
请注意,即使是在`willSet`中,`sink`闭包也会被调用。

21
请记住,Published.Publisher(通过$paddingRect访问)实现的是_willSet_而不是_didSet_。 - Dávid Pásztor
1
@DávidPásztor 是的,这种期望差异可能会导致令人讨厌的微妙错误:https://forums.swift.org/t/is-this-a-bug-in-published/31292 - Tomáš Kafka

12

属性观察器用于观察属性。这个问题与新的Swift语法相关,涉及属性包装器。在您的情况下,您尝试观察Published的值(它是定义专门的属性包装器的结构体),而不是包装属性的值。

如果您需要监视PaddingRect中的左侧或右侧值,请直接观察这些值。

import SwiftUI


struct PaddingRect {
    var left: CGFloat = 20 {
        didSet {
            print("left padding change from:", oldValue, "to:", left)
        }
    }
    var right: CGFloat = 20 {
        didSet {
            print("right padding change from:", oldValue, "to:", right)
        }
    }
}

final class SomeStore : ObservableObject {
    @Published var someOtherValue: String = "Waiting for didSet"
    @Published var paddingRect:PaddingRect = PaddingRect()
}

struct ContentView: View {
    @ObservedObject var store = SomeStore()

    var body: some View {
        VStack {
            Spacer()

            Rectangle()
                .fill(Color.yellow)
                .padding(.leading, store.paddingRect.left)
                .padding(.trailing, store.paddingRect.right)
                .frame(height: 100)

            Text(store.someOtherValue)

            HStack {
                Button(action: {
                    // This doesn't call didSet
                    self.store.paddingRect.left += 20

                    // This does call didSet, ie. setting the whole thing
                    self.store.paddingRect = PaddingRect(
                        left: self.store.paddingRect.left + 20,
                        right: self.store.paddingRect.right
                    )

                }) {
                    Text("Padding left +20")
                }

                Button(action: {
                    self.store.paddingRect.right += 20
                }) {
                    Text("Padding right +20")
                }
            }

            Spacer()
        }
    }
}

struct ContentView_Preview: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

这里输入图片描述

或者利用已发布的投影值是发布者的优势,并将下一个修改器应用于任何视图。

.onReceive(store.$paddingRect) { (p) in
            print(p)
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接