基于本地通知刷新SwiftUI视图。

4
我有以下代码:
  @State private var isDataImported: Bool = false
    
    init() {
        NotificationCenter.default.addObserver(forName: .onDataImported, object: nil, queue: nil) { [self] notification in
            print("Data has been imported...")
            
            DispatchQueue.main.async {
                self.isDataImported = true
                print(self.isDataImported) // prints out false 
            }
            
           
        }
    }

我可以调试并查看通知是否被发送。该行“数据已导入”正在打印。我想更新self.isDataImported属性,然后刷新我的视图。
 if isDataImported {
              ShowDataView() 
            } else {
                ProgressView()
            }

但是ShowDataView视图从未显示,因为isDataImported始终为false。我错过了什么?

2个回答

12

如果您使用SwiftUI,应考虑使用所有可用的响应式工具并订阅Publishers。

SwiftUI有一个方法onReceive(_:perform:),它接受一个Publisher和一个闭包,在从该publisher接收事件时运行。 您可以使用它来监听您的通知并根据情况更改任何状态。

以这种方式进行,而不是在.onAppear中创建和手动订阅Publisher,意味着您不需要保留可取消性。

import SwiftUI
import Combine

extension NSNotification.Name {
    static let onDataImported = Notification.Name("onDataImported")
}

struct ContentView: View {
    @State private var dataReceived = false

    var body: some View {
        VStack {
            Text(dataReceived ? "Received" : "Waiting")
                .padding()

            Button("Simulate Notification") {
                NotificationCenter.default.post(name: .onDataImported, object: nil)
            }
            .padding()
        }
        .onReceive(NotificationCenter.default.publisher(for: .onDataImported), perform: { _ in
            self.dataReceived = true
        })
    }
}

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

我已添加了一个按钮,用于发送通知,以便您可以看到它被接收。


5

SwiftUI视图的传递性质使得在视图的init中尝试捕获对self的引用变得棘手。以下是几种解决方案:

选项1

将所有内容保留在View中:

struct ContentView: View {
    @State private var isDataImported: Bool = false
    @State private var cancellable : AnyCancellable?
    
    var body: some View {
        Group {
            if isDataImported {
                Text("Has data")
            } else {
                Text("Does not have data")
            }
        }.onAppear {
            cancellable = NotificationCenter.default.publisher(for: .onDataImported)
                .receive(on: RunLoop.main)
                .sink { notification in
                    self.isDataImported = true
            }
            NotificationCenter.default.post(name: .onDataImported, object: nil)
        }
    }
}

选项2

通常我会这样做,将其移动到视图模型中,这样您就可以使onAppear更加整洁。 因为视图模型是一个 class 并具有可靠的基于引用的生命周期,因此对 self 的分配问题较少:

class ViewModel: ObservableObject {
    @Published var isDataImported: Bool = false
    private var cancellable : AnyCancellable?
    
    init() {
        cancellable = NotificationCenter.default.publisher(for: .onDataImported)
        .receive(on: RunLoop.main)
        .sink { notification in
            self.isDataImported = true
        }
    }
}

struct ContentView : View {
    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        Group {
            if viewModel.isDataImported {
                Text("Has data")
            } else {
                Text("Does not have data")
            }
        }.onAppear {
            NotificationCenter.default.post(name: .onDataImported, object: nil)
        }
    }
}

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