Я хочу требовать пароль при открытии / создании NSDocument.Где поставить подсказку? - PullRequest
0 голосов
/ 28 декабря 2018

Я действительно незнаком с разработкой MacOS и пытаюсь найти правильный способ сделать это.Сценарий: мое приложение работает с зашифрованными документами.Они кроссплатформенные, поэтому я не могу изменить механизм шифрования (например, использовать что-то, предоставленное непосредственно ОС).Я также хочу позже создать приложение для iOS и поделиться как можно большим количеством кода.

Поток предназначен для следующего:

  1. Либо "Открыть", либо "Новый" aновый документ
  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 или где-либо еще?
  • Какие надлежащие методы следует реализовать/ переопределить, если я хочу использовать почти все функции macOS, которые NSDocument уже делает (автосохранение, поддержка iCloud, управление версиями и т. д.), но хочет только перехватить открытый / новый процесс, чтобы попросить пользователя ввести пароль?

Я в порядке со Swift или Objective-C, так как меня больше интересует «Где», а не точное «Как».

1 Ответ

0 голосов
/ 28 декабря 2018

Вот как я реализовал это сейчас:

  • Создание подкласса NSDocumentController
  • В AppDelegate создайте экземпляр этого класса - этого достаточно, чтобы установить его как DocumentController для приложения (может быть только один)
  • В подклассе установите обработчики для makeUntitledDocumentOfType:error: и makeDocumentWithContentsOfURL:ofType:error:
  • Там я теперь могу создать диалогчтобы запросить пароль и либо создать (расшифрованный) документ, либо вернуть ошибку.
  • MyEncryptedDocument (подкласс NSDocument) требует пароль в своем init / constructor.Это используется в переопределенных readFromData:ofType:error: и dataOfType:error: для загрузки / дешифрования и сохранения / шифрования данных

DocumentController действительно, кажется, место, которое должно иметь дело с этим, по моему мнению, так какпароль / шифрование - это скорее проблема конвейера, чем проблема самого документа или любого пользовательского интерфейса.В целом, это "чувствует" меня как неопытного разработчика 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>
...