2017-01-03 2 views
2

Я пытаюсь реализовать элемент контекстного меню, который будет отображаться в сервисах при выборе текста. Так, например, если я выберу слово в TextEdit, я хочу, чтобы в контекстном меню был представлен пункт меню «Do my stuff», который будет подготавливать выбранное слово к моему коду приложения для дальнейшей обработки.Как зарегистрировать службу из приложения в приложении macOS?

По результатам небольшого исследования я пришел к выводу, что мне нужно внедрить и зарегистрировать услугу. Я пытался следовать документации Providing Services , но это кажется по крайней мере немного устаревшим (не говоря уже в некоторых местах неопределенным).

Из того, что я понял, я был в состоянии сделать следующее:

Я реализовал объект сервиса:

import Foundation 
import AppKit 

class ContextualMenuServiceProvider { 

    func importString(_ pasteBoard: Pasteboard, userData: String, error: String) { 
     print(">>>> in import string from service consumer") 
    } 
} 

Я создал запись в Info.plist для обслуживания:

<key>NSServices</key> 
    <array> 
     <dict> 
      <key>NSMenuItem</key> 
      <dict> 
       <key>default</key> 
       <string>Import to $(PRODUCT_NAME)</string> 
      </dict> 
      <key>NSMessage</key> 
      <string>importString</string> 
      <key>NSPortName</key> 
      <string>MyApp</string> 
      <key>NSSendTypes</key> 
      <array> 
       <string>public.plain-text</string> 
      </array> 
     </dict> 
    </array> 

И, наконец, я попытался зарегистрировать службу в AppDelegate. Теперь документация говорит использовать:

NSApp.setServicesProvider(encryptor) 
// where encryptor is an object of my ContextualMenuServiceProvider 

Тем не менее, кажется, что NSApp не setServicesProvider метода. Я пытался использовать:

NSApp.servicesProvider = ContextualMenuServiceProvider() 

Недвижимость, однако, похоже, что это тоже не работает.

Я тестирую его, запустив приложение из Xcode, а затем, пока приложение запущено, я пытаюсь выбрать какой-либо текст в TextEdit (его нельзя ограничивать TextEdit, я просто использую его в качестве примера), и после щелчка правой кнопкой мыши я ищу свой пункт меню. Это не работает.

Я смог найти какие-то похожие вопросы SO (например, Creating an os x service, или Howto add menu item to Mac OS Finder in Delphi XE2), но я не смог обнаружить, что я делаю с ними неправильно (не говоря уже о том, что большинство из них являются старыми (используя setServiceProvider), или используя такие языки, как C# или Delphi).

Любая идея, что не так с моим кодом/подходом?

ответ

1

Хорошо, похоже, я сделал несколько небольших ошибок, которые заставляют меня думать, что он не работает. Однако, в конце концов, я смог заставить его работать. Так что, если вы столкнулись с теми же задачами, здесь есть решение в Swift 3:

(1) Вы должны реализовать метод обслуживания:

import Cocoa 

class ServiceProvider: NSObject { 

    let errorMessage = NSString(string: "Could not find the text for parsing.") 

    func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>) { 
     guard let str = pasteboard.string(forType: NSStringPboardType) else { 
      error.pointee = errorMessage 
      return 
     } 

     let alert = NSAlert() 
     alert.messageText = "Hello \(str)" 
     alert.informativeText = "Welcome in the service" 
     alert.addButton(withTitle: "OK") 
     alert.runModal() 
    } 
} 

Здесь важно то, что я не знал раньше было то, что объект, предоставляющий услугу, должен подклассифицировать NSObject (он также может быть ViewController или любым другим подклассом NSObject). API-интерфейс служб использует селектор для его вызова, а технология выбора работает только с NSObject.

Кроме того, service метод должен иметь такое заявление: он принимает 3 аргумента, первый является NSPasteboard (вы можете использовать Pasteboard, но это не дает string метод), не маркированы, второй является факультативным String маркированы userData, третий AutoreleasingUnsafeMutablePointer<NSString> с пометкой error. Следуйте за этим, чтобы получить наилучшую безопасность по типу, все еще делая работу. В противном случае API-интерфейс служб не сможет найти его и вызвать.Вы можете использовать UnsafeRawPointers для всех своих аргументов, но вы ничего не получите от этого.

