概述

  • 我有一辆@StateObject,它被用来开一辆NavigationSplitView.
  • 我使用@StateObject上的@Published个属性来驱动视图的 Select .一百零二
  • 当我进入NavigationSplitView人的最终 Select 时,我想创造一个Selection分.
  • 这应该会发布一个属性only once.
import SwiftUI
import Combine

struct Consumption: Identifiable, Hashable {
    var id: UUID = UUID()
    var name: String
}

struct Frequency: Identifiable, Hashable {
    var id: UUID = UUID()
    var name: String
}

struct Selection: Identifiable {
    var id: UUID = UUID()
    var consumption: Consumption
    var frequency: Frequency
}


class SomeModel: ObservableObject {
    
    let consump: [Consumption] = ["Coffee","Tea","Beer"].map { str in Consumption(name: str)}
    let freq: [Frequency] = ["daily","weekly","monthly"].map { str in Frequency(name: str)}

    @Published var consumption: Consumption?
    @Published var frequency: Frequency?
    @Published var selection: Selection?
    
    private var cancellable = Set<AnyCancellable>()

    init(consumption: Consumption? = nil, frequency: Frequency? = nil, selection: Selection? = nil) {
        self.consumption = consumption
        self.frequency = frequency
        self.selection = selection
        
        $frequency
            .print()
            .sink { newValue in
                if let newValue = newValue {
                    print("THIS SHOULD APPEAR ONCE")
                    print("---------------")
                    self.selection = .init(consumption: self.consumption!, frequency: newValue)
                } else {
                    print("NOTHING")
                    print("---------------")
                }
            }.store(in: &cancellable)
    }
    
}

问题

视图上的@StateObject是发布property twice的.(请参阅下面的代码)


struct ContentView: View {
    
    @StateObject var model: SomeModel = .init()
    
    var body: some View {
        NavigationSplitView {
            VStack {
                List(model.consump, selection: $model.consumption) { item in
                    NavigationLink(value: item) {
                        Label(item.name, systemImage: "circle.fill")
                    }
                }
            }
        } content: {
            switch model.consumption {
            case .none:
                Text("nothing")
            case .some(let consumption):
                List(model.freq, id:\.id, selection: $model.frequency) { item in
                    NavigationLink(item.name, value: item)
                }
                .navigationTitle(consumption.name)
            }
        } detail: {
            switch model.selection {
            case .none:
                Text("nothing selected")
            case .some(let selection):
                VStack {
                    Text(selection.consumption.name)
                    Text(selection.frequency.name)
                }
            }
        }

    }
}

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

example of twice published

我的解决方案

如果我通过在视图上创建@State个属性来更改视图,然后使用OnChange修改器更新模型,我会将属性设置为update once.这就是我想要的.(请参阅下面的代码)

 struct ContentView: View {
     
     @StateObject var model: SomeModel = .init()
     
     @State private var consumption: Consumption?
     @State private var frequency: Frequency?

     var body: some View {
         NavigationSplitView {
             VStack {
                 List(model.consump, selection: $consumption) { item in
                     NavigationLink(value: item) {
                         Label(item.name, systemImage: "circle.fill")
                     }
                 }
             }.onChange(of: consumption) { newValue in
                 self.model.consumption = newValue
             }
         } content: {
             switch model.consumption {
             case .none:
                 Text("nothing")
             case .some(let consumption):
                 List(model.freq, id:\.id, selection: $frequency) { item in
                     NavigationLink(item.name, value: item)
                 }
                 .navigationTitle(consumption.name)
                 .onChange(of: frequency) { newValue in
                     self.model.frequency = newValue
                 }
             }
         } detail: {
             switch model.selection {
             case .none:
                 Text("nothing selected")
             case .some(let selection):
                 VStack {
                     Text(selection.consumption.name)
                     Text(selection.frequency.name)
                 }
             }
         }

     }
 }

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

问题/帮助

@StateObject + @PublishedView + @State之间,@Binding的行为有区别吗?

我不明白为什么在我之前的视图中,该属性被发布了两次.

我的解决方案需要做更多的工作,但它通过使用@StateonChange个视图修饰符来工作.

推荐答案

嗯,我不能完全解释为什么会发生这种情况.似乎List更新了它的 Select 两次.

为什么?未知.?May be a bug?.

请考虑以下调试方法:

} content: {
        switch model.consumption {
        case .none:
            Text("nothing")
        case .some(let consumption):
            // Create a custom binding and connect it to the viewmodel
            let binding = Binding<Frequency?> {
                model.frequency
            } set: { frequency, transaction in
                model.frequency = frequency
                // this will print after the List selected a new Value
                print("List set frequency")
            }
                                      // use the custom binding here
            List(model.freq, id:\.id, selection: binding) { item in
                NavigationLink(item.name, value: item)
            }
            .navigationTitle(consumption.name)
        }

这将产生以下输出:

receive subscription: (PublishedSubject)
request unlimited
receive value: (nil)
NOTHING
---------------
receive value: (Optional(__lldb_expr_7.Frequency(id: D9552E6A-71FE-407A-90F5-87C39FE24193, name: "daily")))
THIS SHOULD APPEAR ONCE
---------------
List set frequency
receive value: (Optional(__lldb_expr_7.Frequency(id: D9552E6A-71FE-407A-90F5-87C39FE24193, name: "daily")))
THIS SHOULD APPEAR ONCE
---------------
List set frequency

使用@State.onChange时看不到这一点的原因是.onChange修改器.它仅在值更改时才会触发,但它不会更改.另一方面,无论值是否更改,您的Combine发布程序每次都会触发.

解决方案:

您可以保留合并方法,并使用.removeDuplicates修改器.

$frequency
    .removeDuplicates()
    .print()
    .sink { newValue in

这将确保只有在发送新值时才会调用接收器.

Swift相关问答推荐

可编码:将字符串解码为自定义类型(ISO 8601日期,无时间组件)

如何将泛型函数存储到变量中?

从后台线程获取@发布属性的值(不更改它)是正确的吗?

我在SwiftUI中的动画无法正常工作

像WebSocket一样接收实时更新,但通过异步/等待或组合

NSApplication是否需要NSApplicationDelegate?

在SWIFTUI&39;S视图修改器中使用等待关键字

Swift 扩展:通过方法重载增强现有类

循环字典中的数组

无论玩家移动到视图内的哪个位置,如何使用 SpriteKit 让敌人向玩家emits 子弹?

BLE 在 ESP32 和 Iphone 之间发送字符串

从 Obj-C 函数返回 swift 类的不兼容指针类型

令人满意的 var body:协议扩展中的一些视图

使用 Swiftui 水平修剪 Shape()

如何在不阻塞 UI 更新的情况下使用 Swift Concurrency 在后台执行 CPU 密集型任务?

如果没有标记,则为 Swift 预处理器

FirebaseStorage:如何删除目录

FCM 后台通知在 iOS 中不起作用

Swift 3.0 的 stringByReplacingOccurencesOfString()

<<错误类型>> 奇怪的错误