在 SwiftUI 中理解通信模式"Preference Key"的问题



UPDATE - 我已经更新了整个问题,因为我觉得我对问题的呈现非常糟糕!


我实际上刚刚开始使用SwiftUI,所以如果这个问题很愚蠢,请原谅。我从几天开始尝试,阅读许多不同的帖子并尝试遵循 apple.com 的官方文档,但不幸的是无法了解首选项键在 SwiftUI 中的工作方式。

基本上,我的测试应用程序由3个不同的组件组成:

ContentView
// contains a form and a text field in an HStack
∟ FormView
// contains the SubFormViews in a TabView
L SubFormView
// contains the form fields

我想要实现的是信息流从实际表单字段到主视图,然后更新文本字段。从我目前阅读的内容来看,PreferenceKey似乎是解决此问题的最佳选择。因此,我的块视图如下所示:

我的子窗体视图如下所示:

import SwiftUI
// --------------------
//  The preference key
// --------------------
struct FormFieldKey: PreferenceKey {
static var defaultValue = FormFieldData() // <-- I initialise the values in FormFieldData
static func reduce(value: inout FormFieldData, nextValue: () -> FormFieldData) {
value = nextValue()
}
}
// ---------------------
//  The form field data
// ---------------------
struct FormFieldData: Equatable {
var firstName: String
// Initialise the form fields
init() {
self.firstName = ""
}
}
// -------------------------
//  The actual subform view
// -------------------------
struct SubFormView: View {
@State private var formFields = FormFieldData() // <-- I set the state vars for this view to FormFieldData
var body: some View {
Form {
TextField("First Name", text: $formFields.firstName)
.preference(key: FormFieldKey.self, value: self.formFields)
// If I understood right, I set the Preference Key value here.
// The value gets overridden by formFields.
}.padding()
}
}

然后,我的FormView是:

import SwiftUI
struct FormView: View {
var body: some View {
TabView() {  // <-- I intend to put additional SubFormViews in different tabs
SubFormView()
.tabItem {
Text("Sub Form")
}
}
}
}

最后,我的内容视图是:

import SwiftUI
struct ContentView: View {
@State private var test: FormFieldData? // <-- I'm not sure why this has to be optional
var body: some View {
HStack {
FormView()
.onPreferenceChange(FormFieldKey.self) { self.test = $0 } // <-- I literally have no clue what I'm supposed to do here!
Text("(self.test!.firstName)") // This is what I would like to achieve
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}

再次,如果这是一个愚蠢的问题,我很抱歉,但我真的被困住了,根本无法弄清楚这是如何工作的......

感谢您的帮助!

由于似乎没有人可以帮助解决原始问题,因此我遵循了@Asperi的评论并设法通过ObservableObject解决了问题。我仍然不确定这是否是正确的方法,但至少它对我知道有用。

由于我是 SWIFTUI 的完全菜鸟,这显然不是一个完美的解决方案(例如数据模型(,但以防其他人遇到同样的问题,这是我的解决方案:

首先,我创建了一个类FormFields作为ObservableObject

import Foundation
class FormFields: ObservableObject {
@Published var formData = FormData()
}
// -----------------------
// FORM DATA
// -----------------------
// Combines all data of different form blocks and makes
// it accessible throughout the app.
struct FormData {
var block1 = Block1Data()
// you could add as many blocks as you wish
}

// -----------------------
// BLOCK 1 DATA
// -----------------------
// Data fields for block 1. (in this case it's all Strings)
struct Block1Data {
var firstField: String
var secondField: String
var thirdField: String
// initialise form fields with empty strings (this is not essential,
// you could also do var firstField = ""
init() {
self.firstField = ""
self.secondField = ""
self.thirdField = ""
}
}

然后子窗体视图将如下所示

import SwiftUI
struct SubFormView: View {
// inject the environment variable "formFields"
@EnvironmentObject var formFields: FormFields
var body: some View {
Form {
TextField("First Field", text: $formFields.formData.block1.firstField)
TextField("Second Field", text: $formFields.formData.block1.secondField)
TextField("Third Field", text: $formFields.formData.block1.thirdField)
}
}
}
// if you want your preview working for this specific sub view, don't forget
// to add the environmentObject() to the view!
struct CompanyDataFormView_Previews: PreviewProvider {
static var previews: some View {
CompanyDataFormView().environmentObject(FormFields()) // <-- this here!
}
}

窗体视图不会更改,因为没有任何需要更新的内容

import SwiftUI
struct FormView: View {
var body: some View {
TabView() {
SubFormView()
.tabItem {
Text("Sub Form")
}
}
}
}

然后,内容视图将是

import SwiftUI
struct ContentView: View {
@EnvironmentObject var formFields: FormFields
var body: some View {
HStack {
FormView()
Text("Output any text from the form fields now! E.g. firstField = (formFields.formData.block1.firstField)")
}
}
}
// again, if you wish to see the preview then you need to attach the environmentObject
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(FormFields())
}
}

为了使这项工作正常工作,需要完成最后一步!

您需要在AppDelegate中设置环境变量并将其附加到ContentView。这是我的应用代表

import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
// set the environment variable for the form fields
var formFields = FormFields()
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view. 
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView.environmentObject(formFields)) // <-- attach it to ContentView
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}

我希望这会在开发过程中保护某人一点时间......

最新更新