在 SwiftUI 中理解通信模式“Preference Key”的问题
Posted
技术标签:
【中文标题】在 SwiftUI 中理解通信模式“Preference Key”的问题【英文标题】:Problems with understanding communication pattern "Preference Key" in SwiftUI 【发布时间】:2020-05-22 08:18:02 【问题描述】:更新 - 我已经更新了整个问题,因为我觉得我对问题的描述很糟糕!
我刚刚开始玩SwiftUI,所以如果这个问题很愚蠢,请原谅。几天以来,我一直在尝试,阅读了许多不同的帖子,并尝试遵循 apple.com 上的官方文档,但不幸的是,我无法理解 Preference Keys 在 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
似乎是解决这个问题的最佳选择。因此,我的块视图如下所示:
我的 SubFormView 看起来像这样:
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")
最后我的 ContentView 是:
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)
再次,如果这是一个愚蠢的问题,我很抱歉,但我真的被卡住了,根本无法理解这是如何工作的......
感谢您的帮助!
【问题讨论】:
注入 ObservableObject 作为视图模型在这种情况下会更方便。 @Asperi - 谢谢你的回答!我看了看,但第一个我无法真正让它与ObservableObject
一起工作,第二个我认为首选的方法是通过PreferenceKey
(从孩子到远方的父母)。
from Child to distant Parent
对于子视图状态 - 是的,对于模型数据......我不会,但如你所愿 - 这是你的游戏。 ))
我对任何事情都持开放态度!我对ObservableObject
的问题是我不知道如何告诉Observable 它需要成为TextField 的值。我能找到的唯一属性是.preference
...你能给我一个提示吗?
从远处看,您的问题看起来很像swiftui-lab.com/communicating-with-the-view-tree-part-3 正在做的事情,这是通过 PreferencesKeys 完成的
【参考方案1】:
由于似乎没有人可以帮助解决原始问题,因此我关注了@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 = ""
然后 SubformView 看起来像
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!
FormView 不会改变,因为没有什么需要更新
import SwiftUI
struct FormView: View
var body: some View
TabView()
SubFormView()
.tabItem
Text("Sub Form")
然后 ContentView 将是
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
。这是我的 AppDelegate
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
我希望这可以在开发过程中为某人节省一点时间......
【讨论】:
以上是关于在 SwiftUI 中理解通信模式“Preference Key”的问题的主要内容,如果未能解决你的问题,请参考以下文章
如何允许对不在编辑模式下的 SwiftUI 列表中的行进行重新排序?
如何知道 UIKit 视图控制器中的 swiftUI 事件? ||如何提供 swiftUI 和 uikit 之间的通信