16

In my OS X app, I want to let the user edit images with the appropriate action extensions installed on his/her Mac, e.g the image markup extension as present in Mail.app or TextEdit (for RTFD files with images) - or Pixelmator's repair tool if available. As far as I can recall, Apple announces at WWDC '14 there would be a public API for this task.

Unfortunately I cannot find any starting point on how to use extensions from a host app perspective, neither documentation- nor sample-code-wise.

I found out that you have to set the undocumented style property of the NSSharingPicker to a non-zero value like this:

- (IBAction)testSharingPicker:(id)sender
{
    NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[[self.listing.images.firstObject thumbImage]]];

    [picker setValue:@(1) forKey:@"style"];
    [picker setDelegate:self];

    [picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
}

Once the style value is set, you know you are on the right track, because - (NSArray *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NSArray *)proposedServicesis called with the image editing extensions installed on my system, instead of the regular sharing extensions.

You also need to implement an undocumented delegate method:

- (BOOL)sharingServicePicker:(NSSharingServicePicker *)sharingService shouldShowForView:(NSView*) inView
{
    return YES;
}

But still, the picker is not showing up. All I get is some weird border around the senderbutton.

Teja Nandamuri
  • 11,045
  • 6
  • 57
  • 109
iljawascoding
  • 1,090
  • 9
  • 20
  • Did you play with `-sharingService:sourceFrameOnScreenForShareItem:` `- sharingService:transitionImageForShareItem:contentRect:` and `-sharingService:sourceWindowForShareItems:sharingContentScope:` ? – catlan Feb 03 '15 at 15:43
  • Yes, I implemented all these methods. Doesn't make any difference. I don't get to the point where they are called. – iljawascoding Feb 03 '15 at 15:51
  • eMail or file an enhancement request (bug report @:) and request an official answer. – geowar Feb 27 '15 at 01:51
  • Note that the result of the OP's discoveries are available on GitHub and worth a look! https://github.com/iljaiwas/NSSharingPickerTest – ctietze Jun 19 '19 at 10:39

2 Answers2

5

Unfortunately, it seems Apple only really exposes Action Extensions through NSTextView at the present time.

On OS X, NSTextView plays the central role in presenting Extensions to the users.

That is achievable by creating a NSTextView and inserting an image into it as a NSFileWrapper, for example (this code adapted from TextEdit):

NSMutableAttributedString *attachments = [[NSMutableAttributedString alloc] init];
NSError *error;
    NSFileWrapper *wrapper = [[NSFileWrapper alloc] initWithURL:[NSURL fileURLWithPath:@"/Users/user/Downloads/Test.png"] options:NSFileWrapperReadingImmediate error:&error];
    if (wrapper) {
        NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:wrapper];
        [attachments appendAttributedString:[NSAttributedString attributedStringWithAttachment:attachment]];
}

if ([attachments length] > 0) {
    NSRange selectionRange = [self.textView selectedRange];
    if ([self.textView shouldChangeTextInRange:selectionRange replacementString:[attachments string]]) {
        [[self.textView textStorage] replaceCharactersInRange:selectionRange withAttributedString:attachments];
        [self.textView didChangeText];
    }
}

NSTextView displaying Extensions

(Note that inserting an image as a NSTextAttachmentCell causes a weird crash – rdar://20333977)

That weird border you mention seems to be the Sharing Service Picker that's shown when hovering over an image in a NSTextView, however obscured slightly by the button.

NSButton displaying private NSSharingServicePicker menu

I stumbled on your GitHub repo and have contributed what I've learnt about it in this pull request.

I've achieved a hacky workaround by presenting the NSSharingServicePicker as described in your question and then holding onto the NSSharingServices returned in sharingServicePicker:sharingServicesForItems:proposedSharingServices:. The services themselves can then be invoked with performWithItems: and data is returned in sharingService:didShareItems:.

My code is available here.

Josh
  • 2,324
  • 1
  • 21
  • 29
  • +1 The resulting GitHub repo is pure gold for reference material: https://github.com/iljaiwas/NSSharingPickerTest – ctietze Jun 19 '19 at 10:38
0

Actually,hacking "style" property and implementing NSSharingServicePickerDelegate are unnecessary,the following code is more eligible(of course,you should implement NSSharingServiceDelegate protocol):

- (IBAction)onServiceBtnClick:(id)sender {
NSURL *url = [self.quickLookItems firstObject];
NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
if (image) {
    self.serviceBtn.hidden = YES;

    NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Sharing"];

    NSSharingService *service = [NSSharingService sharingServiceNamed:@"com.apple.Preview.Markup"];
    service.delegate = self;
    self.markupService = service;
    NSArray *services = @[ service ];

    for (NSSharingService *service in services) {
        NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:service.title action:@selector(share:) keyEquivalent:@""];
        [menu addItem:menuItem];
    }

    [menu popUpMenuPositioningItem:nil atLocation:[NSEvent mouseLocation] inView:nil];


    }
}

- (void)share:(NSMenuItem *)sender{
     NSURL *imageURL = [self.quickLookItems firstObject];
     [self.markupService performWithItems:@[imageURL]];
 }
Jagie
  • 2,190
  • 3
  • 27
  • 25