(2) В приложение делегата (или документация говорит, что вы можете сделать это где угодно, но я делаю это здесь) регистрации поставщика услуг:

import Cocoa 

@NSApplicationMain 
class AppDelegate: NSObject, NSApplicationDelegate { 

    @IBOutlet weak var window: NSWindow! 

    func applicationDidFinishLaunching(_ aNotification: Notification) { 

     NSApplication.shared().servicesProvider = ServiceProvider() 

     NSUpdateDynamicServices() 
    } 

} 

Это, кажется, работает также без NSUpdateDynamicServices звоните, но я думаю, что лучше быть в безопасности, чем извините - он должен обновить службы в системе, чтобы вам не приходилось выходить из системы и вводить пользователя в систему, чтобы получить текущую версию сервиса.

(3) И, наконец, вы должны настроить info.plist рекламировать свой метод сервиса:

<key>NSServices</key> 
<array> 
    <dict> 
     <key>NSMessage</key> 
     <string>service</string> 

     <key>NSPortName</key> 
     <string>serviceTest</string> 

     <key>NSMenuItem</key> 
     <dict> 
      <key>default</key> 
      <string>Test hello world</string> 
     </dict> 

     <key>NSRestricted</key> 
     <false/> 

     <key>NSRequiredContext</key> 
     <dict/> 

     <key>NSSendTypes</key> 
     <array> 
      <string>NSStringPboardType</string> 
     </array> 
    </dict> 
</array> 

Это является источником XML из Info.plist - хотя компания Apple рекомендует использовать их редактор, в Xcode 8.2 Мне не удалось добавить ключ NSRequiredContext через редактор - мне пришлось открыть файл как исходный код и добавить его вручную. Я бы рекомендовал редактировать напрямую источник XML.

Вы можете найти документацию о значении каждого ключа в Services Properties, но я хотел бы упомянуть некоторые важные моменты. Во-первых, вы НЕОБХОДИМО использовать ключ NSRequiredContext - они упоминают его в документации, но редактор его не поддерживает - прямое редактирование XML и добавление его даже пустым (как и я). Во-вторых, если вы используете NSSendTypes или и хотите работать со строками, используйте NSStringPboardType, хотя его documentation говорит, что он устарел и должен быть заменен NSPasteboardTypeString - последний не будет работать. Наконец, NSMessage ключ с service Значение - это имя метода услуги. Так что мой объект поставщик услуг объявляет следующий метод:

func service(_ pasteboard: NSPasteboard, userData: String?, error: AutoreleasingUnsafeMutablePointer<NSString>) 

Я использую значение service в NSMessage. Если вы хотите изменить его (например, вы хотите несколько методов обслуживания), просто не забывайте, что эти два должны соответствовать (значение в конфигурации Info.plist и имя метода службы).

(4) В этом состоянии он должен работать. Есть одна вещь, которую я хотел бы упомянуть, которая может помочь вам в отладке. Проверьте это с помощью метода, упомянутого в documentation в конце команды:

/Applications/TextEdit.app/Contents/MacOS/TextEdit -NSDebugServices com.mycompany.myapp 

Запуск этого из терминала должен сообщить, зарегистрирована ли какие-либо услуги, используя при условии сверток, а также будет ли он представлен - например, в моем случае проблема заключалась в том, что я не предоставил обязательный NSRequiredContext. После тестирования с использованием этого подхода я смог узнать, что служба была установлена, и проблема заключалась в том, что API-интерфейс служб предполагал, что он должен быть отфильтрован. После некоторых экспериментов и googling я решил это, добавив пустой NSRequiredContext.

После каждого изменения службы я рекомендую закрыть TextEdit и запустить ее снова, кажется, что она содержит ссылку на старый объект поставщика услуг (или что-то в этом роде, изменения не были зарегистрированы TextEdit, если я не перезапустил Это).