我想在 opening/creating NSDocument 时要求输入密码。在哪里放置提示?
I want to require a password when opening/creating a NSDocument. Where to put the prompt?
我真的不熟悉 macOS 开发,正在努力找出正确的方法来做这件事。场景:我的应用程序使用加密文档。这些是跨平台的,所以我无法更改加密机制(例如,直接使用 OS 提供的内容)。我也想稍后创建一个 iOS 应用程序,并尽可能多地共享代码。
流程是这样的:
- "Open" 或 "New" 一个新文档
- 提示用户输入密码
- (如果打开Document,请验证密码是否正确,否则重复步骤2直到正确或取消)
- 显示文档Window
所以我有这些 classes:
- MyEncryptedDocument,子classing NSDocument
- NSDocumentController,只使用默认
- NSWindow控制器,只使用默认
- NSWindow,直接使用默认值
- 我的ViewController, subclassing NSViewController
这都包含在一个main.storyboard中(考虑拆分,但首先要弄清楚正确的架构):
我在 MyEncryptedDocument
中实现了 read(from data: Data, ofType typeName: String)
,只是将内容作为字节数组读取。现在,这里是我要显示密码提示的地方,但 NSDocument class 似乎不是正确的地方 - 对于初学者来说,我没有 WindowController,并且 windowControllers
是空的(我假设之后调用了 makeWindowControllers)。
我一直在考虑 subclassing NSWindowController 或 NSWindow,但后来我想知道密码提示的正确位置在哪里? WindowController 中的 awakeFromNib
还没有文档,尽管我可以通过 makeWindowControllers
.
分配它
这给我留下了这些问题:
MyEncryptedDocument
真的应该只处理二进制加密数据吗?还是应该处理密码和解密的业务对象?
- 密码提示应该存在于 WindowController、Window、ViewController、Document、DocumentController 还是其他地方?
- 如果我想使用 NSDocument 已经具备的几乎所有 macOS 功能(自动保存、iCloud 支持、版本控制等)但只需要,implement/override 的正确方法是什么拦截 open/new 进程以询问用户密码?
我可以接受 Swift 或 Objective-C,因为我更关心 "Where" 而不是确切的 "How"。
下面是我现在的实现方式:
- 创建 NSDocumentController
的子class
- 在 AppDelegate 中,实例化 class - 这足以将其设置为应用程序的 DocumentController(只能有一个)
- 在子class中,为
makeUntitledDocumentOfType:error:
和makeDocumentWithContentsOfURL:ofType:error:
设置处理程序
- 在那里,我现在可以创建对话框来询问密码并创建(解密的)文档,或者 return 一个错误。
- MyEncryptedDocument(NSDocument 的子class)需要在其 init/constructor 中输入密码。这用于覆盖
readFromData:ofType:error:
和 dataOfType:error:
到 load/decrypt 和 save/encrypt 数据
在我看来,DocumentController 确实是应该处理这个问题的地方,因为 password/encryption 比实际文档或任何 UI 更多的是管道问题].总的来说,作为一个没有经验的 macOS 开发者,这 "feels" 适合我。我不确定 NSAlert 是否是对话框的正确 class;看着 Apple's Guidelines 我想我应该创建自己的 NSPanel 或 NSWindow。但这是以后的问题。
在 Xamarin C# 代码中,class 如下所示:
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 的一个非常简单的子class:
public partial class PasswordViewController : NSViewController
{
public string Password { get => tbPassphrase?.StringValue ?? ""; }
public PasswordViewController(IntPtr handle) : base(handle)
{
}
}
tbPassphrase
是视图中文本框的出口(.h 文件中的 @synthesize tbPassphrase = _tbPassphrase;
)。故事板是一个简单的场景 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>
我真的不熟悉 macOS 开发,正在努力找出正确的方法来做这件事。场景:我的应用程序使用加密文档。这些是跨平台的,所以我无法更改加密机制(例如,直接使用 OS 提供的内容)。我也想稍后创建一个 iOS 应用程序,并尽可能多地共享代码。
流程是这样的:
- "Open" 或 "New" 一个新文档
- 提示用户输入密码
- (如果打开Document,请验证密码是否正确,否则重复步骤2直到正确或取消)
- 显示文档Window
所以我有这些 classes:
- MyEncryptedDocument,子classing NSDocument
- NSDocumentController,只使用默认
- NSWindow控制器,只使用默认
- NSWindow,直接使用默认值
- 我的ViewController, subclassing NSViewController
这都包含在一个main.storyboard中(考虑拆分,但首先要弄清楚正确的架构):
我在 MyEncryptedDocument
中实现了 read(from data: Data, ofType typeName: String)
,只是将内容作为字节数组读取。现在,这里是我要显示密码提示的地方,但 NSDocument class 似乎不是正确的地方 - 对于初学者来说,我没有 WindowController,并且 windowControllers
是空的(我假设之后调用了 makeWindowControllers)。
我一直在考虑 subclassing NSWindowController 或 NSWindow,但后来我想知道密码提示的正确位置在哪里? WindowController 中的 awakeFromNib
还没有文档,尽管我可以通过 makeWindowControllers
.
这给我留下了这些问题:
MyEncryptedDocument
真的应该只处理二进制加密数据吗?还是应该处理密码和解密的业务对象?- 密码提示应该存在于 WindowController、Window、ViewController、Document、DocumentController 还是其他地方?
- 如果我想使用 NSDocument 已经具备的几乎所有 macOS 功能(自动保存、iCloud 支持、版本控制等)但只需要,implement/override 的正确方法是什么拦截 open/new 进程以询问用户密码?
我可以接受 Swift 或 Objective-C,因为我更关心 "Where" 而不是确切的 "How"。
下面是我现在的实现方式:
- 创建 NSDocumentController 的子class
- 在 AppDelegate 中,实例化 class - 这足以将其设置为应用程序的 DocumentController(只能有一个)
- 在子class中,为
makeUntitledDocumentOfType:error:
和makeDocumentWithContentsOfURL:ofType:error:
设置处理程序
- 在那里,我现在可以创建对话框来询问密码并创建(解密的)文档,或者 return 一个错误。
- MyEncryptedDocument(NSDocument 的子class)需要在其 init/constructor 中输入密码。这用于覆盖
readFromData:ofType:error:
和dataOfType:error:
到 load/decrypt 和 save/encrypt 数据
在我看来,DocumentController 确实是应该处理这个问题的地方,因为 password/encryption 比实际文档或任何 UI 更多的是管道问题].总的来说,作为一个没有经验的 macOS 开发者,这 "feels" 适合我。我不确定 NSAlert 是否是对话框的正确 class;看着 Apple's Guidelines 我想我应该创建自己的 NSPanel 或 NSWindow。但这是以后的问题。
在 Xamarin C# 代码中,class 如下所示:
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 的一个非常简单的子class:
public partial class PasswordViewController : NSViewController
{
public string Password { get => tbPassphrase?.StringValue ?? ""; }
public PasswordViewController(IntPtr handle) : base(handle)
{
}
}
tbPassphrase
是视图中文本框的出口(.h 文件中的 @synthesize tbPassphrase = _tbPassphrase;
)。故事板是一个简单的场景 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>