当你用习惯了Windows,刚换到Mac会有各种不适应,比如右键菜单里不能新建txt文件,用惯了图形界面操作,svn、git都要使用命令行来操作,打开终端进入当前目录也要命令行,对于很多非开发人员来说这些操作很不友好,即使是开发人员我也想减少操作步骤,节省时间。
以上这些问题通过Finder Sync扩展都可以解决,你可以通过它来为你的右键菜单增加"新建txt"、“解压rar”、"当前目录打开终端"等功能项,你也可以把命令行使用的svn、git包装成图形界面工具,使用右键菜单和工具栏来灵活使用它。
总结Finder Sync扩展的使用场景:可以为你改造finder的右键菜单和工具栏菜单,至于点击菜单可以实现的功能你可以自由发挥,如果要上架到Appstore,功能设计上要考虑沙盒的局限性和权限的适用范围。
import Cocoa
import FinderSync
class FinderSync: FIFinderSync {
var myFolderURL = URL(fileURLWithPath: "/Users/Shared/MySyncExtension Documents")
override init() {
super.init()
NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString)
// Set up the directory we are syncing.
FIFinderSyncController.default().directoryURLs = [self.myFolderURL]
// Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.colorPanelName)!, label: "Status One" , forBadgeIdentifier: "One")
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.cautionName)!, label: "Status Two", forBadgeIdentifier: "Two")
}
// MARK: - Primary Finder Sync protocol methods
override func beginObservingDirectory(at url: URL) {
// The user is now seeing the container's contents.
// If they see it in more than one view at a time, we're only told once.
NSLog("beginObservingDirectoryAtURL: %@", url.path as NSString)
}
override func endObservingDirectory(at url: URL) {
// The user is no longer seeing the container's contents.
NSLog("endObservingDirectoryAtURL: %@", url.path as NSString)
}
override func requestBadgeIdentifier(for url: URL) {
NSLog("requestBadgeIdentifierForURL: %@", url.path as NSString)
// For demonstration purposes, this picks one of our two badges, or no badge at all, based on the filename.
let whichBadge = abs(url.path.hash) % 3
let badgeIdentifier = ["", "One", "Two"][whichBadge]
FIFinderSyncController.default().setBadgeIdentifier(badgeIdentifier, for: url)
}
// MARK: - Menu and toolbar item support
override var toolbarItemName: String {
return "FinderSy"
}
override var toolbarItemToolTip: String {
return "FinderSy: Click the toolbar item for a menu."
}
override var toolbarItemImage: NSImage {
return NSImage(named: NSImage.cautionName)!
}
override func menu(for menuKind: FIMenuKind) -> NSMenu {
// Produce a menu for the extension.
let menu = NSMenu(title: "")
menu.addItem(withTitle: "Example Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "")
return menu
}
@IBAction func sampleAction(_ sender: AnyObject?) {
let target = FIFinderSyncController.default().targetedURL()
let items = FIFinderSyncController.default().selectedItemURLs()
let item = sender as! NSMenuItem
NSLog("sampleAction: menu item: %@, target = %@, items = ", item.title as NSString, target!.path as NSString)
for obj in items! {
NSLog(" %@", obj.path as NSString)
}
}
}
这里设置你的FinderSync监控路径,finder处于如下路径及子路径中,你的扩展程序才会生效
var myFolderURL = URL(fileURLWithPath: "/Users/Shared/MySyncExtension Documents")
如果你要监控整个磁盘可以设置为
var myFolderURL = URL(fileURLWithPath: "/")
进入设置的监控路径及子路径触发
override func beginObservingDirectory(at url: URL) {
// The user is now seeing the container's contents.
// If they see it in more than one view at a time, we're only told once.
NSLog("beginObservingDirectoryAtURL: %@", url.path as NSString)
}
离开触发如下方法
override func endObservingDirectory(at url: URL) {
// The user is no longer seeing the container's contents.
NSLog("endObservingDirectoryAtURL: %@", url.path as NSString)
}
工具栏菜单项名称、鼠标悬停提示文字以及图标的设置在如下方法内
override var toolbarItemName: String {
return "FinderSy"
}
override var toolbarItemToolTip: String {
return "FinderSy: Click the toolbar item for a menu."
}
override var toolbarItemImage: NSImage {
return NSImage(named: NSImage.cautionName)!
}
finder中的右键菜单以及工具栏菜单的显示都会在下面方法内处理
override func menu(for menuKind: FIMenuKind) -> NSMenu {
let menu = NSMenu(title: "")
//右键点击空白区域显示的菜单
if(menuKind == FIMenuKind.contextualMenuForItems) {
menu.addItem(withTitle: "Context Menu Item context", action: #selector(sampleAction(_:)), keyEquivalent: "")
}
//右键点击文件或文件夹显示的菜单
else if(menuKind == FIMenuKind.contextualMenuForItems) {
menu.addItem(withTitle: "Context Item Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "")
}
//点击工具栏图标显示的菜单
else if(menuKind == FIMenuKind.toolbarItemMenu) {
menu.addItem(withTitle: "Toolbar Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "")
}
//右键点击侧栏显示的菜单
else if(menuKind == FIMenuKind.contextualMenuForSidebar) {
menu.addItem(withTitle: "Sidebar Menu Item", action: #selector(sampleAction(_:)), keyEquivalent: "")
}
return menu
}
确保finder扩展已勾选
运行和普通应用不同,需要选择一个应用,选择finder即可
右键点击菜单如图
finder扩展程序的调试没办法运行断点调试,也不能控制台输出,调试是个很棘手的问题,添加如下方法把控制台输出写入日志文件,通过查看日志文件来一点点调试开发。如果某个操作后突然发现右键菜单消失了或者工具栏中的菜单项不见了,就去看日志吧,十之八九是异常退出了!
func initLogSetting(){
let tmpPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
let fileName = "/out.log"// 注意不是NSData!
let logFilePath = tmpPath.path!.appending(fileName)
freopen(logFilePath.cString(using: .utf8), "a+", stdout)
freopen(logFilePath.cString(using: .utf8), "a+", stderr);
//writeLog(str: logFilePath)
}
修改override init()方法增加
override init() {
super.init()
initLogSetting()
NSLog("FinderSync() launched from %@", Bundle.main.bundlePath as NSString)
// Set up the directory we are syncing.
FIFinderSyncController.default().directoryURLs = [self.myFolderURL]
// Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.colorPanelName)!, label: "Status One" , forBadgeIdentifier: "One")
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.cautionName)!, label: "Status Two", forBadgeIdentifier: "Two")
}
输出日志目录,因为启用了沙盒,文件会创建到沙盒目录下,
/Users/{用户}/Library/Containers/com.test.Macos-FinderSyncExtension.FinderSyncExt/Data/Documents/out.log
徽章功能不太实用,太多应用为了把自己的功能增加到右键菜单或工具栏中都实现了Finder扩展,例如解压缩软件、有道词典之类,徽章是否能显示成功取决于你的finder启动顺序是否是第一个或干脆是唯一一个,实际中不手动干预扩展设置,基本没机会显示出来。
指定可使用的徽章图标
// Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.colorPanelName)!, label: "Status One" , forBadgeIdentifier: "One")
FIFinderSyncController.default().setBadgeImage(NSImage(named: NSImage.cautionName)!, label: "Status Two", forBadgeIdentifier: "Two")
显示徽章,不是唯一的一个finder扩展或者启动顺序排第一个,就不要想进入这个方法了,都不会被调用到,鸡肋的功能
override func requestBadgeIdentifier(for url: URL) {
NSLog("requestBadgeIdentifierForURL: %@", url.path as NSString)
// For demonstration purposes, this picks one of our two badges, or no badge at all, based on the filename.
let whichBadge = abs(url.path.hash) % 2
let badgeIdentifier = ["One", "Two"][whichBadge]
FIFinderSyncController.default().setBadgeIdentifier(badgeIdentifier, for: url)
}