簡體   English   中英

基於本地通知刷新 SwiftUI 視圖

[英]Refresh SwiftUI View Based on Local Notification

我有以下代碼:

  @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 總是假的。 我錯過了什么?

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)
        }
    }
}

如果您正在使用 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()
    }
}

我添加了一個按鈕來發送通知,以便您可以看到它正在被接收。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM