1. 初識Finder Sync
借助Finder Sync擴展,您可以注冊一個或多個文件夾進行監(jiān)控。接下來,您可以在監(jiān)控項目中設置標記并為其創(chuàng)建上下文菜單(contextual menus)。使用來自該應用擴展的API,您也能在Finder窗體中增加工具欄按鈕。
Finder Sync支持可同步本地文件夾與遠程數(shù)據(jù)源內(nèi)容的應用程序。它直接在Finder中提供即時的視覺反饋以改善用戶體驗。標記顯示了每個項目的同步狀態(tài),上下文菜單讓用戶可以管理文件夾內(nèi)容,自定義工具欄按鈕能調(diào)用全局操作,比如打開一個受監(jiān)視的文件夾或者進行同步操作。
提示:Finder Sync擴展僅能讓您修改用戶界面。它不進行實際的同步操作。您需要負責創(chuàng)建自己的同步組件。
Finder Sync擴展有如下功能:
(1).注冊一組用于監(jiān)視的文件夾。
(2).當用戶開始或停止瀏覽受監(jiān)視文件夾的內(nèi)容時收到通知。
比方說,當用戶在Finder或在打開/保存對話框中打開了受監(jiān)視的文件夾時,擴展將會收到消息通知。
(3).在受監(jiān)視文件夾中添加、移除以及更新項目上的標記。
(4).當用戶在受監(jiān)視文件夾中右鍵單擊了一個項目時,顯示一個上下文菜單。
(5).在Finder工具欄上添加自定義按鈕。
和標記以及上下文菜單不同,即使用戶當前沒有瀏覽受監(jiān)視的文件夾,這種按鈕仍然是始終可視的,
2. 指定受監(jiān)視的文件夾
Finder Sync應用擴展的init方法中指定想要監(jiān)視的文件夾,默認使用FIFinderSyncController對象。在絕大多數(shù)情況下,讓用戶在關聯(lián)應用程序提供的用戶界面中指定受監(jiān)視的文件夾。您可以在關聯(lián)應用程序和共享了用戶默認設置的Finder Sync應用擴展之間使用這個數(shù)據(jù)。
激活共享用戶設置,首先在Finder Sync應用擴展和其關聯(lián)應用程序中都添加一個應用程序組(Group)。這個組創(chuàng)建了應用擴展和其關聯(lián)應用程序都可以訪問、共享的容器。打開Xcode中每一個對象的Capabilities窗格,并激活App Groups。 然后對共享組提供唯一標識符。請務必對Finder Sync擴展和其關聯(lián)應用程序使用同一個標識符。


