首页 > 解决方案 > 我想在打开/创建 NSDocument 时要求输入密码。在哪里放置提示?

问题描述

我真的不熟悉 macOS 开发,并试图找出正确的方法来做到这一点。场景:我的应用程序使用加密文档。这些是跨平台的,所以我不能改变加密机制(例如,直接使用操作系统提供的东西)。我还想稍后创建一个 iOS 应用程序,并尽可能多地共享代码。

该流程旨在:

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

所以我有这些课程:

这都包含在一个 main.storyboard 中(考虑拆分,但首先要弄清楚正确的架构):

main.storyboard 结构

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

我一直在考虑继承 NSWindowController 或 NSWindow,但是我想知道密码提示的正确位置在哪里?awakeFromNib在 WindowController 中还没有 Document,虽然我可以通过makeWindowControllers.

这给我留下了这些问题:

我对 Swift 或 Objective-C 都满意,因为我更关心“在哪里”而不是确切的“如何”。

标签: macosappkitfoundation

解决方案


这是我现在实现它的方式:

  • 创建 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>

推荐阅读