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).
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.
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.
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
}
}
let provider = ItemProvider.init(placeholderItem: "SomeString")
let vc = UIActivityViewController.init(activityItems: [provider], applicationActivities: nil)
self.present(vc, animated: true, completion: nil)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With