防止 NSTableView 在用户填充值之前保存新行
Prevent NSTableView from saving new row until user fills values
我正在为我的应用开发首选项 window,用户可以在其中使用 NSTableView
添加和删除预设。预设定义为:
struct Preset: Codable {
var name: String
var value: Int
}
这些预设应该保留到 UserDefaults
。我的目标是尽可能少写代码,少写的应该是通用代码,所以我的项目使用了NSUserDefaultsController
、NSArrayController
和Cocoa绑定。鉴于数组、字符串和整数类型在 UserDefaults
使用的 属性 列表中可用以存储这些设置,我决定使用这些而不是不透明的序列化数据类型。
此外,为了便于其余代码访问这些预设,我创建了一个带有 属性 presets
类型 [Preset]
的设置单例对象,它实现了计算 getters 和设置器从 UserDefaults 中检索它。
我已经将 MCVE 上传到 GitHub。它每秒记录 presets
属性 的值,以证明我需要帮助解决的问题——除了这两个问题,我相信一切正常。
第一个问题是我想在我的 NSTableView
中转到基于视图的内容模式,但是当我进行此更改并恢复另一个时,对 NSTableView
没有保存到 UserDefaults
。它们在基于单元格的内容模式下工作良好,这是我在 MCVE 中使用的模式。由于我是 Cocoa/Swift 新手,我非常感谢有关此项目中需要更改哪些内容的分步说明,以便迁移到基于视图的内容模式,同时保留编辑条目的功能NSTableView
.
编辑:看起来我在这个问题上是一个人:请参阅 Cocoa 邮件列表中的 this project, as well as this 主题。
然而,第二个问题是最严重的:当我按下 +
按钮向我的 NSTableView
添加新行时,最初 "name" 和 "value" 列为空,直到用户向其添加一些 text/values。但是,绑定将一个新的空行(没有 name
和 value
属性)保存到 UserDefaults
。这可以通过注释掉 printPresets()
中的代码看出。例如,在添加新行之前,这是 plist 的内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>presets</key>
<array>
<dict>
<key>name</key>
<string>First preset</string>
<key>value</key>
<integer>1</integer>
</dict>
<dict>
<key>name</key>
<string>Second preset</string>
<key>value</key>
<integer>2</integer>
</dict>
</array>
</dict>
</plist>
按下+
后,这是plist的新内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>presets</key>
<array>
<dict>
<key>name</key>
<string>First preset</string>
<key>value</key>
<integer>1</integer>
</dict>
<dict>
<key>name</key>
<string>Second preset</string>
<key>value</key>
<integer>2</integer>
</dict>
<dict/>
</array>
</dict>
</plist>
注意第二个预设后的额外 <dict/>
。
如果 getter 在设置处于此状态时运行,并且有一个空行,getter 会尝试从 plist 中读取此行,但由于缺少 name
和 value
属性。具体来说,我收到以下错误:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "name",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_PlistKey(stringValue: "Index 2", intValue: 2)], debugDescription:
"No value associated with key CodingKeys(stringValue: \"name\",
intValue: nil) (\"name\").", underlyingError: nil))
我不知道如何解决这个问题。我试过一件事,但没有成功,然后想到了另一件事,我可以就如何实施提出一些建议。如果有人建议我没有想到的其他替代方案,我也将不胜感激。
我尝试将 sharedUserDefaultsController.appliesImmediately
设置为 false
,添加一个 "Save" 按钮并将其绑定到 sharedUserDefaultsController
的 save:
行动。当我这样做时,我的预设没有加载。另一方面,我可以使用 +
按钮添加新行,而不会立即使应用程序崩溃。只要在按下 "Save" 按钮之前向该行添加名称和值,应用程序就不会崩溃。但是,如果我尝试在不填写名称和/或值字段的情况下进行保存,应用程序仍然会崩溃。所以,不是一个好的解决方案。也许我可以将 "Save" 按钮变灰,除非字段已正确填写?但是话又说回来,如果都一样,我想为此使用 "save-less" 范式 window.
我看到的另一种可能性是在创建新行时用一些默认数据填充 "name" 和 "value" 字段,这在某种程度上是合理的。我找到了 "Placeholder" 属性,但它不合适:它只是给用户的一个建议,直到用户真正填写一些东西,这些字段都是留空的,不会写入 plist。
我通过将 NSArrayController
的属性检查器中 "Object Controller" 下的 "Class Name" 更改为我的 MyModuleName.Preset
来解决这个问题,现在声明为 class:
class Preset: NSObject, Codable {
let name: String
let value: Int
override init() {
self.name = "A preset"
self.setpoint = 1
super.init()
}
init(name: String, value: Int) {
self.name = name
self.value = value
super.init()
}
}
通过在 init()
(没有参数的版本)中提供默认值,当创建新行时,它会填充这些默认值,从而使问题消失。
我正在为我的应用开发首选项 window,用户可以在其中使用 NSTableView
添加和删除预设。预设定义为:
struct Preset: Codable {
var name: String
var value: Int
}
这些预设应该保留到 UserDefaults
。我的目标是尽可能少写代码,少写的应该是通用代码,所以我的项目使用了NSUserDefaultsController
、NSArrayController
和Cocoa绑定。鉴于数组、字符串和整数类型在 UserDefaults
使用的 属性 列表中可用以存储这些设置,我决定使用这些而不是不透明的序列化数据类型。
此外,为了便于其余代码访问这些预设,我创建了一个带有 属性 presets
类型 [Preset]
的设置单例对象,它实现了计算 getters 和设置器从 UserDefaults 中检索它。
我已经将 MCVE 上传到 GitHub。它每秒记录 presets
属性 的值,以证明我需要帮助解决的问题——除了这两个问题,我相信一切正常。
第一个问题是我想在我的 NSTableView
中转到基于视图的内容模式,但是当我进行此更改并恢复另一个时,对 NSTableView
没有保存到 UserDefaults
。它们在基于单元格的内容模式下工作良好,这是我在 MCVE 中使用的模式。由于我是 Cocoa/Swift 新手,我非常感谢有关此项目中需要更改哪些内容的分步说明,以便迁移到基于视图的内容模式,同时保留编辑条目的功能NSTableView
.
编辑:看起来我在这个问题上是一个人:请参阅 Cocoa 邮件列表中的 this project, as well as this 主题。
然而,第二个问题是最严重的:当我按下 +
按钮向我的 NSTableView
添加新行时,最初 "name" 和 "value" 列为空,直到用户向其添加一些 text/values。但是,绑定将一个新的空行(没有 name
和 value
属性)保存到 UserDefaults
。这可以通过注释掉 printPresets()
中的代码看出。例如,在添加新行之前,这是 plist 的内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>presets</key>
<array>
<dict>
<key>name</key>
<string>First preset</string>
<key>value</key>
<integer>1</integer>
</dict>
<dict>
<key>name</key>
<string>Second preset</string>
<key>value</key>
<integer>2</integer>
</dict>
</array>
</dict>
</plist>
按下+
后,这是plist的新内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>presets</key>
<array>
<dict>
<key>name</key>
<string>First preset</string>
<key>value</key>
<integer>1</integer>
</dict>
<dict>
<key>name</key>
<string>Second preset</string>
<key>value</key>
<integer>2</integer>
</dict>
<dict/>
</array>
</dict>
</plist>
注意第二个预设后的额外 <dict/>
。
如果 getter 在设置处于此状态时运行,并且有一个空行,getter 会尝试从 plist 中读取此行,但由于缺少 name
和 value
属性。具体来说,我收到以下错误:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "name",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_PlistKey(stringValue: "Index 2", intValue: 2)], debugDescription:
"No value associated with key CodingKeys(stringValue: \"name\",
intValue: nil) (\"name\").", underlyingError: nil))
我不知道如何解决这个问题。我试过一件事,但没有成功,然后想到了另一件事,我可以就如何实施提出一些建议。如果有人建议我没有想到的其他替代方案,我也将不胜感激。
我尝试将
sharedUserDefaultsController.appliesImmediately
设置为false
,添加一个 "Save" 按钮并将其绑定到sharedUserDefaultsController
的save:
行动。当我这样做时,我的预设没有加载。另一方面,我可以使用+
按钮添加新行,而不会立即使应用程序崩溃。只要在按下 "Save" 按钮之前向该行添加名称和值,应用程序就不会崩溃。但是,如果我尝试在不填写名称和/或值字段的情况下进行保存,应用程序仍然会崩溃。所以,不是一个好的解决方案。也许我可以将 "Save" 按钮变灰,除非字段已正确填写?但是话又说回来,如果都一样,我想为此使用 "save-less" 范式 window.我看到的另一种可能性是在创建新行时用一些默认数据填充 "name" 和 "value" 字段,这在某种程度上是合理的。我找到了 "Placeholder" 属性,但它不合适:它只是给用户的一个建议,直到用户真正填写一些东西,这些字段都是留空的,不会写入 plist。
我通过将 NSArrayController
的属性检查器中 "Object Controller" 下的 "Class Name" 更改为我的 MyModuleName.Preset
来解决这个问题,现在声明为 class:
class Preset: NSObject, Codable {
let name: String
let value: Int
override init() {
self.name = "A preset"
self.setpoint = 1
super.init()
}
init(name: String, value: Int) {
self.name = name
self.value = value
super.init()
}
}
通过在 init()
(没有参数的版本)中提供默认值,当创建新行时,它会填充这些默认值,从而使问题消失。