macos - 我想在打开/创建 NSDocument 时要求输入密码。在哪里放置提示?
问题描述
我真的不熟悉 macOS 开发,并试图找出正确的方法来做到这一点。场景:我的应用程序使用加密文档。这些是跨平台的,所以我不能改变加密机制(例如,直接使用操作系统提供的东西)。我还想稍后创建一个 iOS 应用程序,并尽可能多地共享代码。
该流程旨在:
- “打开”或“新建”一个新文档
- 提示用户输入密码
- (如果打开一个Document,验证密码是否正确,否则重复步骤2直到正确或取消)
- 显示文档窗口
所以我有这些课程:
- MyEncryptedDocument,子类化 NSDocument
- NSDocumentController,只使用默认的
- NSWindowController,只使用默认的
- NSWindow,只使用默认的
- MyViewController,继承 NSViewController
这都包含在一个 main.storyboard 中(考虑拆分,但首先要弄清楚正确的架构):
我已经read(from data: Data, ofType typeName: String)
在MyEncryptedDocument
, 中实现了将内容作为字节数组读取。现在,在这里我将显示密码提示,但似乎 NSDocument 类不是正确的地方 - 对于初学者来说,我没有 WindowController,并且windowControllers
是空的(我假设 makeWindowControllers 之后被调用) .
我一直在考虑继承 NSWindowController 或 NSWindow,但是我想知道密码提示的正确位置在哪里?awakeFromNib
在 WindowController 中还没有 Document,虽然我可以通过makeWindowControllers
.
这给我留下了这些问题:
- 实际上应该
MyEncryptedDocument
只处理二进制加密数据吗?还是应该处理密码和解密的业务对象? - 密码提示应该存在于 WindowController、Window、ViewController、Document、DocumentController 还是其他地方?
- 如果我想使用 NSDocument 已经具备的几乎所有 macOS 功能(自动保存、iCloud 支持、版本控制等)但只想拦截打开/新进程以询问用户,那么实现/覆盖的正确方法是什么密码?
我对 Swift 或 Objective-C 都满意,因为我更关心“在哪里”而不是确切的“如何”。
解决方案
这是我现在实现它的方式:
- 创建 NSDocumentController 的子类
- 在 AppDelegate 中,实例化该类 - 足以将其设置为应用程序的DocumentController(只能有一个)
makeUntitledDocumentOfType:error:
在子类中,为和设置处理程序makeDocumentWithContentsOfURL:ofType:error:
- 在那里,我现在可以创建对话框来询问密码并创建(解密的)文档,或者返回错误。
- MyEncryptedDocument(NSDocument 的子类)在其 init/constructor 中需要密码。这用于覆盖
readFromData:ofType:error:
和dataOfType:error:
加载/解密和保存/加密数据
在我看来,DocumentController 似乎确实是应该处理这个问题的地方,因为密码/加密更多的是管道问题,而不是实际文档或任何 UI 的问题。总的来说,作为一个没有经验的 macOS 开发人员,这对我来说“感觉”是正确的。我不确定 NSAlert 是否是对话框的正确类;查看Apple 的指南,我认为我应该创建自己的 NSPanel 或 NSWindow。但这是以后的问题。
在 Xamarin C# 代码中,该类如下所示:
public class MyEncryptedDocumentController : NSDocumentController
{
public MyEncryptedDocumentController()
{
}
// makeUntitledDocumentOfType:error:
public override NSObject MakeUntitledDocument(string typeName, out NSError error)
{
return LoadOrCreateDocument(typeName, null, out error);
}
// makeDocumentWithContentsOfURL:ofType:error:
public override NSObject MakeDocument(NSUrl url, string typeName, out NSError outError)
{
return LoadOrCreateDocument(typeName, url, out outError);
}
private MyEncryptedDocument LoadOrCreateDocument(string typeName, NSUrl url, out NSError error)
{
error = null;
using (var sb = NSStoryboard.FromName("PasswordView", null))
using (var ctrl = sb.InstantiateControllerWithIdentifier("Password View Controller") as PasswordViewController)
using (var win = new NSAlert())
{
win.MessageText = "Please enter the Password:";
//win.InformativeText = "Error message goes here.";
win.AlertStyle = NSAlertStyle.Informational;
win.AccessoryView = ctrl.View;
var btnOK = win.AddButton("OK");
var btnCancel = win.AddButton("Cancel");
var res = win.RunModal();
var pw = ctrl.Password;
if (res == (int)NSAlertButtonReturn.First)
{
var doc = new MyEncryptedDocument(pw);
if (url != null)
{
if (!doc.ReadFromUrl(url, typeName, out error))
{
// Could check if error is a custom "Wrong Password"
// and then re-open the Alert, setting the Informational Text
// to something like "wrong password"
return null;
}
}
return doc;
}
// MyEncryptedDocument.Domain is a NSString("com.mycompany.myapplication");
// MyErrorCodes is just a custom c# enum
error = new NSError(MyEncryptedDocument.Domain, (int)MyErrorCodes.PasswordDialogCancel);
return null;
}
}
}
PasswordViewController
是 NSViewController 的一个非常简单的子类:
public partial class PasswordViewController : NSViewController
{
public string Password { get => tbPassphrase?.StringValue ?? ""; }
public PasswordViewController(IntPtr handle) : base(handle)
{
}
}
tbPassphrase
是视图中文本框的出口(@synthesize tbPassphrase = _tbPassphrase;
在 .h 文件中)。故事板是带有 viewController 的简单场景:
<viewController storyboardIdentifier="Password View Controller" id="5LL-3u-LyJ" customClass="PasswordViewController" sceneMemberID="viewController">
<view key="view" id="yoi-7p-9v6">
<rect key="frame" x="0.0" y="0.0" width="315" height="22"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<secureTextField identifier="tfPassphrase" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="YmM-nK-9Hb">
<rect key="frame" x="0.0" y="0.0" width="315" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="ChX-i5-luo">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
</secureTextField>
</subviews>
</view>
<connections>
<outlet property="tbPassphrase" destination="YmM-nK-9Hb" id="sCC-Ve-8FO"/>
</connections>
</viewController>
推荐阅读
- .net - 错误 NU1605 检测到包降级
- orm - django orm join 没有 foiriegn 键
- linux - 有没有办法在没有 pg_dump 的情况下迁移 postgresql db?
- php - PHP/MySQL - 如何从表中选择 id 并在数组中回显
- logging - iis7/iis10 应用程序池重启/关闭日志在哪里?我怎样才能找到它?
- json - 如何使用 Python jsonpath_ng 过滤器和其他扩展功能?
- macos - mac sed 在匹配的内容之前添加新行
- tidb - 如何删除离线集群节点的监控数据?
- oracle-apex - 将选定记录从交互式网格复制到另一个 Oracle apex5.1
- python - 如何使用 Python 脚本运行 Excel 宏?