根据本地通知刷新 SwiftUI 视图

Mar*_*Doe 4 ios swiftui

我有以下代码:

  @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 
            }
            
           
        }
    }
Run Code Online (Sandbox Code Playgroud)

我可以调试并看到通知正在被触发。正在打印“数据已导入”行。我想更新 self.isDataImported 属性,然后刷新我的视图。

 if isDataImported {
              ShowDataView() 
            } else {
                ProgressView()
            }
Run Code Online (Sandbox Code Playgroud)

但视图 ShowDataView 永远不会显示,因为 isDataImported 始终为 false。我缺少什么?

Abi*_*ern 12

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

SwiftUI 有一个方法onReceive(_:perform:),当它收到来自发布者的事件时,它需要一个发布者和闭包来运行。您可以使用它来监听您的通知并据此更改任何状态。

这样做,而不是在 .onAppear 中创建并手动订阅发布者,意味着您不需要保留可取消的内容。

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

我添加了一个按钮来发送通知,以便您可以看到已收到通知。


jn_*_*pdx 5

SwiftUI 视图的传递性质使得尝试捕获self视图中的引用之类的事情init变得有问题。这里有几个解决方案:

选项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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

选项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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)