Swift UI - 如何从外部访问 @State 变量?

标签 swift swiftui appdelegate

我有ContentView.swift

struct ContentView: View {
    @State var seconds: String = "60"

    init(_ appDelegate: AppDelegate) {
        launchTimer()
    }
    
    func launchTimer() {
        let timer = DispatchTimeInterval.seconds(5)
        let currentDispatchWorkItem = DispatchWorkItem {
            print("in timer", "seconds", seconds) // Always `"60"`
            print("in timer", "$seconds.wrappedValue", $seconds.wrappedValue) // Always `"60"`

            launchTimer()
        }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + timer, execute: currentDispatchWorkItem)
    }

    var body: some View {
        TextField("60", text: $seconds)
    }
    
    func getSeconds() -> String {
        return $seconds.wrappedValue;
    }
}

AppDelegate.swift

class AppDelegate: NSObject, NSApplicationDelegate {
    var contentView = ContentView()

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        launchTimer()
    }

    
    func launchTimer() {
        print(contentView.getSeconds()) // Always `"60"`

        let timer = DispatchTimeInterval.seconds(Int(contentView.getSeconds()) ?? 0)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + timer) {
            self.launchTimer()
        }
    }
    

    @objc func showPreferenceWindow(_ sender: Any?) {
        if(window != nil) {
            window.close()
        }
        
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        window.isReleasedWhenClosed = false
        window.contentView = NSHostingView(rootView: contentView)
        
        window.center()
        window.makeKeyAndOrderFront(nil)
        NSApplication.shared.activate(ignoringOtherApps: true)
    }
}

contentView.getSeconds() 始终返回 60,即使我在内容 View 中修改 TexField 的值(通过在文本字段中手动输入)也是如此。

如何从应用程序委托(delegate)获取状态变量的包装值/实际值?

最佳答案

@State/@StateObject 是一件棘手的事情,发生的事情是 SwiftUI 将状态值连接到 UI 层次结构中的某个 View 实例。

在您的情况下,@State var秒:String =“60”连接到下面的 View (简化方案):

NSWindow
  -> NSHostingView
    -> ContentView <----- this is where the @State usage is valid

正如其他人所说,ContentView 是一个结构体,它是一个值类型,这意味着它的内容会在所有使用站点中复制,因此您不必像类那样拥有唯一的实例,最终会出现多个实例。

只有其中一个实例(SwiftUI 将 View 添加到 UI 树时生成的副本)连接到文本字段并进行更新。

为了让事情变得更有趣,State property wrapper也是一个结构体,这意味着它“遭受”与 View 相同的症状。

这是使用 SwiftUI 的好处之一,与 UIKit 相比,您根本不关心 View 实例。

现在,如果您想使用 AppDelegate 或任何其他地方的秒数值,则必须循环数据存储而不是 View 。

对于初学者来说,更改 View 以接收@Binding

struct ContentView: View {
    @Binding var seconds: String = "60"

    // the rest of the view code remains the same

然后创建一个存储数据的ObservableObject,并在创建 View 时注入(inject)其$seconds绑定(bind):

class AppData: ObservableObject {
    @Published var seconds: String = "60"
}

class AppDelegate {
    let appData = AppData()

    // ... other code left out for clarity

    @objc func showPreferenceWindow(_ sender: Any?) {
        // ... 
        let contentView = ContentView(seconds: appData.$seconds
        window.contentView = NSHostingView(rootView: contentView)
        // ... 
    }
}

SwiftUI 更多的是关于数据而不是 View 本身,因此如果您想在多个位置访问相同的数据,请确保循环存储而不是数据附加到的 View 。并且还要确保该数据的真实来源是一个类,因为对象引用保证指向相同的内存位置(当然假设引用不会改变)。

关于Swift UI - 如何从外部访问 @State 变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69272227/

相关文章:

ios - 在 Swift 中使用自定义消息抛出错误/异常的最简单方法?

ios - 注销后更改rootviewcontroller

swift - 使用@ViewBuilder 创建支持多个 child 的 View

ios - 收到 Push 崩溃时发布 NSNotification

ios - 在 applicationWillTerminate 中删除 Realm 数据

xcode - 为什么 UIBarbuttonItem 在代码编写得很好的情况下不能在 Swift 中工作?

ios - 如何防止图像超出图像框(或屏幕)

swift - 无需安装在 View 上即可访问 StateObject 的对象。这将每次创建一个新实例 - SwiftUI

ios - 在 Swift 中,当应用程序进入后台时,如何在 AppDelegate 中验证 NSTimer?

ios - 带有属性包装器的不可用属性