Mac OS™-Finder Sync使用入门

1. 初识Finder Sync

借助Finder Sync扩展,您可以注册一个或多个文件夹进行监控。接下来,您可以在监控项目中设置标记并为其创建上下文菜单(contextual menus)。使用来自该应用扩展的API,您也能在Finder窗体中增加工具栏按钮。

Finder Sync支持可同步本地文件夹与远程数据源内容的应用程序。它直接在Finder中提供即时的视觉反馈以改善用户体验。标记显示了每个项目的同步状态,上下文菜单让用户可以管理文件夹内容,自定义工具栏按钮能调用全局操作,比如打开一个受监视的文件夹或者进行同步操作。

提示:Finder Sync扩展仅能让您修改用户界面。它不进行实际的同步操作。您需要负责创建自己的同步组件。

Finder Sync扩展有如下功能:

(1).注册一组用于监视的文件夹。

(2).当用户开始或停止浏览受监视文件夹的内容时收到通知。

比方说,当用户在Finder或在打开/保存对话框中打开了受监视的文件夹时,扩展将会收到消息通知。

(3).在受监视文件夹中添加、移除以及更新项目上的标记。

(4).当用户在受监视文件夹中右键单击了一个项目时,显示一个上下文菜单。

(5).在Finder工具栏上添加自定义按钮。

和标记以及上下文菜单不同,即使用户当前没有浏览受监视的文件夹,这种按钮仍然是始终可视的,

2. 指定受监视的文件夹

Finder Sync应用扩展的init方法中指定想要监视的文件夹,默认使用FIFinderSyncController对象。在绝大多数情况下,让用户在关联应用程序提供的用户界面中指定受监视的文件夹。您可以在关联应用程序和共享了用户默认设置的Finder Sync应用扩展之间使用这个数据。

激活共享用户设置,首先在Finder Sync应用扩展和其关联应用程序中都添加一个应用程序组(Group)。这个组创建了应用扩展和其关联应用程序都可以访问、共享的容器。打开Xcode中每一个对象的Capabilities窗格,并激活App Groups。 然后对共享组提供唯一标识符。请务必对Finder Sync扩展和其关联应用程序使用同一个标识符。

image.png
image.png

接下来,通过调用initWithSuiteName:方法以及使用共享组中的标识符来实例化一个新的NSUserDefaults对象。这种init方法将创建一个默认的用户对象,用来加载和保存数据到共享容器中。

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);
    /*
     当用户开始查看受监视文件夹或其子文件夹的内容时,系统将会调用此方法。它将当前打开文件夹的URL作为参数。
     对于每个唯一的URL,系统只调用此方法一次。
     只要这些内容仍然在至少一个Finder窗口中可见,任何额外打开相同URL的Finder窗口的操作将被忽略。
     系统为所有打开和保存对话框额外创建您扩展的实例。
     这些扩展将自己调用beginObservingDirectoryAtURL:方法,即使该文件夹已经在Finder窗口中打开。
     */
}

- (void)endObservingDirectoryAtURL:(NSURL *)url {
    NSLog(@"endObservingDirectoryAtURL:%@", url.filePathURL);
    /*
     当用户不再查看给定URL中的内容时,系统将会调用此方法.
     与beginObservingDirectoryAtURL方法一样,打开和保存对话框分别在Finder中被额外创建扩展实例。
     */
}

- (void)requestBadgeIdentifierForURL:(NSURL *)url {
    NSLog(@"requestBadgeIdentifierForURL:%@", url.filePathURL);
    /*
     当受监视文件夹中的一个新项目变得对用户可见时,系统将会调用此方法。当每个文件首次在Finder视图中显示,这个方法就会被调用一次。每当新文件滚动到视图中,系统也会继续调用此方法。

     您通常执行此方法来检查所提供URL对应项目的状态,然后调用Finder Sync控制器的setBadgeIdentifier:forURL:方法来设置相应的标记。您可能还想要跟踪这些URL,只要它们的状态发生变化就更新他们的标记。
     */
    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 {
    /*
     打开文件夹时刷新
     选中文件时也会刷新
     */
    
    /*
     下面这个不知道怎么用?
     实现menuForMenuKind:方法以提供一个自定义的上下文菜单。
     该menu参数指明了您的应用扩展应该创建的菜单类型。每个菜单类型对应不同的用户交互类型。

     FIMenuKindContextualMenuForItems
     用户右键单击单个或多个受监控文件夹内的项目。您的应用扩展应当显示影响所选项目的菜单项。

     FIMenuKindContextualMenuForContainer
     当用户浏览受监控文件夹时右键单击Finder窗口的空白处。您的扩展应当显示影响当前文件夹内容的菜单项。

     FIMenuKindContextualMenuForSidebar
     用户右键单击显示受监控文件夹及其部分内容的侧边栏。您的扩展应当显示影响所选项内容的菜单项。

     FIMenuKindToolbarItemMenu
     用户单击扩展提供的导航栏按钮。由于导航栏按钮始终可见,用户此时有可能没在浏览受监控的文件夹。您的扩展或许要显示能始终让用户可见的全局操作的菜单栏。如果存在某个菜单项,其仍然可以影响在受监控文件夹中所选的项目。
     */

    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:

image.png
image.png

运行成功后,打开一个文件夹,就可以看到效果,点击菜单打印结果。

image.png

或者


image.png

4. 调试

可以将控制台打印写到文件中方便调试

-(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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容