接下來,通過調(diào)用initWithSuiteName:方法以及使用共享組中的標識符來實例化一個新的NSUserDefaults對象。這種init方法將創(chuàng)建一個默認的用戶對象,用來加載和保存數(shù)據(jù)到共享容器中。
3. 代碼比較直觀
//
// FinderSync.m
// ZZXFinderSync
//
// Created by on 2021/11/11.
//
#import "FinderSync.h"
@interface FinderSync ()
@property NSURL *myFolderURL;
@end
@implementation FinderSync
- (instancetype)init {
self = [super init];
NSLog(@"%s launched from %@ ; compiled at %s", __PRETTY_FUNCTION__, [[NSBundle mainBundle] bundlePath], __TIME__);
// Set up the directory we are syncing.
self.myFolderURL = [NSURL fileURLWithPath:@"/Users/Shared/MySyncExtension Documents"];
[FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:self.myFolderURL];
// Set up images for our badge identifiers. For demonstration purposes, this uses off-the-shelf images.
[[FIFinderSyncController defaultController] setBadgeImage:[NSImage imageNamed: NSImageNameColorPanel] label:@"Status One" forBadgeIdentifier:@"One"];
[[FIFinderSyncController defaultController] setBadgeImage:[NSImage imageNamed: NSImageNameCaution] label:@"Status Two" forBadgeIdentifier:@"Two"];
[[FIFinderSyncController defaultController] setBadgeImage:[NSImage imageNamed: NSImageNameCaution] label:@"Status Two" forBadgeIdentifier:@"Three"];
[self writeToFile];
return self;
}
#pragma mark - 徽章處理 文件同步的代理
- (void)beginObservingDirectoryAtURL:(NSURL *)url {
NSLog(@"beginObservingDirectoryAtURL:%@", url.filePathURL);
/*
當用戶開始查看受監(jiān)視文件夾或其子文件夾的內(nèi)容時,系統(tǒng)將會調(diào)用此方法。它將當前打開文件夾的URL作為參數(shù)。
對于每個唯一的URL,系統(tǒng)只調(diào)用此方法一次。
只要這些內(nèi)容仍然在至少一個Finder窗口中可見,任何額外打開相同URL的Finder窗口的操作將被忽略。
系統(tǒng)為所有打開和保存對話框額外創(chuàng)建您擴展的實例。
這些擴展將自己調(diào)用beginObservingDirectoryAtURL:方法,即使該文件夾已經(jīng)在Finder窗口中打開。
*/
}
- (void)endObservingDirectoryAtURL:(NSURL *)url {
NSLog(@"endObservingDirectoryAtURL:%@", url.filePathURL);
/*
當用戶不再查看給定URL中的內(nèi)容時,系統(tǒng)將會調(diào)用此方法.
與beginObservingDirectoryAtURL方法一樣,打開和保存對話框分別在Finder中被額外創(chuàng)建擴展實例。
*/
}
- (void)requestBadgeIdentifierForURL:(NSURL *)url {
NSLog(@"requestBadgeIdentifierForURL:%@", url.filePathURL);
/*
當受監(jiān)視文件夾中的一個新項目變得對用戶可見時,系統(tǒng)將會調(diào)用此方法。當每個文件首次在Finder視圖中顯示,這個方法就會被調(diào)用一次。每當新文件滾動到視圖中,系統(tǒng)也會繼續(xù)調(diào)用此方法。
您通常執(zhí)行此方法來檢查所提供URL對應項目的狀態(tài),然后調(diào)用Finder Sync控制器的setBadgeIdentifier:forURL:方法來設置相應的標記。您可能還想要跟蹤這些URL,只要它們的狀態(tài)發(fā)生變化就更新他們的標記。
*/
NSString *nsFilePath = [url.filePathURL path];
NSFileManager *fileManager = [NSFileManager defaultManager];
// 文件夾不處理。
BOOL isDir;
[fileManager fileExistsAtPath:nsFilePath isDirectory:&isDir];
if (isDir) return;
// 獲取文件的修改時間。
NSDictionary *attrDict = [fileManager attributesOfItemAtPath:nsFilePath error:nil];
NSDate *modDate = [attrDict objectForKey:NSFileModificationDate];
NSLog(@"?????? 修改時間 ----%@",modDate);
// /private/var/folders/kt/k000ltc54fz78ffbxjdvg63w0000gn/T/com.zzx.MacWithXPC.ZZXFinderSync
//徽章處理
NSInteger whichBadge = [url.filePathURL hash] % 3;
NSString* badgeIdentifier = @[@"One", @"Two", @"three"][whichBadge];
[[FIFinderSyncController defaultController] setBadgeIdentifier:badgeIdentifier forURL:url];
}
#pragma mark - 菜單處理 添加上下文菜單欄
/// 新增選項
- (NSString *)toolbarItemName {
return @"ZZXFinderSync";
}
/// 新增選項 -簡介
- (NSString *)toolbarItemToolTip {
return @"ZZXFinderSync: Click the toolbar item for a menu.";
}
/// 新增選項 - icon
- (NSImage *)toolbarItemImage {
return [NSImage imageNamed:NSImageNameCaution];
}
- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu {
/*
打開文件夾時刷新
選中文件時也會刷新
*/
/*
下面這個不知道怎么用?
實現(xiàn)menuForMenuKind:方法以提供一個自定義的上下文菜單。
該menu參數(shù)指明了您的應用擴展應該創(chuàng)建的菜單類型。每個菜單類型對應不同的用戶交互類型。
FIMenuKindContextualMenuForItems
用戶右鍵單擊單個或多個受監(jiān)控文件夾內(nèi)的項目。您的應用擴展應當顯示影響所選項目的菜單項。
FIMenuKindContextualMenuForContainer
當用戶瀏覽受監(jiān)控文件夾時右鍵單擊Finder窗口的空白處。您的擴展應當顯示影響當前文件夾內(nèi)容的菜單項。
FIMenuKindContextualMenuForSidebar
用戶右鍵單擊顯示受監(jiān)控文件夾及其部分內(nèi)容的側邊欄。您的擴展應當顯示影響所選項內(nèi)容的菜單項。
FIMenuKindToolbarItemMenu
用戶單擊擴展提供的導航欄按鈕。由于導航欄按鈕始終可見,用戶此時有可能沒在瀏覽受監(jiān)控的文件夾。您的擴展或許要顯示能始終讓用戶可見的全局操作的菜單欄。如果存在某個菜單項,其仍然可以影響在受監(jiān)控文件夾中所選的項目。
*/
NSMenu *mainMenu = [[NSMenu alloc] init];
NSMenuItem *menuItem1 = [[NSMenuItem alloc] initWithTitle:@"菜單1" action:nil keyEquivalent:@""];
NSMenuItem *menuItem2 = [[NSMenuItem alloc] initWithTitle:@"菜單2" action:nil keyEquivalent:@""];
[mainMenu addItem:menuItem1];
[mainMenu insertItem:menuItem2 atIndex:0];
//當前選中的文件
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
if (items.count > 0) {
//單獨設置菜單的顯示
NSString *nsFilePath = [items.firstObject path];
NSLog(@"?????? nsFilePath ----%@----%@",nsFilePath,target.path);
}
NSInteger indexItem = 0;
NSMenu *subMenu1 = [[NSMenu alloc] init];
//-*開始 測試用
NSMenuItem *restoreItem = [[NSMenuItem alloc] initWithTitle:@"選項1" action:@selector(restoreFile:) keyEquivalent:@""];
[restoreItem setTarget:self];
[restoreItem setEnabled:YES];
[subMenu1 insertItem:restoreItem atIndex:indexItem];
indexItem++;
NSMenuItem *decryptItem = [[NSMenuItem alloc] initWithTitle:@"選項2" action:@selector(decryptFile:) keyEquivalent:@""];
[decryptItem setTarget:self];
[decryptItem setEnabled:YES];
[subMenu1 insertItem:decryptItem atIndex:indexItem];
indexItem++;
NSMenuItem *encryptItem = [[NSMenuItem alloc] initWithTitle:@"選項3" action:@selector(encryptFile:) keyEquivalent:@""];
[encryptItem setTarget:self];
[encryptItem setEnabled:YES];
[subMenu1 insertItem:encryptItem atIndex:indexItem];
indexItem++;
NSMenu *subMenu2 = [[NSMenu alloc] init];
NSMenuItem *encryptItem1 = [[NSMenuItem alloc] initWithTitle:@"選項4" action:@selector(encryptFile:) keyEquivalent:@""];
[encryptItem1 setTarget:self];
[encryptItem1 setEnabled:YES];
[subMenu2 insertItem:encryptItem1 atIndex:0];
indexItem++;
NSMenuItem *encryptItem2 = [[NSMenuItem alloc] initWithTitle:@"選項5" action:@selector(encryptFile:) keyEquivalent:@""];
[encryptItem2 setTarget:self];
[encryptItem2 setEnabled:YES];
[subMenu2 insertItem:encryptItem2 atIndex:1];
indexItem++;
[mainMenu setSubmenu:subMenu1 forItem:menuItem1];
[mainMenu setSubmenu:subMenu2 forItem:menuItem2];
return mainMenu;
}
- (IBAction)sampleAction:(id)sender {
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
NSLog(@"sampleAction: menu item: %@, target = %@, items = ", [sender title], [target filePathURL]);
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"文件路徑:%@", [obj path]);
//設置徽標
NSString *filePath = [obj path];
[[FIFinderSyncController defaultController] setBadgeIdentifier:@"Three" forURL:[NSURL fileURLWithPath:filePath]];
}];
}
- (IBAction)restoreFile:(id)sender {
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
NSLog(@"sampleAction: menu item: %@, target = %@, items = ", [sender title], [target filePathURL]);
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@" %@", [obj filePathURL]);
}];
}
- (IBAction)decryptFile:(id)sender {
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
NSLog(@"sampleAction: menu item: %@, target = %@, items = ", [sender title], [target filePathURL]);
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@" %@", [obj filePathURL]);
}];
}
- (IBAction)encryptFile:(id)sender {
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
NSLog(@"sampleAction: menu item: %@, target = %@, items = ", [sender title], [target filePathURL]);
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@" %@", [obj filePathURL]);
}];
}
-(void)writeToFile{
NSString *tmpPath = NSTemporaryDirectory();
NSString *fileName = [NSString stringWithFormat:@"ZZXFinderSync.log"];// 注意不是NSData!
NSString *logFilePath = [tmpPath stringByAppendingPathComponent:fileName];
// NSString *logFilePath = @"/Users/mac/Desktop/ZZX/ZZXFinderSync.log";
// 將log輸入到文件
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}
@end
4. 運行
選擇ZZXFinderSync來運行,依附于Finder,然后Run:


運行成功后,打開一個文件夾,就可以看到效果,點擊菜單打印結果。

或者

4. 調(diào)試
可以將控制臺打印寫到文件中方便調(diào)試
-(void)writeToFile{
NSString *tmpPath = NSTemporaryDirectory();
NSString *fileName = [NSString stringWithFormat:@"ZZXFinderSync.log"];// 注意不是NSData!
NSString *logFilePath = [tmpPath stringByAppendingPathComponent:fileName];
// 將log輸入到文件
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}