1. 初識(shí)Finder Sync
借助Finder Sync擴(kuò)展购公,您可以注冊(cè)一個(gè)或多個(gè)文件夾進(jìn)行監(jiān)控秉继。接下來,您可以在監(jiān)控項(xiàng)目中設(shè)置標(biāo)記并為其創(chuàng)建上下文菜單(contextual menus)糙俗。使用來自該應(yīng)用擴(kuò)展的API垃瞧,您也能在Finder窗體中增加工具欄按鈕。
Finder Sync支持可同步本地文件夾與遠(yuǎn)程數(shù)據(jù)源內(nèi)容的應(yīng)用程序船庇。它直接在Finder中提供即時(shí)的視覺反饋以改善用戶體驗(yàn)吭产。標(biāo)記顯示了每個(gè)項(xiàng)目的同步狀態(tài),上下文菜單讓用戶可以管理文件夾內(nèi)容溢十,自定義工具欄按鈕能調(diào)用全局操作垮刹,比如打開一個(gè)受監(jiān)視的文件夾或者進(jìn)行同步操作。
提示:Finder Sync擴(kuò)展僅能讓您修改用戶界面张弛。它不進(jìn)行實(shí)際的同步操作荒典。您需要負(fù)責(zé)創(chuàng)建自己的同步組件。
Finder Sync擴(kuò)展有如下功能:
(1).注冊(cè)一組用于監(jiān)視的文件夾吞鸭。
(2).當(dāng)用戶開始或停止瀏覽受監(jiān)視文件夾的內(nèi)容時(shí)收到通知寺董。
比方說,當(dāng)用戶在Finder或在打開/保存對(duì)話框中打開了受監(jiān)視的文件夾時(shí)刻剥,擴(kuò)展將會(huì)收到消息通知遮咖。
(3).在受監(jiān)視文件夾中添加、移除以及更新項(xiàng)目上的標(biāo)記造虏。
(4).當(dāng)用戶在受監(jiān)視文件夾中右鍵單擊了一個(gè)項(xiàng)目時(shí)御吞,顯示一個(gè)上下文菜單。
(5).在Finder工具欄上添加自定義按鈕漓藕。
和標(biāo)記以及上下文菜單不同陶珠,即使用戶當(dāng)前沒有瀏覽受監(jiān)視的文件夾,這種按鈕仍然是始終可視的享钞,
2. 指定受監(jiān)視的文件夾
Finder Sync應(yīng)用擴(kuò)展的init方法中指定想要監(jiān)視的文件夾揍诽,默認(rèn)使用FIFinderSyncController對(duì)象。在絕大多數(shù)情況下,讓用戶在關(guān)聯(lián)應(yīng)用程序提供的用戶界面中指定受監(jiān)視的文件夾暑脆。您可以在關(guān)聯(lián)應(yīng)用程序和共享了用戶默認(rèn)設(shè)置的Finder Sync應(yīng)用擴(kuò)展之間使用這個(gè)數(shù)據(jù)渠啤。
激活共享用戶設(shè)置,首先在Finder Sync應(yīng)用擴(kuò)展和其關(guān)聯(lián)應(yīng)用程序中都添加一個(gè)應(yīng)用程序組(Group)添吗。這個(gè)組創(chuàng)建了應(yīng)用擴(kuò)展和其關(guān)聯(lián)應(yīng)用程序都可以訪問沥曹、共享的容器。打開Xcode中每一個(gè)對(duì)象的Capabilities窗格根资,并激活A(yù)pp Groups架专。 然后對(duì)共享組提供唯一標(biāo)識(shí)符同窘。請(qǐng)務(wù)必對(duì)Finder Sync擴(kuò)展和其關(guān)聯(lián)應(yīng)用程序使用同一個(gè)標(biāo)識(shí)符玄帕。
接下來,通過調(diào)用initWithSuiteName:方法以及使用共享組中的標(biāo)識(shí)符來實(shí)例化一個(gè)新的NSUserDefaults對(duì)象想邦。這種init方法將創(chuàng)建一個(gè)默認(rèn)的用戶對(duì)象裤纹,用來加載和保存數(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);
/*
當(dāng)用戶開始查看受監(jiān)視文件夾或其子文件夾的內(nèi)容時(shí)丧没,系統(tǒng)將會(huì)調(diào)用此方法鹰椒。它將當(dāng)前打開文件夾的URL作為參數(shù)。
對(duì)于每個(gè)唯一的URL呕童,系統(tǒng)只調(diào)用此方法一次漆际。
只要這些內(nèi)容仍然在至少一個(gè)Finder窗口中可見,任何額外打開相同URL的Finder窗口的操作將被忽略夺饲。
系統(tǒng)為所有打開和保存對(duì)話框額外創(chuàng)建您擴(kuò)展的實(shí)例奸汇。
這些擴(kuò)展將自己調(diào)用beginObservingDirectoryAtURL:方法,即使該文件夾已經(jīng)在Finder窗口中打開往声。
*/
}
- (void)endObservingDirectoryAtURL:(NSURL *)url {
NSLog(@"endObservingDirectoryAtURL:%@", url.filePathURL);
/*
當(dāng)用戶不再查看給定URL中的內(nèi)容時(shí)擂找,系統(tǒng)將會(huì)調(diào)用此方法.
與beginObservingDirectoryAtURL方法一樣,打開和保存對(duì)話框分別在Finder中被額外創(chuàng)建擴(kuò)展實(shí)例浩销。
*/
}
- (void)requestBadgeIdentifierForURL:(NSURL *)url {
NSLog(@"requestBadgeIdentifierForURL:%@", url.filePathURL);
/*
當(dāng)受監(jiān)視文件夾中的一個(gè)新項(xiàng)目變得對(duì)用戶可見時(shí)贯涎,系統(tǒng)將會(huì)調(diào)用此方法。當(dāng)每個(gè)文件首次在Finder視圖中顯示慢洋,這個(gè)方法就會(huì)被調(diào)用一次塘雳。每當(dāng)新文件滾動(dòng)到視圖中,系統(tǒng)也會(huì)繼續(xù)調(diào)用此方法普筹。
您通常執(zhí)行此方法來檢查所提供URL對(duì)應(yīng)項(xiàng)目的狀態(tài)败明,然后調(diào)用Finder Sync控制器的setBadgeIdentifier:forURL:方法來設(shè)置相應(yīng)的標(biāo)記。您可能還想要跟蹤這些URL斑芜,只要它們的狀態(tài)發(fā)生變化就更新他們的標(biāo)記肩刃。
*/
NSString *nsFilePath = [url.filePathURL path];
NSFileManager *fileManager = [NSFileManager defaultManager];
// 文件夾不處理。
BOOL isDir;
[fileManager fileExistsAtPath:nsFilePath isDirectory:&isDir];
if (isDir) return;
// 獲取文件的修改時(shí)間。
NSDictionary *attrDict = [fileManager attributesOfItemAtPath:nsFilePath error:nil];
NSDate *modDate = [attrDict objectForKey:NSFileModificationDate];
NSLog(@"?????? 修改時(shí)間 ----%@",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 - 菜單處理 添加上下文菜單欄
/// 新增選項(xiàng)
- (NSString *)toolbarItemName {
return @"ZZXFinderSync";
}
/// 新增選項(xiàng) -簡(jiǎn)介
- (NSString *)toolbarItemToolTip {
return @"ZZXFinderSync: Click the toolbar item for a menu.";
}
/// 新增選項(xiàng) - icon
- (NSImage *)toolbarItemImage {
return [NSImage imageNamed:NSImageNameCaution];
}
- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu {
/*
打開文件夾時(shí)刷新
選中文件時(shí)也會(huì)刷新
*/
/*
下面這個(gè)不知道怎么用盈包?
實(shí)現(xiàn)menuForMenuKind:方法以提供一個(gè)自定義的上下文菜單沸呐。
該menu參數(shù)指明了您的應(yīng)用擴(kuò)展應(yīng)該創(chuàng)建的菜單類型。每個(gè)菜單類型對(duì)應(yīng)不同的用戶交互類型呢燥。
FIMenuKindContextualMenuForItems
用戶右鍵單擊單個(gè)或多個(gè)受監(jiān)控文件夾內(nèi)的項(xiàng)目崭添。您的應(yīng)用擴(kuò)展應(yīng)當(dāng)顯示影響所選項(xiàng)目的菜單項(xiàng)。
FIMenuKindContextualMenuForContainer
當(dāng)用戶瀏覽受監(jiān)控文件夾時(shí)右鍵單擊Finder窗口的空白處叛氨。您的擴(kuò)展應(yīng)當(dāng)顯示影響當(dāng)前文件夾內(nèi)容的菜單項(xiàng)呼渣。
FIMenuKindContextualMenuForSidebar
用戶右鍵單擊顯示受監(jiān)控文件夾及其部分內(nèi)容的側(cè)邊欄。您的擴(kuò)展應(yīng)當(dāng)顯示影響所選項(xiàng)內(nèi)容的菜單項(xiàng)寞埠。
FIMenuKindToolbarItemMenu
用戶單擊擴(kuò)展提供的導(dǎo)航欄按鈕屁置。由于導(dǎo)航欄按鈕始終可見,用戶此時(shí)有可能沒在瀏覽受監(jiān)控的文件夾仁连。您的擴(kuò)展或許要顯示能始終讓用戶可見的全局操作的菜單欄蓝角。如果存在某個(gè)菜單項(xiàng),其仍然可以影響在受監(jiān)控文件夾中所選的項(xiàng)目饭冬。
*/
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];
//當(dāng)前選中的文件
NSURL* target = [[FIFinderSyncController defaultController] targetedURL];
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
if (items.count > 0) {
//單獨(dú)設(shè)置菜單的顯示
NSString *nsFilePath = [items.firstObject path];
NSLog(@"?????? nsFilePath ----%@----%@",nsFilePath,target.path);
}
NSInteger indexItem = 0;
NSMenu *subMenu1 = [[NSMenu alloc] init];
//-*開始 測(cè)試用
NSMenuItem *restoreItem = [[NSMenuItem alloc] initWithTitle:@"選項(xiàng)1" action:@selector(restoreFile:) keyEquivalent:@""];
[restoreItem setTarget:self];
[restoreItem setEnabled:YES];
[subMenu1 insertItem:restoreItem atIndex:indexItem];
indexItem++;
NSMenuItem *decryptItem = [[NSMenuItem alloc] initWithTitle:@"選項(xiàng)2" action:@selector(decryptFile:) keyEquivalent:@""];
[decryptItem setTarget:self];
[decryptItem setEnabled:YES];
[subMenu1 insertItem:decryptItem atIndex:indexItem];
indexItem++;
NSMenuItem *encryptItem = [[NSMenuItem alloc] initWithTitle:@"選項(xiàng)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:@"選項(xiàng)4" action:@selector(encryptFile:) keyEquivalent:@""];
[encryptItem1 setTarget:self];
[encryptItem1 setEnabled:YES];
[subMenu2 insertItem:encryptItem1 atIndex:0];
indexItem++;
NSMenuItem *encryptItem2 = [[NSMenuItem alloc] initWithTitle:@"選項(xiàng)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]);
//設(shè)置徽標(biāo)
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. 運(yùn)行
選擇ZZXFinderSync來運(yùn)行使鹅,依附于Finder,然后Run:
運(yùn)行成功后昌抠,打開一個(gè)文件夾患朱,就可以看到效果,點(diǎn)擊菜單打印結(jié)果炊苫。
或者
4. 調(diào)試
可以將控制臺(tái)打印寫到文件中方便調(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);
}