Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel UIActivityItemProvider and don't show activity?

I'm using UIActivityItemProvider subclass to provide custom data. But sometimes getting data fails and I don't want to present activity (e.g. message composer). Tried [self cancel] and return nil; in item method, but message composer still shows (with empty message).

like image 333
feduza Avatar asked Feb 26 '14 09:02

feduza


2 Answers

If you dismiss the UIActivityViewController before returning from -(id)item it will not present the users chosen activity.

To do this you first need to grab the activityViewController in activityViewControllerPlaceholderItem. In -(id)item run code in a dispatch_async to update progress and dismiss on complete / error which I'm doing using a promise lib.

In your subclass of UIActivityItemProvider do something similar to the example below.

-(id) activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{ self.avc = activityViewController;
  return NSURL;
}

-(id)item
{ __block BOOL fileProcDone = NO;
  dispatch_async(dispatch_get_main_queue(), ^
  { self.pvc = [[ProgressAlertVC alloc] init];
    [self.vc presentViewController:self.pvc animated:YES completion:nil];
    [[[[self promiseMakeFile]
    progressed:^(float progress)
    { self.pvc.progress = progress;
    }]
    fulfilled:^(id result)
    { [self.pvc dismissViewControllerAnimated:YES completion:^
      { fileProcDone = YES;
      }];
    }]
    failed:^(NSError *error)
    { [self.pvc dismissViewControllerAnimated:YES completion:^
      { [self.vc dismissViewControllerAnimated:YES completion:^
        { fileProcDone = YES;
        }];
      }];
    }];
  });
  while (!fileProcDone)
  { [NSThread sleepForTimeInterval:0.1];
  }; 
  return NSURL;
}

This will result in a console log message from activity extensions but as long as they deal correctly with errors things should be fine. If you return nil from -(id)activityViewController: itemForActivityType: you don't get console errors but will get the users chosen activity even if you dismiss the UIActivityViewController at this point.

like image 93
Evan Michael Kyle Avatar answered Oct 23 '22 00:10

Evan Michael Kyle


You simply need to call the cancel method of UIActivityItemProvider. Since UIActivityItemProvider is an NSOperation, calling cancel will mark the operation cancelled.

At that point, you have a few options to actually stop the long running task, depending on the structure of your task. You could override the cancel method and do your cancellation there, just be sure to call [super cancel] as well. The second option is the check the value of isCancelled within the item method.

An example item provider

import UIKit
import Dispatch

class ItemProvider: UIActivityItemProvider {

    override public var item: Any {
        let semaphore = DispatchSemaphore(value: 0)

        let message = "This will stop the entire share flow until you press OK. It represents a long running task."
        let alert = UIAlertController.init(title: "Hello", message: message, preferredStyle: .alert)
        let action = UIAlertAction.init(title: "OK", style: .default, handler:
            { action in
                semaphore.signal()
        })
        let cancel = UIAlertAction.init(title: "CANCEL", style: .destructive, handler:
            { [weak self] action in
                self?.cancel()
                semaphore.signal()
        })

        alert.addAction(action)
        alert.addAction(cancel)

        //Truly, some hacking to for the purpose of demonstrating the solution
        DispatchQueue.main.async {
            UIApplication.shared.delegate?.window??.rootViewController?.presentedViewController!.present(alert, animated: true, completion: nil)
        }

        // We can block here, because our long running task is in another queue
        semaphore.wait()

        // Once the item is properly cancelled, it doesn't really matter what you return
        return NSURL.init(string: "blah") as Any
    }
}

In the view controller, start a share activity like this.

    let provider = ItemProvider.init(placeholderItem: "SomeString")
    let vc = UIActivityViewController.init(activityItems: [provider], applicationActivities: nil)

    self.present(vc, animated: true, completion: nil)
like image 1
allenh Avatar answered Oct 23 '22 02:10

allenh