我是 SwiftUI 的新手,想知道 SwiftUI 中是否有類似于 React.useEffect 的概念。
下面是我在 macos 上監聽鍵盤事件的代碼。
import SwiftUI
import PlaygroundSupport
struct ContentView : View {
var hello: String
@State var monitor: Any?
@State var text = ""
init(hello: String) {
self.hello = hello
print("ContentView init")
}
var body: some View {
VStack{
Text(hello)
.padding()
TextField("input", text: $text)
}
.onAppear {
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
print(hello)
return nil
}
}
}
}
struct MainView: View {
@State var hello: String = "h"
var body: some View {
ContentView(hello: hello)
Button(action: {
hello = "_h"
}) {
Text("tap me")
}
}
}
PlaygroundPage.current.setLiveView(MainView())
操場輸出如下
ContentView init
h
h
ContentView init
h
h
h
由于 onAppear 只觸發一次,即使 ContentView 多次初始化。所以這里的事件回呼總是列印第一個捕獲的值(“h”)。
那么我應該在哪里添加事件監聽器,我應該在哪里洗掉它?
uj5u.com熱心網友回復:
在 React 中,您useEffect在 Component 中使用來宣告會在渲染階段之外導致副作用的任務或操作。
好吧,SwiftUI 并不完全是 React,有一些問題和用例可以用完全不同的方法解決。但是,當試圖找到類似的東西時:
在 SwiftUI 中,您可以呼叫從任何“動作”閉包呼叫的任何函式,例如從 SwiftUI 呼叫Button。此函式可以修改@State變數,而不會中斷渲染階段。
或者您可以使用任務修改器,即呼叫.task { ... }可能最接近的 SwiftUI 視圖。
就個人而言,我不會宣告在 SwiftUI 視圖的主體函式中使用任何會對 AppState或模型造成副作用的任務或操作。相反,我更喜歡將用戶的動作(又名“意圖”、“事件”)發送到模型或視圖模型或有限狀態自動機。然后這些事件在一個純函式中處理,稱為“update()”,它執行“邏輯”,這個純函式可以宣告“效果”。然后這些效果將在這個純更新函式之外被呼叫,在任何需要的地方產生副作用,并回傳一個作為事件具體化的結果,它本身又被輸入到純更新函式中。該更新函式產生一個“視圖狀態”,
現在,我想澄清一些潛在的誤解:
“由于 onAppear 只觸發一次,甚至 ContentView init 多次”
出現
對于您在螢屏上標識為“視圖”的視圖,這實際上可以呼叫多次。onAppear通常,用于執行某種初始化或設定并非總是沒有問題。不過,有一些方法可以完全避免這些問題。
“內容視圖初始化”
您最好將 SwiftUI 視圖視為“函式”(什么?)
使用該“功能”,您可以實作兩件事:
- 創建一個底層視圖,其職責是渲染像素,并為此視圖創建(私有)資料,它需要相應地渲染。
- 修改此基礎視圖的此資料或屬性。
對于任一操作,您都必須呼叫 SwiftUI 視圖的初始化程式。當任一操作完成時,SwiftUI 視圖(一個結構!)將再次縮小。通常,結構的值,SwiftUI 視圖只是臨時駐留在堆疊上。
宣告為和朋友的變數@State與負責渲染像素的底層視圖相關聯。它們的生命周期與您可以在螢屏上感知的可渲染視圖系結。
現在,查看您的代碼,它應該可以按預期作業。您為事件處理程式物件創建了一個私有@State變數。這似乎是正確的做法。但是,@State這意味著作為一個私有變數,其中的更改會導致視圖呈現不同的方式。您的事件處理程式物件實際上是一個“Any”,即一個參考。這個參考永遠不會改變:它將被設定,onAppear然后它永遠不會改變,除非onAppear將再次呼叫相同的底層可渲染視圖。對于您的事件處理程式物件,可能有比使用@State和更好的解決方案onAppear(見下文)。
現在,當您想要呈現事件的值(又名maskas NSEvent.EventTypeMask)時,您需要在這種型別的 SwiftUI 視圖中使用另一個@State變數,您可以在通知處理程式中設定/更新它。變數應該是結構或列舉,而不是參考!
然后 SwiftUI 會通知這個變數的變化,然后會呼叫你顯式渲染這個值的 body 函式。請注意,您可以從任何執行緒更新@State變數。
問題
根據檔案“您必須呼叫 removeMonitor(_:) 來停止監視器。”
不幸的是,當底層可渲染視圖被釋放時@State,保存對事件處理程式物件的參考的變數將不會呼叫。removeMonitor(_:)
無賴!
你需要做的是,改變你的設計。您需要做的是引入一個“模型”,它是一個ObservableObject. 它應該發布一個值(表示您在通知處理程式中收到的內容),該值將相應地呈現在 SwiftUI 視圖中。
當視圖出現時,該模型還應該接收一個事件(例如,將從 SwiftUI 視圖中為模型呼叫一個函式),然后模型在其中創建事件處理程式物件,除非它已經創建(這完全解決了您的onAppear問題) . 或者,只需在模型的初始化程式中創建一次且僅一次的事件處理程式 - 這可以說是更好的解決方案。
當事件處理程式的通知處理程式將被呼叫時,您會相應地更新模型的發布值。
將模型(一個 ObservableObject)正確地集成到 SwiftUI 視圖中是 SwiftUI 中的標準模式。如果您不確定如何完成此操作,請尋求有關 SO 的幫助。
現在,由于 Model 是一個類值,您可以確保呼叫removeMonitor(_:)它的deinit函式。
先發制人
import SwiftUI
final class EventHandlerModel: ObservableObject {
private var monitor: Any!
@Published private(set) var viewState: String = ""
init() {
monitor = NSEvent.addLocalMonitorForEvents(
matching: .keyDown
) { event in
assert(Thread.isMainThread)
self.viewState = "\(event)"
return event
}
}
deinit {
guard let monitor = self.monitor else {
return
}
NSEvent.removeMonitor(monitor)
}
}
struct ContentView: View {
@StateObject private var model = EventHandlerModel()
var body: some View {
Text(verbatim: model.viewState)
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/525539.html
標籤:迅速苹果系统迅捷
