我想在打开/创建NSDocument时要求输入密码。提示放在哪里?

时间:2018-12-28 14:40:13

标签: macos appkit foundation

我真的不熟悉macOS的开发,并试图找出正确的方法来进行此操作。场景:我的应用程序使用加密的文档。这些是跨平台的,因此我无法更改加密机制(例如,直接使用OS提供的功能)。我还想稍后创建一个iOS应用,并共享尽可能多的代码。

流程应为:

  1. “打开”或“新建”新文​​档
  2. 提示用户输入密码
  3. (如果打开文档,请确认密码正确,否则重复步骤2直到密码正确或被取消)
  4. 显示文档窗口

所以我有这些课程:

  • MyEncryptedDocument,子类化NSDocument
  • NSDocumentController,仅使用默认值
  • NSWindowController,仅使用默认值
  • NSWindow,仅使用默认值
  • MyViewController,子类化NSViewController

所有内容都包含在一个main.storyboard中(考虑拆分,但首先要弄清楚正确的体系结构):

main.storyboard structure

我在read(from data: Data, ofType typeName: String)中实现了MyEncryptedDocument,只是将内容读取为字节数组。现在,在这里我将显示密码提示,但是看来NSDocument类不是正确的位置-对于初学者,我没有WindowController,并且windowControllers为空(我假设之后会调用makeWindowControllers。)

我一直在考虑将NSWindowController或NSWindow子类化,但是然后我想知道密码提示的正确位置在哪里?尽管我可以通过awakeFromNib进行分配,但WindowController中的makeWindowControllers还没有文档。

这让我有以下问题:

  • MyEncryptedDocument应该只处理二进制的加密数据吗?还是应该处理密码和解密的业务对象?
  • 密码提示是否应该存在于WindowController,Window,ViewController,Document,DocumentController或其他地方?
  • 如果我想使用NSDocument已经完成的几乎所有macOS功能(自动保存,iCloud支持,版本控制等),但只想拦截打开/新进程询问的正确方法是什么?用户输入密码?

我对Swift或Objective-C都很满意,因为我更关心“ Where”,而不关心确切的“ How”。

1 个答案:

答案 0 :(得分:1)

这是我现在实现的方式:

  • 创建NSDocumentController的子类
  • 在AppDelegate中,实例化该类-足以将其设置为应用程序的 DocumentController(只能有一个)
  • 在子类中,为makeUntitledDocumentOfType:error:makeDocumentWithContentsOfURL:ofType:error:设置处理程序
  • 现在,我可以在其中创建对话框以询问密码,然后创建(解密的)文档,或者返回错误。
  • MyEncryptedDocument(NSDocument的子类)在其init / constructor中要求输入密码。在覆盖的readFromData:ofType:error:dataOfType:error:中使用它来加载/解密以及保存/加密数据

在我看来,DocumentController确实似乎是应该处理此问题的地方,因为密码/加密更像是管道问题,而不是实际文档或任何UI。总的来说,这对我来说是经验不足的macOS开发人员。我不确定NSAlert是否是对话框的正确类;看Apple's Guidelines,我认为我应该创建自己的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是视图中文本框的出口(.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>